Code

Merge branch 'jk/maint-diffstat-overflow' into maint
authorJunio C Hamano <gitster@pobox.com>
Fri, 23 Apr 2010 05:29:13 +0000 (22:29 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 23 Apr 2010 05:29:13 +0000 (22:29 -0700)
* jk/maint-diffstat-overflow:
  diff: use large integers for diffstat calculations

1  2 
diff.c

diff --combined diff.c
index ca37d44b94a1b89f16e701f28ab63dd5ff78cd54,0182237c2dab7fa3aa7e916f2eb8bc75865c0b9a..edec0f6b818541b79e2773934ab1d28ce279805c
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -14,7 -14,6 +14,7 @@@
  #include "userdiff.h"
  #include "sigchain.h"
  #include "submodule.h"
 +#include "ll-merge.h"
  
  #ifdef NO_FAST_WORKING_DIRECTORY
  #define FAST_WORKING_DIRECTORY 0
@@@ -195,7 -194,6 +195,7 @@@ struct emit_callback 
        struct diff_words_data *diff_words;
        int *found_changesp;
        FILE *file;
 +      struct strbuf *header;
  };
  
  static int count_lines(const char *data, int size)
@@@ -799,11 -797,6 +799,11 @@@ static void fn_out_consume(void *priv, 
        const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
        const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
  
 +      if (ecbdata->header) {
 +              fprintf(ecbdata->file, "%s", ecbdata->header->buf);
 +              strbuf_reset(ecbdata->header);
 +              ecbdata->header = NULL;
 +      }
        *(ecbdata->found_changesp) = 1;
  
        if (ecbdata->label_path[0]) {
@@@ -949,7 -942,7 +949,7 @@@ struct diffstat_t 
                unsigned is_unmerged:1;
                unsigned is_binary:1;
                unsigned is_renamed:1;
-               unsigned int added, deleted;
+               uintmax_t added, deleted;
        } **files;
  };
  
@@@ -1041,7 -1034,7 +1041,7 @@@ static void fill_print_name(struct diff
  static void show_stats(struct diffstat_t *data, struct diff_options *options)
  {
        int i, len, add, del, adds = 0, dels = 0;
-       int max_change = 0, max_len = 0;
+       uintmax_t max_change = 0, max_len = 0;
        int total_files = data->nr;
        int width, name_width;
        const char *reset, *set, *add_c, *del_c;
  
        for (i = 0; i < data->nr; i++) {
                struct diffstat_file *file = data->files[i];
-               int change = file->added + file->deleted;
+               uintmax_t change = file->added + file->deleted;
                fill_print_name(file);
                len = strlen(file->print_name);
                if (max_len < len)
        for (i = 0; i < data->nr; i++) {
                const char *prefix = "";
                char *name = data->files[i]->print_name;
-               int added = data->files[i]->added;
-               int deleted = data->files[i]->deleted;
+               uintmax_t added = data->files[i]->added;
+               uintmax_t deleted = data->files[i]->deleted;
                int name_len;
  
                /*
                if (data->files[i]->is_binary) {
                        show_name(options->file, prefix, name, len);
                        fprintf(options->file, "  Bin ");
-                       fprintf(options->file, "%s%d%s", del_c, deleted, reset);
+                       fprintf(options->file, "%s%"PRIuMAX"%s",
+                               del_c, deleted, reset);
                        fprintf(options->file, " -> ");
-                       fprintf(options->file, "%s%d%s", add_c, added, reset);
+                       fprintf(options->file, "%s%"PRIuMAX"%s",
+                               add_c, added, reset);
                        fprintf(options->file, " bytes");
                        fprintf(options->file, "\n");
                        continue;
                        del = scale_linear(del, width, max_change);
                }
                show_name(options->file, prefix, name, len);
-               fprintf(options->file, "%5d%s", added + deleted,
+               fprintf(options->file, "%5"PRIuMAX"%s", added + deleted,
                                added + deleted ? " " : "");
                show_graph(options->file, '+', add, add_c, reset);
                show_graph(options->file, '-', del, del_c, reset);
@@@ -1201,7 -1196,8 +1203,8 @@@ static void show_numstat(struct diffsta
                        fprintf(options->file, "-\t-\t");
                else
                        fprintf(options->file,
-                               "%d\t%d\t", file->added, file->deleted);
+                               "%"PRIuMAX"\t%"PRIuMAX"\t",
+                               file->added, file->deleted);
                if (options->line_termination) {
                        fill_print_name(file);
                        if (!file->is_renamed)
@@@ -1371,32 -1367,37 +1374,32 @@@ static void free_diffstat_info(struct d
  struct checkdiff_t {
        const char *filename;
        int lineno;
 +      int conflict_marker_size;
        struct diff_options *o;
        unsigned ws_rule;
        unsigned status;
  };
  
 -static int is_conflict_marker(const char *line, unsigned long len)
 +static int is_conflict_marker(const char *line, int marker_size, unsigned long len)
  {
        char firstchar;
        int cnt;
  
 -      if (len < 8)
 +      if (len < marker_size + 1)
                return 0;
        firstchar = line[0];
        switch (firstchar) {
 -      case '=': case '>': case '<':
 +      case '=': case '>': case '<': case '|':
                break;
        default:
                return 0;
        }
 -      for (cnt = 1; cnt < 7; cnt++)
 +      for (cnt = 1; cnt < marker_size; cnt++)
                if (line[cnt] != firstchar)
                        return 0;
 -      /* line[0] thru line[6] are same as firstchar */
 -      if (firstchar == '=') {
 -              /* divider between ours and theirs? */
 -              if (len != 8 || line[7] != '\n')
 -                      return 0;
 -      } else if (len < 8 || !isspace(line[7])) {
 -              /* not divider before ours nor after theirs */
 +      /* line[1] thru line[marker_size-1] are same as firstchar */
 +      if (len < marker_size + 1 || !isspace(line[marker_size]))
                return 0;
 -      }
        return 1;
  }
  
@@@ -1404,7 -1405,6 +1407,7 @@@ static void checkdiff_consume(void *pri
  {
        struct checkdiff_t *data = priv;
        int color_diff = DIFF_OPT_TST(data->o, COLOR_DIFF);
 +      int marker_size = data->conflict_marker_size;
        const char *ws = diff_get_color(color_diff, DIFF_WHITESPACE);
        const char *reset = diff_get_color(color_diff, DIFF_RESET);
        const char *set = diff_get_color(color_diff, DIFF_FILE_NEW);
        if (line[0] == '+') {
                unsigned bad;
                data->lineno++;
 -              if (is_conflict_marker(line + 1, len - 1)) {
 +              if (is_conflict_marker(line + 1, marker_size, len - 1)) {
                        data->status |= 1;
                        fprintf(data->o->file,
                                "%s:%d: leftover conflict marker\n",
@@@ -1604,7 -1604,6 +1607,7 @@@ static void builtin_diff(const char *na
        const char *reset = diff_get_color_opt(o, DIFF_RESET);
        const char *a_prefix, *b_prefix;
        const char *textconv_one = NULL, *textconv_two = NULL;
 +      struct strbuf header = STRBUF_INIT;
  
        if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
                        (!one->mode || S_ISGITLINK(one->mode)) &&
                const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
                const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
                show_submodule_summary(o->file, one ? one->path : two->path,
 -                              one->sha1, two->sha1,
 +                              one->sha1, two->sha1, two->dirty_submodule,
                                del, add, reset);
                return;
        }
        b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
        lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
        lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
 -      fprintf(o->file, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
 +      strbuf_addf(&header, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
        if (lbl[0][0] == '/') {
                /* /dev/null */
 -              fprintf(o->file, "%snew file mode %06o%s\n", set, two->mode, reset);
 +              strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset);
                if (xfrm_msg && xfrm_msg[0])
 -                      fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
 +                      strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
        }
        else if (lbl[1][0] == '/') {
 -              fprintf(o->file, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
 +              strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
                if (xfrm_msg && xfrm_msg[0])
 -                      fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
 +                      strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
        }
        else {
                if (one->mode != two->mode) {
 -                      fprintf(o->file, "%sold mode %06o%s\n", set, one->mode, reset);
 -                      fprintf(o->file, "%snew mode %06o%s\n", set, two->mode, reset);
 +                      strbuf_addf(&header, "%sold mode %06o%s\n", set, one->mode, reset);
 +                      strbuf_addf(&header, "%snew mode %06o%s\n", set, two->mode, reset);
                }
                if (xfrm_msg && xfrm_msg[0])
 -                      fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
 +                      strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
 +
                /*
                 * we do not run diff between different kind
                 * of objects.
                if (complete_rewrite &&
                    (textconv_one || !diff_filespec_is_binary(one)) &&
                    (textconv_two || !diff_filespec_is_binary(two))) {
 +                      fprintf(o->file, "%s", header.buf);
 +                      strbuf_reset(&header);
                        emit_rewrite_diff(name_a, name_b, one, two,
                                                textconv_one, textconv_two, o);
                        o->found_changes = 1;
                if (mf1.size == mf2.size &&
                    !memcmp(mf1.ptr, mf2.ptr, mf1.size))
                        goto free_ab_and_return;
 +              fprintf(o->file, "%s", header.buf);
 +              strbuf_reset(&header);
                if (DIFF_OPT_TST(o, BINARY))
                        emit_binary_diff(o->file, &mf1, &mf2);
                else
                struct emit_callback ecbdata;
                const struct userdiff_funcname *pe;
  
 +              if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS)) {
 +                      fprintf(o->file, "%s", header.buf);
 +                      strbuf_reset(&header);
 +              }
 +
                if (textconv_one) {
                        size_t size;
                        mf1.ptr = run_textconv(textconv_one, one, &size);
                if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
                        check_blank_at_eof(&mf1, &mf2, &ecbdata);
                ecbdata.file = o->file;
 +              ecbdata.header = header.len ? &header : NULL;
                xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
        }
  
   free_ab_and_return:
 +      strbuf_release(&header);
        diff_free_filespec_data(one);
        diff_free_filespec_data(two);
        free(a_one);
@@@ -1857,7 -1844,6 +1860,7 @@@ static void builtin_checkdiff(const cha
        data.lineno = 0;
        data.o = o;
        data.ws_rule = whitespace_rule(attr_path);
 +      data.conflict_marker_size = ll_merge_marker_size(attr_path);
  
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
@@@ -1995,7 -1981,7 +1998,7 @@@ static int reuse_worktree_file(const ch
         * If ce is marked as "assume unchanged", there is no
         * guarantee that work tree matches what we are looking for.
         */
 -      if (ce->ce_flags & CE_VALID)
 +      if ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce))
                return 0;
  
        /*
@@@ -2027,14 -2013,9 +2030,14 @@@ static int populate_from_stdin(struct d
  static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
  {
        int len;
 -      char *data = xmalloc(100);
 +      char *data = xmalloc(100), *dirty = "";
 +
 +      /* Are we looking at the work tree? */
 +      if (!s->sha1_valid && s->dirty_submodule)
 +              dirty = "-dirty";
 +
        len = snprintf(data, 100,
 -              "Subproject commit %s\n", sha1_to_hex(s->sha1));
 +                     "Subproject commit %s%s\n", sha1_to_hex(s->sha1), dirty);
        s->data = data;
        s->size = len;
        s->should_free = 1;
@@@ -2297,7 -2278,7 +2300,7 @@@ static void run_external_diff(const cha
        }
        *arg = NULL;
        fflush(NULL);
 -      retval = run_command_v_opt(spawn_arg, 0);
 +      retval = run_command_v_opt(spawn_arg, RUN_USING_SHELL);
        remove_tempfile();
        if (retval) {
                fprintf(stderr, "external diff died, stopping at %s.\n", name);
@@@ -2573,20 -2554,6 +2576,20 @@@ int diff_setup_done(struct diff_option
        if (count > 1)
                die("--name-only, --name-status, --check and -s are mutually exclusive");
  
 +      /*
 +       * Most of the time we can say "there are changes"
 +       * only by checking if there are changed paths, but
 +       * --ignore-whitespace* options force us to look
 +       * inside contents.
 +       */
 +
 +      if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) ||
 +          DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE) ||
 +          DIFF_XDL_TST(options, IGNORE_WHITESPACE_AT_EOL))
 +              DIFF_OPT_SET(options, DIFF_FROM_CONTENTS);
 +      else
 +              DIFF_OPT_CLR(options, DIFF_FROM_CONTENTS);
 +
        if (DIFF_OPT_TST(options, FIND_COPIES_HARDER))
                options->detect_rename = DIFF_DETECT_COPY;
  
         * to have found.  It does not make sense not to return with
         * exit code in such a case either.
         */
 -      if (DIFF_OPT_TST(options, QUIET)) {
 +      if (DIFF_OPT_TST(options, QUICK)) {
                options->output_format = DIFF_FORMAT_NO_OUTPUT;
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        }
@@@ -2838,7 -2805,7 +2841,7 @@@ int diff_opt_parse(struct diff_options 
        else if (!strcmp(arg, "--exit-code"))
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        else if (!strcmp(arg, "--quiet"))
 -              DIFF_OPT_SET(options, QUIET);
 +              DIFF_OPT_SET(options, QUICK);
        else if (!strcmp(arg, "--ext-diff"))
                DIFF_OPT_SET(options, ALLOW_EXTERNAL);
        else if (!strcmp(arg, "--no-ext-diff"))
                ;
        else if (!prefixcmp(arg, "--output=")) {
                options->file = fopen(arg + strlen("--output="), "w");
 +              if (!options->file)
 +                      die_errno("Could not open '%s'", arg + strlen("--output="));
                options->close_file = 1;
        } else
                return 0;
@@@ -3520,29 -3485,6 +3523,29 @@@ void diff_flush(struct diff_options *op
                separator++;
        }
  
 +      if (output_format & DIFF_FORMAT_NO_OUTPUT &&
 +          DIFF_OPT_TST(options, EXIT_WITH_STATUS) &&
 +          DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) {
 +              /*
 +               * run diff_flush_patch for the exit status. setting
 +               * options->file to /dev/null should be safe, becaue we
 +               * aren't supposed to produce any output anyway.
 +               */
 +              if (options->close_file)
 +                      fclose(options->file);
 +              options->file = fopen("/dev/null", "w");
 +              if (!options->file)
 +                      die_errno("Could not open /dev/null");
 +              options->close_file = 1;
 +              for (i = 0; i < q->nr; i++) {
 +                      struct diff_filepair *p = q->queue[i];
 +                      if (check_pair_status(p))
 +                              diff_flush_patch(p, options);
 +                      if (options->found_changes)
 +                              break;
 +              }
 +      }
 +
        if (output_format & DIFF_FORMAT_PATCH) {
                if (separator) {
                        putc(options->line_termination, options->file);
@@@ -3570,18 -3512,6 +3573,18 @@@ free_queue
        q->nr = q->alloc = 0;
        if (options->close_file)
                fclose(options->file);
 +
 +      /*
 +       * Report the content-level differences with HAS_CHANGES;
 +       * diff_addremove/diff_change does not set the bit when
 +       * DIFF_FROM_CONTENTS is in effect (e.g. with -w).
 +       */
 +      if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) {
 +              if (options->found_changes)
 +                      DIFF_OPT_SET(options, HAS_CHANGES);
 +              else
 +                      DIFF_OPT_CLR(options, HAS_CHANGES);
 +      }
  }
  
  static void diffcore_apply_filter(const char *filter)
@@@ -3665,7 -3595,7 +3668,7 @@@ static void diffcore_skip_stat_unmatch(
                struct diff_filepair *p = q->queue[i];
  
                /*
 -               * 1. Entries that come from stat info dirtyness
 +               * 1. Entries that come from stat info dirtiness
                 *    always have both sides (iow, not create/delete),
                 *    one side of the object name is unknown, with
                 *    the same mode and size.  Keep the ones that
        *q = outq;
  }
  
 +static int diffnamecmp(const void *a_, const void *b_)
 +{
 +      const struct diff_filepair *a = *((const struct diff_filepair **)a_);
 +      const struct diff_filepair *b = *((const struct diff_filepair **)b_);
 +      const char *name_a, *name_b;
 +
 +      name_a = a->one ? a->one->path : a->two->path;
 +      name_b = b->one ? b->one->path : b->two->path;
 +      return strcmp(name_a, name_b);
 +}
 +
 +void diffcore_fix_diff_index(struct diff_options *options)
 +{
 +      struct diff_queue_struct *q = &diff_queued_diff;
 +      qsort(q->queue, q->nr, sizeof(q->queue[0]), diffnamecmp);
 +}
 +
  void diffcore_std(struct diff_options *options)
  {
        if (options->skip_stat_unmatch)
        diff_resolve_rename_copy();
        diffcore_apply_filter(options->filter);
  
 -      if (diff_queued_diff.nr)
 +      if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
                DIFF_OPT_SET(options, HAS_CHANGES);
        else
                DIFF_OPT_CLR(options, HAS_CHANGES);
@@@ -3759,7 -3672,7 +3762,7 @@@ int diff_result_code(struct diff_option
  void diff_addremove(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
 -                  const char *concatpath)
 +                  const char *concatpath, unsigned dirty_submodule)
  {
        struct diff_filespec *one, *two;
  
  
        if (addremove != '+')
                fill_filespec(one, sha1, mode);
 -      if (addremove != '-')
 +      if (addremove != '-') {
                fill_filespec(two, sha1, mode);
 +              two->dirty_submodule = dirty_submodule;
 +      }
  
        diff_queue(&diff_queued_diff, one, two);
 -      DIFF_OPT_SET(options, HAS_CHANGES);
 +      if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
 +              DIFF_OPT_SET(options, HAS_CHANGES);
  }
  
  void diff_change(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
                 const unsigned char *old_sha1,
                 const unsigned char *new_sha1,
 -               const char *concatpath)
 +               const char *concatpath,
 +               unsigned old_dirty_submodule, unsigned new_dirty_submodule)
  {
        struct diff_filespec *one, *two;
  
                const unsigned char *tmp_c;
                tmp = old_mode; old_mode = new_mode; new_mode = tmp;
                tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
 +              tmp = old_dirty_submodule; old_dirty_submodule = new_dirty_submodule;
 +                      new_dirty_submodule = tmp;
        }
  
        if (options->prefix &&
        two = alloc_filespec(concatpath);
        fill_filespec(one, old_sha1, old_mode);
        fill_filespec(two, new_sha1, new_mode);
 +      one->dirty_submodule = old_dirty_submodule;
 +      two->dirty_submodule = new_dirty_submodule;
  
        diff_queue(&diff_queued_diff, one, two);
 -      DIFF_OPT_SET(options, HAS_CHANGES);
 +      if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
 +              DIFF_OPT_SET(options, HAS_CHANGES);
  }
  
  void diff_unmerge(struct diff_options *options,
@@@ -3863,7 -3767,6 +3866,7 @@@ static char *run_textconv(const char *p
        const char **arg = argv;
        struct child_process child;
        struct strbuf buf = STRBUF_INIT;
 +      int err = 0;
  
        temp = prepare_temp_file(spec->path, spec);
        *arg++ = pgm;
        *arg = NULL;
  
        memset(&child, 0, sizeof(child));
 +      child.use_shell = 1;
        child.argv = argv;
        child.out = -1;
 -      if (start_command(&child) != 0 ||
 -          strbuf_read(&buf, child.out, 0) < 0 ||
 -          finish_command(&child) != 0) {
 +      if (start_command(&child)) {
 +              remove_tempfile();
 +              return NULL;
 +      }
 +
 +      if (strbuf_read(&buf, child.out, 0) < 0)
 +              err = error("error reading from textconv command '%s'", pgm);
 +      close(child.out);
 +
 +      if (finish_command(&child) || err) {
                strbuf_release(&buf);
                remove_tempfile();
 -              error("error running textconv command '%s'", pgm);
                return NULL;
        }
        remove_tempfile();