Code

revision traversal: show full history with merge simplification
[git.git] / diff.c
diff --git a/diff.c b/diff.c
index 20135cbe7c8209835dca8d52d1d48e7aa871c1b3..cbf25473c594abfd1fc13473108dc9c15e2f1d15 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -19,7 +19,7 @@
 #endif
 
 static int diff_detect_rename_default;
-static int diff_rename_limit_default = 100;
+static int diff_rename_limit_default = 200;
 int diff_use_color_default = -1;
 static const char *external_diff_cmd_cfg;
 int diff_auto_refresh_index = 1;
@@ -129,7 +129,7 @@ static int parse_funcname_pattern(const char *var, const char *ep, const char *v
  * never be affected by the setting of diff.renames
  * the user happens to have in the configuration file.
  */
-int git_diff_ui_config(const char *var, const char *value)
+int git_diff_ui_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "diff.renamelimit")) {
                diff_rename_limit_default = git_config_int(var, value);
@@ -153,12 +153,8 @@ int git_diff_ui_config(const char *var, const char *value)
                diff_auto_refresh_index = git_config_bool(var, value);
                return 0;
        }
-       if (!strcmp(var, "diff.external")) {
-               if (!value)
-                       return config_error_nonbool(var);
-               external_diff_cmd_cfg = xstrdup(value);
-               return 0;
-       }
+       if (!strcmp(var, "diff.external"))
+               return git_config_string(&external_diff_cmd_cfg, var, value);
        if (!prefixcmp(var, "diff.")) {
                const char *ep = strrchr(var, '.');
 
@@ -166,10 +162,10 @@ int git_diff_ui_config(const char *var, const char *value)
                        return parse_lldiff_command(var, ep, value);
        }
 
-       return git_diff_basic_config(var, value);
+       return git_diff_basic_config(var, value, cb);
 }
 
-int git_diff_basic_config(const char *var, const char *value)
+int git_diff_basic_config(const char *var, const char *value, void *cb)
 {
        if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
                int slot = parse_diff_color_slot(var, 11);
@@ -190,7 +186,7 @@ int git_diff_basic_config(const char *var, const char *value)
                }
        }
 
-       return git_color_default_config(var, value);
+       return git_color_default_config(var, value, cb);
 }
 
 static char *quote_two(const char *one, const char *two)
@@ -535,9 +531,9 @@ static void emit_add_line(const char *reset, struct emit_callback *ecbdata, cons
        else {
                /* Emit just the prefix, then the rest. */
                emit_line(ecbdata->file, set, reset, line, ecbdata->nparents);
-               (void)check_and_emit_line(line + ecbdata->nparents,
-                   len - ecbdata->nparents, ecbdata->ws_rule,
-                   ecbdata->file, set, reset, ws);
+               ws_check_emit(line + ecbdata->nparents,
+                             len - ecbdata->nparents, ecbdata->ws_rule,
+                             ecbdata->file, set, reset, ws);
        }
 }
 
@@ -830,12 +826,12 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
        /* Sanity: give at least 5 columns to the graph,
         * but leave at least 10 columns for the name.
         */
-       if (width < name_width + 15) {
-               if (name_width <= 25)
-                       width = name_width + 15;
-               else
-                       name_width = width - 15;
-       }
+       if (width < 25)
+               width = 25;
+       if (name_width < 10)
+               name_width = 10;
+       else if (width < name_width + 15)
+               name_width = width - 15;
 
        /* Find the longest filename and max number of changes */
        reset = diff_get_color_opt(options, DIFF_RESET);
@@ -928,7 +924,8 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
                        total = add + del;
                }
                show_name(options->file, prefix, name, len, reset, set);
-               fprintf(options->file, "%5d ", added + deleted);
+               fprintf(options->file, "%5d%s", added + deleted,
+                               added + deleted ? " " : "");
                show_graph(options->file, '+', add, add_c, reset);
                show_graph(options->file, '-', del, del_c, reset);
                fprintf(options->file, "\n");
@@ -997,18 +994,23 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options)
        }
 }
 
-struct diffstat_dir {
-       struct diffstat_file **files;
-       int nr, percent, cumulative;
+struct dirstat_file {
+       const char *name;
+       unsigned long changed;
 };
 
-static long gather_dirstat(FILE *file, struct diffstat_dir *dir, unsigned long changed, const char *base, int baselen)
+struct dirstat_dir {
+       struct dirstat_file *files;
+       int alloc, nr, percent, cumulative;
+};
+
+static long gather_dirstat(FILE *file, struct dirstat_dir *dir, unsigned long changed, const char *base, int baselen)
 {
        unsigned long this_dir = 0;
        unsigned int sources = 0;
 
        while (dir->nr) {
-               struct diffstat_file *f = *dir->files;
+               struct dirstat_file *f = dir->files;
                int namelen = strlen(f->name);
                unsigned long this;
                char *slash;
@@ -1023,10 +1025,7 @@ static long gather_dirstat(FILE *file, struct diffstat_dir *dir, unsigned long c
                        this = gather_dirstat(file, dir, changed, f->name, newbaselen);
                        sources++;
                } else {
-                       if (f->is_unmerged || f->is_binary)
-                               this = 0;
-                       else
-                               this = f->added + f->deleted;
+                       this = f->changed;
                        dir->files++;
                        dir->nr--;
                        sources += 2;
@@ -1054,19 +1053,58 @@ static long gather_dirstat(FILE *file, struct diffstat_dir *dir, unsigned long c
        return this_dir;
 }
 
-static void show_dirstat(struct diffstat_t *data, struct diff_options *options)
+static void show_dirstat(struct diff_options *options)
 {
        int i;
        unsigned long changed;
-       struct diffstat_dir dir;
+       struct dirstat_dir dir;
+       struct diff_queue_struct *q = &diff_queued_diff;
+
+       dir.files = NULL;
+       dir.alloc = 0;
+       dir.nr = 0;
+       dir.percent = options->dirstat_percent;
+       dir.cumulative = options->output_format & DIFF_FORMAT_CUMULATIVE;
 
-       /* Calculate total changes */
        changed = 0;
-       for (i = 0; i < data->nr; i++) {
-               if (data->files[i]->is_binary || data->files[i]->is_unmerged)
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               const char *name;
+               unsigned long copied, added, damage;
+
+               name = p->one->path ? p->one->path : p->two->path;
+
+               if (DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) {
+                       diff_populate_filespec(p->one, 0);
+                       diff_populate_filespec(p->two, 0);
+                       diffcore_count_changes(p->one, p->two, NULL, NULL, 0,
+                                              &copied, &added);
+                       diff_free_filespec_data(p->one);
+                       diff_free_filespec_data(p->two);
+               } else if (DIFF_FILE_VALID(p->one)) {
+                       diff_populate_filespec(p->one, 1);
+                       copied = added = 0;
+                       diff_free_filespec_data(p->one);
+               } else if (DIFF_FILE_VALID(p->two)) {
+                       diff_populate_filespec(p->two, 1);
+                       copied = 0;
+                       added = p->two->size;
+                       diff_free_filespec_data(p->two);
+               } else
                        continue;
-               changed += data->files[i]->added;
-               changed += data->files[i]->deleted;
+
+               /*
+                * Original minus copied is the removed material,
+                * added is the new material.  They are both damages
+                * made to the preimage.
+                */
+               damage = (p->one->size - copied) + added;
+
+               ALLOC_GROW(dir.files, dir.nr + 1, dir.alloc);
+               dir.files[dir.nr].name = name;
+               dir.files[dir.nr].changed = damage;
+               changed += damage;
+               dir.nr++;
        }
 
        /* This can happen even with many files, if everything was renames */
@@ -1074,10 +1112,6 @@ static void show_dirstat(struct diffstat_t *data, struct diff_options *options)
                return;
 
        /* Show all directories with more than x% of the changes */
-       dir.files = data->files;
-       dir.nr = data->nr;
-       dir.percent = options->dirstat_percent;
-       dir.cumulative = options->output_format & DIFF_FORMAT_CUMULATIVE;
        gather_dirstat(options->file, &dir, changed, "", 0);
 }
 
@@ -1098,40 +1132,85 @@ static void free_diffstat_info(struct diffstat_t *diffstat)
 struct checkdiff_t {
        struct xdiff_emit_state xm;
        const char *filename;
-       int lineno, color_diff;
+       int lineno;
+       struct diff_options *o;
        unsigned ws_rule;
        unsigned status;
-       FILE *file;
+       int trailing_blanks_start;
 };
 
+static int is_conflict_marker(const char *line, unsigned long len)
+{
+       char firstchar;
+       int cnt;
+
+       if (len < 8)
+               return 0;
+       firstchar = line[0];
+       switch (firstchar) {
+       case '=': case '>': case '<':
+               break;
+       default:
+               return 0;
+       }
+       for (cnt = 1; cnt < 7; 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 */
+               return 0;
+       }
+       return 1;
+}
+
 static void checkdiff_consume(void *priv, char *line, unsigned long len)
 {
        struct checkdiff_t *data = priv;
-       const char *ws = diff_get_color(data->color_diff, DIFF_WHITESPACE);
-       const char *reset = diff_get_color(data->color_diff, DIFF_RESET);
-       const char *set = diff_get_color(data->color_diff, DIFF_FILE_NEW);
+       int color_diff = DIFF_OPT_TST(data->o, COLOR_DIFF);
+       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);
        char *err;
 
        if (line[0] == '+') {
+               unsigned bad;
                data->lineno++;
-               data->status = check_and_emit_line(line + 1, len - 1,
-                   data->ws_rule, NULL, NULL, NULL, NULL);
-               if (!data->status)
+               if (!ws_blank_line(line + 1, len - 1, data->ws_rule))
+                       data->trailing_blanks_start = 0;
+               else if (!data->trailing_blanks_start)
+                       data->trailing_blanks_start = data->lineno;
+               if (is_conflict_marker(line + 1, len - 1)) {
+                       data->status |= 1;
+                       fprintf(data->o->file,
+                               "%s:%d: leftover conflict marker\n",
+                               data->filename, data->lineno);
+               }
+               bad = ws_check(line + 1, len - 1, data->ws_rule);
+               if (!bad)
                        return;
-               err = whitespace_error_string(data->status);
-               fprintf(data->file, "%s:%d: %s.\n", data->filename, data->lineno, err);
+               data->status |= bad;
+               err = whitespace_error_string(bad);
+               fprintf(data->o->file, "%s:%d: %s.\n",
+                       data->filename, data->lineno, err);
                free(err);
-               emit_line(data->file, set, reset, line, 1);
-               (void)check_and_emit_line(line + 1, len - 1, data->ws_rule,
-                   data->file, set, reset, ws);
-       } else if (line[0] == ' ')
+               emit_line(data->o->file, set, reset, line, 1);
+               ws_check_emit(line + 1, len - 1, data->ws_rule,
+                             data->o->file, set, reset, ws);
+       } else if (line[0] == ' ') {
                data->lineno++;
-       else if (line[0] == '@') {
+               data->trailing_blanks_start = 0;
+       } else if (line[0] == '@') {
                char *plus = strchr(line, '+');
                if (plus)
                        data->lineno = strtol(plus, NULL, 10) - 1;
                else
                        die("invalid diff");
+               data->trailing_blanks_start = 0;
        }
 }
 
@@ -1504,8 +1583,9 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
 
 static void builtin_checkdiff(const char *name_a, const char *name_b,
                              const char *attr_path,
-                            struct diff_filespec *one,
-                            struct diff_filespec *two, struct diff_options *o)
+                             struct diff_filespec *one,
+                             struct diff_filespec *two,
+                             struct diff_options *o)
 {
        mmfile_t mf1, mf2;
        struct checkdiff_t data;
@@ -1517,13 +1597,18 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
        data.xm.consume = checkdiff_consume;
        data.filename = name_b ? name_b : name_a;
        data.lineno = 0;
-       data.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
+       data.o = o;
        data.ws_rule = whitespace_rule(attr_path);
-       data.file = o->file;
 
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
 
+       /*
+        * All the other codepaths check both sides, but not checking
+        * the "old" side here is deliberate.  We are checking the newly
+        * introduced changes, and as long as the "new" side is text, we
+        * can and should check what it introduces.
+        */
        if (diff_filespec_is_binary(two))
                goto free_and_return;
        else {
@@ -1537,6 +1622,12 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
                ecb.outf = xdiff_outf;
                ecb.priv = &data;
                xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+
+               if (data.trailing_blanks_start) {
+                       fprintf(o->file, "%s:%d: ends with blank lines.\n",
+                               data.filename, data.trailing_blanks_start);
+                       data.status = 1; /* report errors */
+               }
        }
  free_and_return:
        diff_free_filespec_data(one);
@@ -2189,7 +2280,6 @@ void diff_setup(struct diff_options *options)
        options->rename_limit = -1;
        options->dirstat_percent = 3;
        options->context = 3;
-       options->msg_sep = "";
 
        options->change = diff_change;
        options->add_remove = diff_addremove;
@@ -2466,6 +2556,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                DIFF_OPT_SET(options, ALLOW_EXTERNAL);
        else if (!strcmp(arg, "--no-ext-diff"))
                DIFF_OPT_CLR(options, ALLOW_EXTERNAL);
+       else if (!strcmp(arg, "--ignore-submodules"))
+               DIFF_OPT_SET(options, IGNORE_SUBMODULES);
 
        /* misc options */
        else if (!strcmp(arg, "-z"))
@@ -3101,7 +3193,7 @@ void diff_flush(struct diff_options *options)
                separator++;
        }
 
-       if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIRSTAT)) {
+       if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) {
                struct diffstat_t diffstat;
 
                memset(&diffstat, 0, sizeof(struct diffstat_t));
@@ -3111,8 +3203,6 @@ void diff_flush(struct diff_options *options)
                        if (check_pair_status(p))
                                diff_flush_stat(p, options, &diffstat);
                }
-               if (output_format & DIFF_FORMAT_DIRSTAT)
-                       show_dirstat(&diffstat, options);
                if (output_format & DIFF_FORMAT_NUMSTAT)
                        show_numstat(&diffstat, options);
                if (output_format & DIFF_FORMAT_DIFFSTAT)
@@ -3122,6 +3212,8 @@ void diff_flush(struct diff_options *options)
                free_diffstat_info(&diffstat);
                separator++;
        }
+       if (output_format & DIFF_FORMAT_DIRSTAT)
+               show_dirstat(options);
 
        if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) {
                for (i = 0; i < q->nr; i++)
@@ -3131,11 +3223,10 @@ void diff_flush(struct diff_options *options)
 
        if (output_format & DIFF_FORMAT_PATCH) {
                if (separator) {
+                       putc(options->line_termination, options->file);
                        if (options->stat_sep) {
                                /* attach patch instead of inline */
                                fputs(options->stat_sep, options->file);
-                       } else {
-                               putc(options->line_termination, options->file);
                        }
                }
 
@@ -3320,11 +3411,13 @@ int diff_result_code(struct diff_options *opt, int status)
 void diff_addremove(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
-                   const char *base, const char *path)
+                   const char *concatpath)
 {
-       char concatpath[PATH_MAX];
        struct diff_filespec *one, *two;
 
+       if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(mode))
+               return;
+
        /* This may look odd, but it is a preparation for
         * feeding "there are unchanged files which should
         * not produce diffs, but when you are doing copy
@@ -3341,9 +3434,6 @@ void diff_addremove(struct diff_options *options,
                addremove = (addremove == '+' ? '-' :
                             addremove == '-' ? '+' : addremove);
 
-       if (!path) path = "";
-       sprintf(concatpath, "%s%s", base, path);
-
        if (options->prefix &&
            strncmp(concatpath, options->prefix, options->prefix_length))
                return;
@@ -3364,19 +3454,20 @@ 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 *base, const char *path)
+                const char *concatpath)
 {
-       char concatpath[PATH_MAX];
        struct diff_filespec *one, *two;
 
+       if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(old_mode)
+                       && S_ISGITLINK(new_mode))
+               return;
+
        if (DIFF_OPT_TST(options, REVERSE_DIFF)) {
                unsigned tmp;
                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;
        }
-       if (!path) path = "";
-       sprintf(concatpath, "%s%s", base, path);
 
        if (options->prefix &&
            strncmp(concatpath, options->prefix, options->prefix_length))