From d9c66f0b5bfdf3fc2898b7baad1bb9a72bfd7bf7 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 13 Aug 2018 04:33:04 -0700 Subject: range-diff: first rudimentary implementation At this stage, `git range-diff` can determine corresponding commits of two related commit ranges. This makes use of the recently introduced implementation of the linear assignment algorithm. The core of this patch is a straight port of the ideas of tbdiff, the apparently dormant project at https://github.com/trast/tbdiff. The output does not at all match `tbdiff`'s output yet, as this patch really concentrates on getting the patch matching part right. Note: due to differences in the diff algorithm (`tbdiff` uses the Python module `difflib`, Git uses its xdiff fork), the cost matrix calculated by `range-diff` is different (but very similar) to the one calculated by `tbdiff`. Therefore, it is possible that they find different matching commits in corner cases (e.g. when a patch was split into two patches of roughly equal length). Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- range-diff.c | 311 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 range-diff.c (limited to 'range-diff.c') diff --git a/range-diff.c b/range-diff.c new file mode 100644 index 0000000000..15d418afa6 --- /dev/null +++ b/range-diff.c @@ -0,0 +1,311 @@ +#include "cache.h" +#include "range-diff.h" +#include "string-list.h" +#include "run-command.h" +#include "argv-array.h" +#include "hashmap.h" +#include "xdiff-interface.h" +#include "linear-assignment.h" + +struct patch_util { + /* For the search for an exact match */ + struct hashmap_entry e; + const char *diff, *patch; + + int i; + int diffsize; + size_t diff_offset; + /* the index of the matching item in the other branch, or -1 */ + int matching; + struct object_id oid; +}; + +/* + * Reads the patches into a string list, with the `util` field being populated + * as struct object_id (will need to be free()d). + */ +static int read_patches(const char *range, struct string_list *list) +{ + struct child_process cp = CHILD_PROCESS_INIT; + FILE *in; + struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT; + struct patch_util *util = NULL; + int in_header = 1; + + argv_array_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges", + "--reverse", "--date-order", "--decorate=no", + "--no-abbrev-commit", range, + NULL); + cp.out = -1; + cp.no_stdin = 1; + cp.git_cmd = 1; + + if (start_command(&cp)) + return error_errno(_("could not start `log`")); + in = fdopen(cp.out, "r"); + if (!in) { + error_errno(_("could not read `log` output")); + finish_command(&cp); + return -1; + } + + while (strbuf_getline(&line, in) != EOF) { + const char *p; + + if (skip_prefix(line.buf, "commit ", &p)) { + if (util) { + string_list_append(list, buf.buf)->util = util; + strbuf_reset(&buf); + } + util = xcalloc(sizeof(*util), 1); + if (get_oid(p, &util->oid)) { + error(_("could not parse commit '%s'"), p); + free(util); + string_list_clear(list, 1); + strbuf_release(&buf); + strbuf_release(&line); + fclose(in); + finish_command(&cp); + return -1; + } + util->matching = -1; + in_header = 1; + continue; + } + + if (starts_with(line.buf, "diff --git")) { + in_header = 0; + strbuf_addch(&buf, '\n'); + if (!util->diff_offset) + util->diff_offset = buf.len; + strbuf_addbuf(&buf, &line); + } else if (in_header) { + if (starts_with(line.buf, "Author: ")) { + strbuf_addbuf(&buf, &line); + strbuf_addstr(&buf, "\n\n"); + } else if (starts_with(line.buf, " ")) { + strbuf_addbuf(&buf, &line); + strbuf_addch(&buf, '\n'); + } + continue; + } else if (starts_with(line.buf, "@@ ")) + strbuf_addstr(&buf, "@@"); + else if (!line.buf[0] || starts_with(line.buf, "index ")) + /* + * A completely blank (not ' \n', which is context) + * line is not valid in a diff. We skip it + * silently, because this neatly handles the blank + * separator line between commits in git-log + * output. + * + * We also want to ignore the diff's `index` lines + * because they contain exact blob hashes in which + * we are not interested. + */ + continue; + else + strbuf_addbuf(&buf, &line); + + strbuf_addch(&buf, '\n'); + util->diffsize++; + } + fclose(in); + strbuf_release(&line); + + if (util) + string_list_append(list, buf.buf)->util = util; + strbuf_release(&buf); + + if (finish_command(&cp)) + return -1; + + return 0; +} + +static int patch_util_cmp(const void *dummy, const struct patch_util *a, + const struct patch_util *b, const char *keydata) +{ + return strcmp(a->diff, keydata ? keydata : b->diff); +} + +static void find_exact_matches(struct string_list *a, struct string_list *b) +{ + struct hashmap map; + int i; + + hashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0); + + /* First, add the patches of a to a hash map */ + for (i = 0; i < a->nr; i++) { + struct patch_util *util = a->items[i].util; + + util->i = i; + util->patch = a->items[i].string; + util->diff = util->patch + util->diff_offset; + hashmap_entry_init(util, strhash(util->diff)); + hashmap_add(&map, util); + } + + /* Now try to find exact matches in b */ + for (i = 0; i < b->nr; i++) { + struct patch_util *util = b->items[i].util, *other; + + util->i = i; + util->patch = b->items[i].string; + util->diff = util->patch + util->diff_offset; + hashmap_entry_init(util, strhash(util->diff)); + other = hashmap_remove(&map, util, NULL); + if (other) { + if (other->matching >= 0) + BUG("already assigned!"); + + other->matching = i; + util->matching = other->i; + } + } + + hashmap_free(&map, 0); +} + +static void diffsize_consume(void *data, char *line, unsigned long len) +{ + (*(int *)data)++; +} + +static int diffsize(const char *a, const char *b) +{ + xpparam_t pp = { 0 }; + xdemitconf_t cfg = { 0 }; + mmfile_t mf1, mf2; + int count = 0; + + mf1.ptr = (char *)a; + mf1.size = strlen(a); + mf2.ptr = (char *)b; + mf2.size = strlen(b); + + cfg.ctxlen = 3; + if (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg)) + return count; + + error(_("failed to generate diff")); + return COST_MAX; +} + +static void get_correspondences(struct string_list *a, struct string_list *b, + int creation_factor) +{ + int n = a->nr + b->nr; + int *cost, c, *a2b, *b2a; + int i, j; + + ALLOC_ARRAY(cost, st_mult(n, n)); + ALLOC_ARRAY(a2b, n); + ALLOC_ARRAY(b2a, n); + + for (i = 0; i < a->nr; i++) { + struct patch_util *a_util = a->items[i].util; + + for (j = 0; j < b->nr; j++) { + struct patch_util *b_util = b->items[j].util; + + if (a_util->matching == j) + c = 0; + else if (a_util->matching < 0 && b_util->matching < 0) + c = diffsize(a_util->diff, b_util->diff); + else + c = COST_MAX; + cost[i + n * j] = c; + } + + c = a_util->matching < 0 ? + a_util->diffsize * creation_factor / 100 : COST_MAX; + for (j = b->nr; j < n; j++) + cost[i + n * j] = c; + } + + for (j = 0; j < b->nr; j++) { + struct patch_util *util = b->items[j].util; + + c = util->matching < 0 ? + util->diffsize * creation_factor / 100 : COST_MAX; + for (i = a->nr; i < n; i++) + cost[i + n * j] = c; + } + + for (i = a->nr; i < n; i++) + for (j = b->nr; j < n; j++) + cost[i + n * j] = 0; + + compute_assignment(n, n, cost, a2b, b2a); + + for (i = 0; i < a->nr; i++) + if (a2b[i] >= 0 && a2b[i] < b->nr) { + struct patch_util *a_util = a->items[i].util; + struct patch_util *b_util = b->items[a2b[i]].util; + + a_util->matching = a2b[i]; + b_util->matching = i; + } + + free(cost); + free(a2b); + free(b2a); +} + +static const char *short_oid(struct patch_util *util) +{ + return find_unique_abbrev(&util->oid, DEFAULT_ABBREV); +} + +static void output(struct string_list *a, struct string_list *b) +{ + int i; + + for (i = 0; i < b->nr; i++) { + struct patch_util *util = b->items[i].util, *prev; + + if (util->matching < 0) + printf("-: -------- > %d: %s\n", + i + 1, short_oid(util)); + else { + prev = a->items[util->matching].util; + printf("%d: %s ! %d: %s\n", + util->matching + 1, short_oid(prev), + i + 1, short_oid(util)); + } + } + + for (i = 0; i < a->nr; i++) { + struct patch_util *util = a->items[i].util; + + if (util->matching < 0) + printf("%d: %s < -: --------\n", + i + 1, short_oid(util)); + } +} + +int show_range_diff(const char *range1, const char *range2, + int creation_factor) +{ + int res = 0; + + struct string_list branch1 = STRING_LIST_INIT_DUP; + struct string_list branch2 = STRING_LIST_INIT_DUP; + + if (read_patches(range1, &branch1)) + res = error(_("could not parse log for '%s'"), range1); + if (!res && read_patches(range2, &branch2)) + res = error(_("could not parse log for '%s'"), range2); + + if (!res) { + find_exact_matches(&branch1, &branch2); + get_correspondences(&branch1, &branch2, creation_factor); + output(&branch1, &branch2); + } + + string_list_clear(&branch1, 1); + string_list_clear(&branch2, 1); + + return res; +} -- cgit v1.2.3 From 9dc46e0268028059aecd140254bfe30277d04767 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 13 Aug 2018 04:33:05 -0700 Subject: range-diff: improve the order of the shown commits This patch lets `git range-diff` use the same order as tbdiff. The idea is simple: for left-to-right readers, it is natural to assume that the `git range-diff` is performed between an older vs a newer version of the branch. As such, the user is probably more interested in the question "where did this come from?" rather than "where did that one go?". To that end, we list the commits in the order of the second commit range ("the newer version"), inserting the unmatched commits of the first commit range as soon as all their predecessors have been shown. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- range-diff.c | 59 ++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 19 deletions(-) (limited to 'range-diff.c') diff --git a/range-diff.c b/range-diff.c index 15d418afa6..2d94200d30 100644 --- a/range-diff.c +++ b/range-diff.c @@ -12,7 +12,7 @@ struct patch_util { struct hashmap_entry e; const char *diff, *patch; - int i; + int i, shown; int diffsize; size_t diff_offset; /* the index of the matching item in the other branch, or -1 */ @@ -260,28 +260,49 @@ static const char *short_oid(struct patch_util *util) static void output(struct string_list *a, struct string_list *b) { - int i; - - for (i = 0; i < b->nr; i++) { - struct patch_util *util = b->items[i].util, *prev; + int i = 0, j = 0; + + /* + * We assume the user is really more interested in the second argument + * ("newer" version). To that end, we print the output in the order of + * the RHS (the `b` parameter). To put the LHS (the `a` parameter) + * commits that are no longer in the RHS into a good place, we place + * them once we have shown all of their predecessors in the LHS. + */ + + while (i < a->nr || j < b->nr) { + struct patch_util *a_util, *b_util; + a_util = i < a->nr ? a->items[i].util : NULL; + b_util = j < b->nr ? b->items[j].util : NULL; + + /* Skip all the already-shown commits from the LHS. */ + while (i < a->nr && a_util->shown) + a_util = ++i < a->nr ? a->items[i].util : NULL; + + /* Show unmatched LHS commit whose predecessors were shown. */ + if (i < a->nr && a_util->matching < 0) { + printf("%d: %s < -: --------\n", + i + 1, short_oid(a_util)); + i++; + continue; + } - if (util->matching < 0) + /* Show unmatched RHS commits. */ + while (j < b->nr && b_util->matching < 0) { printf("-: -------- > %d: %s\n", - i + 1, short_oid(util)); - else { - prev = a->items[util->matching].util; - printf("%d: %s ! %d: %s\n", - util->matching + 1, short_oid(prev), - i + 1, short_oid(util)); + j + 1, short_oid(b_util)); + b_util = ++j < b->nr ? b->items[j].util : NULL; } - } - - for (i = 0; i < a->nr; i++) { - struct patch_util *util = a->items[i].util; - if (util->matching < 0) - printf("%d: %s < -: --------\n", - i + 1, short_oid(util)); + /* Show matching LHS/RHS pair. */ + if (j < b->nr) { + a_util = a->items[b_util->matching].util; + printf("%d: %s ! %d: %s\n", + b_util->matching + 1, short_oid(a_util), + j + 1, short_oid(b_util)); + a_util->shown = 1; + j++; + } } } -- cgit v1.2.3 From c8c5e43ac3f9a5b785faf16f10bb8e2c493606a4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 13 Aug 2018 04:33:07 -0700 Subject: range-diff: also show the diff between patches Just like tbdiff, we now show the diff between matching patches. This is a "diff of two diffs", so it can be a bit daunting to read for the beginner. An alternative would be to display an interdiff, i.e. the hypothetical diff which is the result of first reverting the old diff and then applying the new diff. Especially when rebasing frequently, an interdiff is often not feasible, though: if the old diff cannot be applied in reverse (due to a moving upstream), an interdiff can simply not be inferred. This commit brings `range-diff` closer to feature parity with regard to tbdiff. To make `git range-diff` respect e.g. color.diff.* settings, we have to adjust git_branch_config() accordingly. Note: while we now parse diff options such as --color, the effect is not yet the same as in tbdiff, where also the commit pairs would be colored. This is left for a later commit. Note also: while tbdiff accepts the `--no-patches` option to suppress these diffs between patches, we prefer the `-s` (or `--no-patch`) option that is automatically supported via our use of diff_opt_parse(). And finally note: to support diff options, we have to call `parse_options()` such that it keeps unknown options, and then loop over those and let `diff_opt_parse()` handle them. After that loop, we have to call `parse_options()` again, to make sure that no unknown options are left. Helped-by: Thomas Gummerer Helped-by: Eric Sunshine Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/range-diff.c | 31 +++++++++++++++++++++++++++++-- range-diff.c | 34 +++++++++++++++++++++++++++++++--- range-diff.h | 4 +++- 3 files changed, 63 insertions(+), 6 deletions(-) (limited to 'range-diff.c') diff --git a/builtin/range-diff.c b/builtin/range-diff.c index 94c1f362cc..3b06ed9449 100644 --- a/builtin/range-diff.c +++ b/builtin/range-diff.c @@ -2,6 +2,7 @@ #include "builtin.h" #include "parse-options.h" #include "range-diff.h" +#include "config.h" static const char * const builtin_range_diff_usage[] = { N_("git range-diff [] .. .."), @@ -13,15 +14,40 @@ NULL int cmd_range_diff(int argc, const char **argv, const char *prefix) { int creation_factor = 60; + struct diff_options diffopt = { NULL }; struct option options[] = { OPT_INTEGER(0, "creation-factor", &creation_factor, N_("Percentage by which creation is weighted")), OPT_END() }; - int res = 0; + int i, j, res = 0; struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT; + git_config(git_diff_ui_config, NULL); + + diff_setup(&diffopt); + diffopt.output_format = DIFF_FORMAT_PATCH; + argc = parse_options(argc, argv, NULL, options, + builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0); + + for (i = j = 1; i < argc && strcmp("--", argv[i]); ) { + int c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix); + + if (!c) + argv[j++] = argv[i++]; + else + i += c; + } + while (i < argc) + argv[j++] = argv[i++]; + argc = j; + diff_setup_done(&diffopt); + + /* Make sure that there are no unparsed options */ + argc = parse_options(argc, argv, NULL, + options + ARRAY_SIZE(options) - 1, /* OPT_END */ builtin_range_diff_usage, 0); if (argc == 2) { @@ -59,7 +85,8 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix) usage_with_options(builtin_range_diff_usage, options); } - res = show_range_diff(range1.buf, range2.buf, creation_factor); + res = show_range_diff(range1.buf, range2.buf, creation_factor, + &diffopt); strbuf_release(&range1); strbuf_release(&range2); diff --git a/range-diff.c b/range-diff.c index 2d94200d30..71883a4b7d 100644 --- a/range-diff.c +++ b/range-diff.c @@ -6,6 +6,7 @@ #include "hashmap.h" #include "xdiff-interface.h" #include "linear-assignment.h" +#include "diffcore.h" struct patch_util { /* For the search for an exact match */ @@ -258,7 +259,31 @@ static const char *short_oid(struct patch_util *util) return find_unique_abbrev(&util->oid, DEFAULT_ABBREV); } -static void output(struct string_list *a, struct string_list *b) +static struct diff_filespec *get_filespec(const char *name, const char *p) +{ + struct diff_filespec *spec = alloc_filespec(name); + + fill_filespec(spec, &null_oid, 0, 0644); + spec->data = (char *)p; + spec->size = strlen(p); + spec->should_munmap = 0; + spec->is_stdin = 1; + + return spec; +} + +static void patch_diff(const char *a, const char *b, + struct diff_options *diffopt) +{ + diff_queue(&diff_queued_diff, + get_filespec("a", a), get_filespec("b", b)); + + diffcore_std(diffopt); + diff_flush(diffopt); +} + +static void output(struct string_list *a, struct string_list *b, + struct diff_options *diffopt) { int i = 0, j = 0; @@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b) printf("%d: %s ! %d: %s\n", b_util->matching + 1, short_oid(a_util), j + 1, short_oid(b_util)); + if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT)) + patch_diff(a->items[b_util->matching].string, + b->items[j].string, diffopt); a_util->shown = 1; j++; } @@ -307,7 +335,7 @@ static void output(struct string_list *a, struct string_list *b) } int show_range_diff(const char *range1, const char *range2, - int creation_factor) + int creation_factor, struct diff_options *diffopt) { int res = 0; @@ -322,7 +350,7 @@ int show_range_diff(const char *range1, const char *range2, if (!res) { find_exact_matches(&branch1, &branch2); get_correspondences(&branch1, &branch2, creation_factor); - output(&branch1, &branch2); + output(&branch1, &branch2, diffopt); } string_list_clear(&branch1, 1); diff --git a/range-diff.h b/range-diff.h index 7b6eef303f..2407d46a30 100644 --- a/range-diff.h +++ b/range-diff.h @@ -1,7 +1,9 @@ #ifndef RANGE_DIFF_H #define RANGE_DIFF_H +#include "diff.h" + int show_range_diff(const char *range1, const char *range2, - int creation_factor); + int creation_factor, struct diff_options *diffopt); #endif -- cgit v1.2.3 From a142f978e7fbf7f4cbacb19c2475fbb82c29e5ca Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 13 Aug 2018 04:33:08 -0700 Subject: range-diff: right-trim commit messages When comparing commit messages, we need to keep in mind that they are indented by four spaces. That is, empty lines are no longer empty, but have "trailing whitespace". When displaying them in color, that results in those nagging red lines. Let's just right-trim the lines in the commit message, it's not like trailing white-space in the commit messages are important enough to care about in `git range-diff`. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- range-diff.c | 1 + 1 file changed, 1 insertion(+) (limited to 'range-diff.c') diff --git a/range-diff.c b/range-diff.c index 71883a4b7d..1ecee2c09c 100644 --- a/range-diff.c +++ b/range-diff.c @@ -85,6 +85,7 @@ static int read_patches(const char *range, struct string_list *list) strbuf_addbuf(&buf, &line); strbuf_addstr(&buf, "\n\n"); } else if (starts_with(line.buf, " ")) { + strbuf_rtrim(&line); strbuf_addbuf(&buf, &line); strbuf_addch(&buf, '\n'); } -- cgit v1.2.3 From eb0be38cc94d308fc114289e9555523ae34433b1 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 13 Aug 2018 04:33:13 -0700 Subject: range-diff: adjust the output of the commit pairs This not only uses "dashed stand-ins" for "pairs" where one side is missing (i.e. unmatched commits that are present only in one of the two commit ranges), but also adds onelines for the reader's pleasure. This change brings `git range-diff` yet another step closer to feature parity with tbdiff: it now shows the oneline, too, and indicates with `=` when the commits have identical diffs. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- range-diff.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 9 deletions(-) (limited to 'range-diff.c') diff --git a/range-diff.c b/range-diff.c index 1ecee2c09c..23aa61af59 100644 --- a/range-diff.c +++ b/range-diff.c @@ -7,6 +7,8 @@ #include "xdiff-interface.h" #include "linear-assignment.h" #include "diffcore.h" +#include "commit.h" +#include "pretty.h" struct patch_util { /* For the search for an exact match */ @@ -255,9 +257,49 @@ static void get_correspondences(struct string_list *a, struct string_list *b, free(b2a); } -static const char *short_oid(struct patch_util *util) +static void output_pair_header(struct strbuf *buf, + struct strbuf *dashes, + struct patch_util *a_util, + struct patch_util *b_util) { - return find_unique_abbrev(&util->oid, DEFAULT_ABBREV); + struct object_id *oid = a_util ? &a_util->oid : &b_util->oid; + struct commit *commit; + + if (!dashes->len) + strbuf_addchars(dashes, '-', + strlen(find_unique_abbrev(oid, + DEFAULT_ABBREV))); + + strbuf_reset(buf); + if (!a_util) + strbuf_addf(buf, "-: %s ", dashes->buf); + else + strbuf_addf(buf, "%d: %s ", a_util->i + 1, + find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV)); + + if (!a_util) + strbuf_addch(buf, '>'); + else if (!b_util) + strbuf_addch(buf, '<'); + else if (strcmp(a_util->patch, b_util->patch)) + strbuf_addch(buf, '!'); + else + strbuf_addch(buf, '='); + + if (!b_util) + strbuf_addf(buf, " -: %s", dashes->buf); + else + strbuf_addf(buf, " %d: %s", b_util->i + 1, + find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV)); + + commit = lookup_commit_reference(the_repository, oid); + if (commit) { + strbuf_addch(buf, ' '); + pp_commit_easy(CMIT_FMT_ONELINE, commit, buf); + } + strbuf_addch(buf, '\n'); + + fwrite(buf->buf, buf->len, 1, stdout); } static struct diff_filespec *get_filespec(const char *name, const char *p) @@ -286,6 +328,7 @@ static void patch_diff(const char *a, const char *b, static void output(struct string_list *a, struct string_list *b, struct diff_options *diffopt) { + struct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT; int i = 0, j = 0; /* @@ -307,25 +350,21 @@ static void output(struct string_list *a, struct string_list *b, /* Show unmatched LHS commit whose predecessors were shown. */ if (i < a->nr && a_util->matching < 0) { - printf("%d: %s < -: --------\n", - i + 1, short_oid(a_util)); + output_pair_header(&buf, &dashes, a_util, NULL); i++; continue; } /* Show unmatched RHS commits. */ while (j < b->nr && b_util->matching < 0) { - printf("-: -------- > %d: %s\n", - j + 1, short_oid(b_util)); + output_pair_header(&buf, &dashes, NULL, b_util); b_util = ++j < b->nr ? b->items[j].util : NULL; } /* Show matching LHS/RHS pair. */ if (j < b->nr) { a_util = a->items[b_util->matching].util; - printf("%d: %s ! %d: %s\n", - b_util->matching + 1, short_oid(a_util), - j + 1, short_oid(b_util)); + output_pair_header(&buf, &dashes, a_util, b_util); if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT)) patch_diff(a->items[b_util->matching].string, b->items[j].string, diffopt); @@ -333,6 +372,8 @@ static void output(struct string_list *a, struct string_list *b, j++; } } + strbuf_release(&buf); + strbuf_release(&dashes); } int show_range_diff(const char *range1, const char *range2, -- cgit v1.2.3 From 4eba1fe6156739e69e8cf3c1e84a9f050d91a884 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 13 Aug 2018 04:33:14 -0700 Subject: range-diff: do not show "function names" in hunk headers We are comparing complete, formatted commit messages with patches. There are no function names here, so stop looking for them. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- range-diff.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'range-diff.c') diff --git a/range-diff.c b/range-diff.c index 23aa61af59..6d75563f46 100644 --- a/range-diff.c +++ b/range-diff.c @@ -9,6 +9,7 @@ #include "diffcore.h" #include "commit.h" #include "pretty.h" +#include "userdiff.h" struct patch_util { /* For the search for an exact match */ @@ -302,6 +303,10 @@ static void output_pair_header(struct strbuf *buf, fwrite(buf->buf, buf->len, 1, stdout); } +static struct userdiff_driver no_func_name = { + .funcname = { "$^", 0 } +}; + static struct diff_filespec *get_filespec(const char *name, const char *p) { struct diff_filespec *spec = alloc_filespec(name); @@ -311,6 +316,7 @@ static struct diff_filespec *get_filespec(const char *name, const char *p) spec->size = strlen(p); spec->should_munmap = 0; spec->is_stdin = 1; + spec->driver = &no_func_name; return spec; } -- cgit v1.2.3 From faa1df86dc033ce1e6136ed89ad55df1bd479099 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 13 Aug 2018 04:33:18 -0700 Subject: range-diff: use color for the commit pairs Arguably the most important part of `git range-diff`'s output is the list of commits in the two branches, together with their relationships. For that reason, tbdiff introduced color-coding that is pretty intuitive, especially for unchanged patches (all dim yellow, like the first line in `git show`'s output) vs modified patches (old commit is red, new commit is green). Let's imitate that color scheme. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- range-diff.c | 51 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 13 deletions(-) (limited to 'range-diff.c') diff --git a/range-diff.c b/range-diff.c index 6d75563f46..b1663da7cd 100644 --- a/range-diff.c +++ b/range-diff.c @@ -258,34 +258,53 @@ static void get_correspondences(struct string_list *a, struct string_list *b, free(b2a); } -static void output_pair_header(struct strbuf *buf, +static void output_pair_header(struct diff_options *diffopt, + struct strbuf *buf, struct strbuf *dashes, struct patch_util *a_util, struct patch_util *b_util) { struct object_id *oid = a_util ? &a_util->oid : &b_util->oid; struct commit *commit; + char status; + const char *color_reset = diff_get_color_opt(diffopt, DIFF_RESET); + const char *color_old = diff_get_color_opt(diffopt, DIFF_FILE_OLD); + const char *color_new = diff_get_color_opt(diffopt, DIFF_FILE_NEW); + const char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT); + const char *color; if (!dashes->len) strbuf_addchars(dashes, '-', strlen(find_unique_abbrev(oid, DEFAULT_ABBREV))); + if (!b_util) { + color = color_old; + status = '<'; + } else if (!a_util) { + color = color_new; + status = '>'; + } else if (strcmp(a_util->patch, b_util->patch)) { + color = color_commit; + status = '!'; + } else { + color = color_commit; + status = '='; + } + strbuf_reset(buf); + strbuf_addstr(buf, status == '!' ? color_old : color); if (!a_util) strbuf_addf(buf, "-: %s ", dashes->buf); else strbuf_addf(buf, "%d: %s ", a_util->i + 1, find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV)); - if (!a_util) - strbuf_addch(buf, '>'); - else if (!b_util) - strbuf_addch(buf, '<'); - else if (strcmp(a_util->patch, b_util->patch)) - strbuf_addch(buf, '!'); - else - strbuf_addch(buf, '='); + if (status == '!') + strbuf_addf(buf, "%s%s", color_reset, color); + strbuf_addch(buf, status); + if (status == '!') + strbuf_addf(buf, "%s%s", color_reset, color_new); if (!b_util) strbuf_addf(buf, " -: %s", dashes->buf); @@ -295,10 +314,13 @@ static void output_pair_header(struct strbuf *buf, commit = lookup_commit_reference(the_repository, oid); if (commit) { + if (status == '!') + strbuf_addf(buf, "%s%s", color_reset, color); + strbuf_addch(buf, ' '); pp_commit_easy(CMIT_FMT_ONELINE, commit, buf); } - strbuf_addch(buf, '\n'); + strbuf_addf(buf, "%s\n", color_reset); fwrite(buf->buf, buf->len, 1, stdout); } @@ -356,21 +378,24 @@ static void output(struct string_list *a, struct string_list *b, /* Show unmatched LHS commit whose predecessors were shown. */ if (i < a->nr && a_util->matching < 0) { - output_pair_header(&buf, &dashes, a_util, NULL); + output_pair_header(diffopt, + &buf, &dashes, a_util, NULL); i++; continue; } /* Show unmatched RHS commits. */ while (j < b->nr && b_util->matching < 0) { - output_pair_header(&buf, &dashes, NULL, b_util); + output_pair_header(diffopt, + &buf, &dashes, NULL, b_util); b_util = ++j < b->nr ? b->items[j].util : NULL; } /* Show matching LHS/RHS pair. */ if (j < b->nr) { a_util = a->items[b_util->matching].util; - output_pair_header(&buf, &dashes, a_util, b_util); + output_pair_header(diffopt, + &buf, &dashes, a_util, b_util); if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT)) patch_diff(a->items[b_util->matching].string, b->items[j].string, diffopt); -- cgit v1.2.3 From d1f87a2d9c4877ada003c231b92de7560eab62ed Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 13 Aug 2018 04:33:28 -0700 Subject: range-diff: left-pad patch numbers As pointed out by Elijah Newren, tbdiff has this neat little alignment trick where it outputs the commit pairs with patch numbers that are padded to the maximal patch number's width: 1: cafedead = 1: acefade first patch [...] 314: beefeada < 314: facecab up to PI! Let's do the same in range-diff, too. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- range-diff.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'range-diff.c') diff --git a/range-diff.c b/range-diff.c index b1663da7cd..b6b9abac26 100644 --- a/range-diff.c +++ b/range-diff.c @@ -259,6 +259,7 @@ static void get_correspondences(struct string_list *a, struct string_list *b, } static void output_pair_header(struct diff_options *diffopt, + int patch_no_width, struct strbuf *buf, struct strbuf *dashes, struct patch_util *a_util, @@ -295,9 +296,9 @@ static void output_pair_header(struct diff_options *diffopt, strbuf_reset(buf); strbuf_addstr(buf, status == '!' ? color_old : color); if (!a_util) - strbuf_addf(buf, "-: %s ", dashes->buf); + strbuf_addf(buf, "%*s: %s ", patch_no_width, "-", dashes->buf); else - strbuf_addf(buf, "%d: %s ", a_util->i + 1, + strbuf_addf(buf, "%*d: %s ", patch_no_width, a_util->i + 1, find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV)); if (status == '!') @@ -307,9 +308,9 @@ static void output_pair_header(struct diff_options *diffopt, strbuf_addf(buf, "%s%s", color_reset, color_new); if (!b_util) - strbuf_addf(buf, " -: %s", dashes->buf); + strbuf_addf(buf, " %*s: %s", patch_no_width, "-", dashes->buf); else - strbuf_addf(buf, " %d: %s", b_util->i + 1, + strbuf_addf(buf, " %*d: %s", patch_no_width, b_util->i + 1, find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV)); commit = lookup_commit_reference(the_repository, oid); @@ -357,6 +358,7 @@ static void output(struct string_list *a, struct string_list *b, struct diff_options *diffopt) { struct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT; + int patch_no_width = decimal_width(1 + (a->nr > b->nr ? a->nr : b->nr)); int i = 0, j = 0; /* @@ -378,7 +380,7 @@ static void output(struct string_list *a, struct string_list *b, /* Show unmatched LHS commit whose predecessors were shown. */ if (i < a->nr && a_util->matching < 0) { - output_pair_header(diffopt, + output_pair_header(diffopt, patch_no_width, &buf, &dashes, a_util, NULL); i++; continue; @@ -386,7 +388,7 @@ static void output(struct string_list *a, struct string_list *b, /* Show unmatched RHS commits. */ while (j < b->nr && b_util->matching < 0) { - output_pair_header(diffopt, + output_pair_header(diffopt, patch_no_width, &buf, &dashes, NULL, b_util); b_util = ++j < b->nr ? b->items[j].util : NULL; } @@ -394,7 +396,7 @@ static void output(struct string_list *a, struct string_list *b, /* Show matching LHS/RHS pair. */ if (j < b->nr) { a_util = a->items[b_util->matching].util; - output_pair_header(diffopt, + output_pair_header(diffopt, patch_no_width, &buf, &dashes, a_util, b_util); if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT)) patch_diff(a->items[b_util->matching].string, -- cgit v1.2.3