{"thread":{"id":"48405","subject":"[PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike","startedAt":"2018-04-30T21:54:13Z","lastAt":"2019-03-05T06:29:14Z","messageCount":387,"participants":["Johannes Schindelin","Ramsay Jones","Duy Nguyen","Stefan Beller","Ævar Arnfjörð Bjarmason","Jacob Keller","Philip Oakley","Eric Sunshine","Junio C Hamano","Elijah Newren","Jeff King","Todd Zullinger","Igor Djordjevic","Martin Ågren","brian m. carlson","SZEDER Gábor","Brandon Williams","Øyvind Rønningstad","Johannes Schindelin via GitGitGadget","Thomas Rast via GitGitGadget","Thomas Gummerer"],"isPatch":true,"patchVersion":1,"patchTotal":18},"messages":[{"id":"351585","messageId":"39272eefcfe66de3ca1aa2ee43d6626ce558caae.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-04-30T21:54:13Z","receivedAt":"2018-04-30T21:54:13Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe problem solved by the code introduced in this commit goes like this:\ngiven two sets of items, and a cost matrix which says how much it\n\"costs\" to assign any given item of the first set to any given item of\nthe second, assign all items (except when the sets have different size)\nin the cheapest way.\n\nWe use the Jonker-Volgenant algorithm to solve the assignment problem to\nanswer questions such as: given two different versions of a topic branch\n(or iterations of a patch series), what is the best pairing of\ncommits/patches between the different versions?\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Makefile            |   1 +\n linear-assignment.c | 203 ++++++++++++++++++++++++++++++++++++++++++++\n linear-assignment.h |  22 +++++\n 3 files changed, 226 insertions(+)\n create mode 100644 linear-assignment.c\n create mode 100644 linear-assignment.h\n\ndiff --git a/Makefile b/Makefile\nindex 0cb6590f2..c5ba124f1 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -868,6 +868,7 @@ LIB_OBJS += gpg-interface.o\n LIB_OBJS += graph.o\n LIB_OBJS += grep.o\n LIB_OBJS += hashmap.o\n+LIB_OBJS += linear-assignment.o\n LIB_OBJS += help.o\n LIB_OBJS += hex.o\n LIB_OBJS += ident.o\ndiff --git a/linear-assignment.c b/linear-assignment.c\nnew file mode 100644\nindex 000000000..0b0344b5f\n--- /dev/null\n+++ b/linear-assignment.c\n@@ -0,0 +1,203 @@\n+/*\n+ * Based on: Jonker, R., & Volgenant, A. (1987). <i>A shortest augmenting path\n+ * algorithm for dense and sparse linear assignment problems</i>. Computing,\n+ * 38(4), 325-340.\n+ */\n+#include \"cache.h\"\n+#include \"linear-assignment.h\"\n+\n+#define COST(column, row) cost[(column) + column_count * (row)]\n+\n+/*\n+ * The parameter `cost` is the cost matrix: the cost to assign column j to row\n+ * i is `cost[j + column_count * i].\n+ */\n+void compute_assignment(int column_count, int row_count, int *cost,\n+\t\t\tint *column2row, int *row2column)\n+{\n+\tint *v, *d;\n+\tint *free_row, free_count = 0, saved_free_count, *pred, *col;\n+\tint i, j, phase;\n+\n+\tmemset(column2row, -1, sizeof(int) * column_count);\n+\tmemset(row2column, -1, sizeof(int) * row_count);\n+\tALLOC_ARRAY(v, column_count);\n+\n+\t/* column reduction */\n+\tfor (j = column_count - 1; j >= 0; j--) {\n+\t\tint i1 = 0;\n+\n+\t\tfor (i = 1; i < row_count; i++)\n+\t\t\tif (COST(j, i1) > COST(j, i))\n+\t\t\t\ti1 = i;\n+\t\tv[j] = COST(j, i1);\n+\t\tif (row2column[i1] == -1) {\n+\t\t\t/* row i1 unassigned */\n+\t\t\trow2column[i1] = j;\n+\t\t\tcolumn2row[j] = i1;\n+\t\t} else {\n+\t\t\tif (row2column[i1] >= 0)\n+\t\t\t\trow2column[i1] = -2 - row2column[i1];\n+\t\t\tcolumn2row[j] = -1;\n+\t\t}\n+\t}\n+\n+\t/* reduction transfer */\n+\tALLOC_ARRAY(free_row, row_count);\n+\tfor (i = 0; i < row_count; i++) {\n+\t\tint j1 = row2column[i];\n+\t\tif (j1 == -1)\n+\t\t\tfree_row[free_count++] = i;\n+\t\telse if (j1 < -1)\n+\t\t\trow2column[i] = -2 - j1;\n+\t\telse {\n+\t\t\tint min = COST(!j1, i) - v[!j1];\n+\t\t\tfor (j = 1; j < column_count; j++)\n+\t\t\t\tif (j != j1 && min > COST(j, i) - v[j])\n+\t\t\t\t\tmin = COST(j, i) - v[j];\n+\t\t\tv[j1] -= min;\n+\t\t}\n+\t}\n+\n+\tif (free_count ==\n+\t    (column_count < row_count ? row_count - column_count : 0)) {\n+\t\tfree(v);\n+\t\tfree(free_row);\n+\t\treturn;\n+\t}\n+\n+\t/* augmenting row reduction */\n+\tfor (phase = 0; phase < 2; phase++) {\n+\t\tint k = 0;\n+\n+\t\tsaved_free_count = free_count;\n+\t\tfree_count = 0;\n+\t\twhile (k < saved_free_count) {\n+\t\t\tint u1, u2;\n+\t\t\tint j1 = 0, j2, i0;\n+\n+\t\t\ti = free_row[k++];\n+\t\t\tu1 = COST(j1, i) - v[j1];\n+\t\t\tj2 = -1;\n+\t\t\tu2 = INT_MAX;\n+\t\t\tfor (j = 1; j < column_count; j++) {\n+\t\t\t\tint c = COST(j, i) - v[j];\n+\t\t\t\tif (u2 > c) {\n+\t\t\t\t\tif (u1 < c) {\n+\t\t\t\t\t\tu2 = c;\n+\t\t\t\t\t\tj2 = j;\n+\t\t\t\t\t} else {\n+\t\t\t\t\t\tu2 = u1;\n+\t\t\t\t\t\tu1 = c;\n+\t\t\t\t\t\tj2 = j1;\n+\t\t\t\t\t\tj1 = j;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tif (j2 < 0) {\n+\t\t\t\tj2 = j1;\n+\t\t\t\tu2 = u1;\n+\t\t\t}\n+\n+\t\t\ti0 = column2row[j1];\n+\t\t\tif (u1 < u2)\n+\t\t\t\tv[j1] -= u2 - u1;\n+\t\t\telse if (i0 >= 0) {\n+\t\t\t\tj1 = j2;\n+\t\t\t\ti0 = column2row[j1];\n+\t\t\t}\n+\n+\t\t\tif (i0 >= 0) {\n+\t\t\t\tif (u1 < u2)\n+\t\t\t\t\tfree_row[--k] = i0;\n+\t\t\t\telse\n+\t\t\t\t\tfree_row[free_count++] = i0;\n+\t\t\t}\n+\t\t\trow2column[i] = j1;\n+\t\t\tcolumn2row[j1] = i;\n+\t\t}\n+\t}\n+\n+\t/* augmentation */\n+\tsaved_free_count = free_count;\n+\tALLOC_ARRAY(d, column_count);\n+\tALLOC_ARRAY(pred, column_count);\n+\tALLOC_ARRAY(col, column_count);\n+\tfor (free_count = 0; free_count < saved_free_count; free_count++) {\n+\t\tint i1 = free_row[free_count], low = 0, up = 0, last, k;\n+\t\tint min, c, u1;\n+\n+\t\tfor (j = 0; j < column_count; j++) {\n+\t\t\td[j] = COST(j, i1) - v[j];\n+\t\t\tpred[j] = i1;\n+\t\t\tcol[j] = j;\n+\t\t}\n+\n+\t\tj = -1;\n+\t\tdo {\n+\t\t\tlast = low;\n+\t\t\tmin = d[col[up++]];\n+\t\t\tfor (k = up; k < column_count; k++) {\n+\t\t\t\tj = col[k];\n+\t\t\t\tc = d[j];\n+\t\t\t\tif (c <= min) {\n+\t\t\t\t\tif (c < min) {\n+\t\t\t\t\t\tup = low;\n+\t\t\t\t\t\tmin = c;\n+\t\t\t\t\t}\n+\t\t\t\t\tcol[k] = col[up];\n+\t\t\t\t\tcol[up++] = j;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tfor (k = low; k < up; k++)\n+\t\t\t\tif (column2row[col[k]] == -1)\n+\t\t\t\t\tgoto update;\n+\n+\t\t\t/* scan a row */\n+\t\t\tdo {\n+\t\t\t\tint j1 = col[low++];\n+\n+\t\t\t\ti = column2row[j1];\n+\t\t\t\tu1 = COST(j1, i) - v[j1] - min;\n+\t\t\t\tfor (k = up; k < column_count; k++) {\n+\t\t\t\t\tj = col[k];\n+\t\t\t\t\tc = COST(j, i) - v[j] - u1;\n+\t\t\t\t\tif (c < d[j]) {\n+\t\t\t\t\t\td[j] = c;\n+\t\t\t\t\t\tpred[j] = i;\n+\t\t\t\t\t\tif (c == min) {\n+\t\t\t\t\t\t\tif (column2row[j] == -1)\n+\t\t\t\t\t\t\t\tgoto update;\n+\t\t\t\t\t\t\tcol[k] = col[up];\n+\t\t\t\t\t\t\tcol[up++] = j;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t} while (low != up);\n+\t\t} while (low == up);\n+\n+update:\n+\t\t/* updating of the column pieces */\n+\t\tfor (k = 0; k < last; k++) {\n+\t\t\tint j1 = col[k];\n+\t\t\tv[j1] += d[j1] - min;\n+\t\t}\n+\n+\t\t/* augmentation */\n+\t\tdo {\n+\t\t\tif (j < 0)\n+\t\t\t\tBUG(\"negative j: %d\", j);\n+\t\t\ti = pred[j];\n+\t\t\tcolumn2row[j] = i;\n+\t\t\tk = j;\n+\t\t\tj = row2column[i];\n+\t\t\trow2column[i] = k;\n+\t\t} while (i1 != i);\n+\t}\n+\n+\tfree(col);\n+\tfree(pred);\n+\tfree(d);\n+\tfree(v);\n+\tfree(free_row);\n+}\ndiff --git a/linear-assignment.h b/linear-assignment.h\nnew file mode 100644\nindex 000000000..fc4c502c8\n--- /dev/null\n+++ b/linear-assignment.h\n@@ -0,0 +1,22 @@\n+#ifndef HUNGARIAN_H\n+#define HUNGARIAN_H\n+\n+/*\n+ * Compute an assignment of columns -> rows (and vice versa) such that every\n+ * column is assigned to at most one row (and vice versa) minimizing the\n+ * overall cost.\n+ *\n+ * The parameter `cost` is the cost matrix: the cost to assign column j to row\n+ * i is `cost[j + column_count * i].\n+ *\n+ * The arrays column2row and row2column will be populated with the respective\n+ * assignments (-1 for unassigned, which can happen only if column_count !=\n+ * row_count).\n+ */\n+void compute_assignment(int column_count, int row_count, int *cost,\n+\t\t\tint *column2row, int *row2column);\n+\n+/* The maximal cost in the cost matrix (to prevent integer overflows). */\n+#define COST_MAX (1<<16)\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"351605","messageId":"7f15b26d4eaeca276ae2050584ff632b012787f2.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 02/20] Introduce `range-diff` to compare iterations of a topic branch","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-05-01T19:42:28Z","receivedAt":"2018-05-01T19:42:28Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis command does not do a whole lot so far, apart from showing a usage\nthat is oddly similar to that of `git tbdiff`. And for a good reason:\nthe next commits will turn `range-branch` into a full-blown replacement\nfor `tbdiff`.\n\nAt this point, we ignore tbdiff's color options, as they will all be\nimplemented later using diff_options.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n .gitignore           |  1 +\n Makefile             |  1 +\n builtin.h            |  1 +\n builtin/range-diff.c | 25 +++++++++++++++++++++++++\n command-list.txt     |  1 +\n git.c                |  1 +\n 6 files changed, 30 insertions(+)\n create mode 100644 builtin/range-diff.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 3284a1e9b..cc0ad74b4 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -113,6 +113,7 @@\n /git-pull\n /git-push\n /git-quiltimport\n+/git-range-diff\n /git-read-tree\n /git-rebase\n /git-rebase--am\ndiff --git a/Makefile b/Makefile\nindex c5ba124f1..190384cae 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1059,6 +1059,7 @@ BUILTIN_OBJS += builtin/prune-packed.o\n BUILTIN_OBJS += builtin/prune.o\n BUILTIN_OBJS += builtin/pull.o\n BUILTIN_OBJS += builtin/push.o\n+BUILTIN_OBJS += builtin/range-diff.o\n BUILTIN_OBJS += builtin/read-tree.o\n BUILTIN_OBJS += builtin/rebase--helper.o\n BUILTIN_OBJS += builtin/receive-pack.o\ndiff --git a/builtin.h b/builtin.h\nindex 0362f1ce2..99206df4b 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -201,6 +201,7 @@ extern int cmd_prune(int argc, const char **argv, const char *prefix);\n extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);\n extern int cmd_pull(int argc, const char **argv, const char *prefix);\n extern int cmd_push(int argc, const char **argv, const char *prefix);\n+extern int cmd_range_diff(int argc, const char **argv, const char *prefix);\n extern int cmd_read_tree(int argc, const char **argv, const char *prefix);\n extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix);\n extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nnew file mode 100644\nindex 000000000..36788ea4f\n--- /dev/null\n+++ b/builtin/range-diff.c\n@@ -0,0 +1,25 @@\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"parse-options.h\"\n+\n+static const char * const builtin_range_diff_usage[] = {\n+N_(\"git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n+N_(\"git range-diff [<options>] <old-tip>...<new-tip>\"),\n+N_(\"git range-diff [<options>] <base> <old-tip> <new-tip>\"),\n+NULL\n+};\n+\n+int cmd_range_diff(int argc, const char **argv, const char *prefix)\n+{\n+\tint creation_factor = 60;\n+\tstruct option options[] = {\n+\t\tOPT_INTEGER(0, \"creation-factor\", &creation_factor,\n+\t\t\t    N_(\"Percentage by which creation is weighted\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, options,\n+\t\t\t     builtin_range_diff_usage, 0);\n+\n+\treturn 0;\n+}\ndiff --git a/command-list.txt b/command-list.txt\nindex e1c26c1bb..a9dda3b8a 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -139,6 +139,7 @@ git-prune-packed                        plumbingmanipulators\n git-pull                                mainporcelain           remote\n git-push                                mainporcelain           remote\n git-quiltimport                         foreignscminterface\n+git-range-diff                          mainporcelain\n git-read-tree                           plumbingmanipulators\n git-rebase                              mainporcelain           history\n git-receive-pack                        synchelpers\ndiff --git a/git.c b/git.c\nindex 9dbe6ffaa..13e37f1e3 100644\n--- a/git.c\n+++ b/git.c\n@@ -517,6 +517,7 @@ static struct cmd_struct commands[] = {\n \t{ \"prune-packed\", cmd_prune_packed, RUN_SETUP },\n \t{ \"pull\", cmd_pull, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"push\", cmd_push, RUN_SETUP },\n+\t{ \"range-diff\", cmd_range_diff, RUN_SETUP | USE_PAGER },\n \t{ \"read-tree\", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX},\n \t{ \"rebase--helper\", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"receive-pack\", cmd_receive_pack },\n-- \ngitgitgadget\n\n"},{"id":"351604","messageId":"076e1192d15562d868a8a6014f36155f360da83d.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 03/20] range-diff: first rudimentary implementation","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-05-02T00:34:01Z","receivedAt":"2018-05-02T00:34:01Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nAt this stage, `git range-diff` can determine corresponding commits\nof two related commit ranges. This makes use of the recently introduced\nimplementation of the Hungarian algorithm.\n\nThe core of this patch is a straight port of the ideas of tbdiff, the\napparently dormant project at https://github.com/trast/tbdiff.\n\nThe output does not at all match `tbdiff`'s output yet, as this patch\nreally concentrates on getting the patch matching part right.\n\nNote: due to differences in the diff algorithm (`tbdiff` uses the Python\nmodule `difflib`, Git uses its xdiff fork), the cost matrix calculated\nby `range-diff` is different (but very similar) to the one calculated\nby `tbdiff`. Therefore, it is possible that they find different matching\ncommits in corner cases (e.g. when a patch was split into two patches of\nroughly equal length).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Makefile             |   1 +\n builtin/range-diff.c |  47 ++++++-\n range-diff.c         | 307 +++++++++++++++++++++++++++++++++++++++++++\n range-diff.h         |   7 +\n 4 files changed, 359 insertions(+), 3 deletions(-)\n create mode 100644 range-diff.c\n create mode 100644 range-diff.h\n\ndiff --git a/Makefile b/Makefile\nindex 190384cae..f20126e11 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -921,6 +921,7 @@ LIB_OBJS += progress.o\n LIB_OBJS += prompt.o\n LIB_OBJS += protocol.o\n LIB_OBJS += quote.o\n+LIB_OBJS += range-diff.o\n LIB_OBJS += reachable.o\n LIB_OBJS += read-cache.o\n LIB_OBJS += reflog-walk.o\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 36788ea4f..c37a72100 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -1,6 +1,7 @@\n #include \"cache.h\"\n #include \"builtin.h\"\n #include \"parse-options.h\"\n+#include \"range-diff.h\"\n \n static const char * const builtin_range_diff_usage[] = {\n N_(\"git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n@@ -17,9 +18,49 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"Percentage by which creation is weighted\")),\n \t\tOPT_END()\n \t};\n+\tint res = 0;\n+\tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n \n-\targc = parse_options(argc, argv, NULL, options,\n-\t\t\t     builtin_range_diff_usage, 0);\n+\targc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,\n+\t\t\t     0);\n \n-\treturn 0;\n+\tif (argc == 2) {\n+\t\tif (!strstr(argv[0], \"..\"))\n+\t\t\twarning(_(\"no .. in range: '%s'\"), argv[0]);\n+\t\tstrbuf_addstr(&range1, argv[0]);\n+\n+\t\tif (!strstr(argv[1], \"..\"))\n+\t\t\twarning(_(\"no .. in range: '%s'\"), argv[1]);\n+\t\tstrbuf_addstr(&range2, argv[1]);\n+\t} else if (argc == 3) {\n+\t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n+\t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n+\t} else if (argc == 1) {\n+\t\tconst char *b = strstr(argv[0], \"...\"), *a = argv[0];\n+\t\tint a_len;\n+\n+\t\tif (!b)\n+\t\t\tdie(_(\"single arg format requires a symmetric range\"));\n+\n+\t\ta_len = (int)(b - a);\n+\t\tif (!a_len) {\n+\t\t\ta = \"HEAD\";\n+\t\t\ta_len = strlen(a);\n+\t\t}\n+\t\tb += 3;\n+\t\tif (!*b)\n+\t\t\tb = \"HEAD\";\n+\t\tstrbuf_addf(&range1, \"%s..%.*s\", b, a_len, a);\n+\t\tstrbuf_addf(&range2, \"%.*s..%s\", a_len, a, b);\n+\t} else {\n+\t\terror(_(\"need two commit ranges\"));\n+\t\tusage_with_options(builtin_range_diff_usage, options);\n+\t}\n+\n+\tres = show_range_diff(range1.buf, range2.buf, creation_factor);\n+\n+\tstrbuf_release(&range1);\n+\tstrbuf_release(&range2);\n+\n+\treturn res;\n }\ndiff --git a/range-diff.c b/range-diff.c\nnew file mode 100644\nindex 000000000..c374333a4\n--- /dev/null\n+++ b/range-diff.c\n@@ -0,0 +1,307 @@\n+#include \"cache.h\"\n+#include \"range-diff.h\"\n+#include \"string-list.h\"\n+#include \"run-command.h\"\n+#include \"argv-array.h\"\n+#include \"hashmap.h\"\n+#include \"xdiff-interface.h\"\n+#include \"linear-assignment.h\"\n+\n+struct patch_util {\n+\t/* For the search for an exact match */\n+\tstruct hashmap_entry e;\n+\tconst char *diff, *patch;\n+\n+\tint i;\n+\tint diffsize;\n+\tsize_t diff_offset;\n+\t/* the index of the matching item in the other branch, or -1 */\n+\tint matching;\n+\tstruct object_id oid;\n+};\n+\n+/*\n+ * Reads the patches into a string list, with the `util` field being populated\n+ * as struct object_id (will need to be free()d).\n+ */\n+static int read_patches(const char *range, struct string_list *list)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tFILE *in;\n+\tstruct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;\n+\tstruct patch_util *util = NULL;\n+\tint in_header = 1;\n+\n+\targv_array_pushl(&cp.args, \"log\", \"--no-color\", \"-p\", \"--no-merges\",\n+\t\t\t\"--reverse\", \"--date-order\", \"--decorate=no\",\n+\t\t\t\"--no-abbrev-commit\", range,\n+\t\t\tNULL);\n+\tcp.out = -1;\n+\tcp.no_stdin = 1;\n+\tcp.git_cmd = 1;\n+\n+\tif (start_command(&cp))\n+\t\treturn error_errno(_(\"could not start `log`\"));\n+\tin = fdopen(cp.out, \"r\");\n+\tif (!in) {\n+\t\terror_errno(_(\"could not read `log` output\"));\n+\t\tfinish_command(&cp);\n+\t\treturn -1;\n+\t}\n+\n+\twhile (strbuf_getline(&line, in) != EOF) {\n+\t\tconst char *p;\n+\n+\t\tif (skip_prefix(line.buf, \"commit \", &p)) {\n+\t\t\tif (util) {\n+\t\t\t\tstring_list_append(list, buf.buf)->util = util;\n+\t\t\t\tstrbuf_reset(&buf);\n+\t\t\t}\n+\t\t\tutil = xcalloc(sizeof(*util), 1);\n+\t\t\tif (get_oid(p, &util->oid)) {\n+\t\t\t\terror(_(\"could not parse commit '%s'\"), p);\n+\t\t\t\tfree(util);\n+\t\t\t\tstring_list_clear(list, 1);\n+\t\t\t\tstrbuf_release(&buf);\n+\t\t\t\tstrbuf_release(&line);\n+\t\t\t\tfclose(in);\n+\t\t\t\tfinish_command(&cp);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t\tutil->matching = -1;\n+\t\t\tin_header = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (starts_with(line.buf, \"diff --git\")) {\n+\t\t\tin_header = 0;\n+\t\t\tstrbuf_addch(&buf, '\\n');\n+\t\t\tif (!util->diff_offset)\n+\t\t\t\tutil->diff_offset = buf.len;\n+\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\t} else if (in_header) {\n+\t\t\tif (starts_with(line.buf, \"Author: \")) {\n+\t\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\t\t\tstrbuf_addstr(&buf, \"\\n\\n\");\n+\t\t\t} else if (starts_with(line.buf, \"    \")) {\n+\t\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\t\t\tstrbuf_addch(&buf, '\\n');\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t} else if (starts_with(line.buf, \"@@ \"))\n+\t\t\tstrbuf_addstr(&buf, \"@@\");\n+\t\telse if (line.buf[0] && !starts_with(line.buf, \"index \"))\n+\t\t\t/*\n+\t\t\t * A completely blank (not ' \\n', which is context)\n+\t\t\t * line is not valid in a diff.  We skip it\n+\t\t\t * silently, because this neatly handles the blank\n+\t\t\t * separator line between commits in git-log\n+\t\t\t * output.\n+\t\t\t */\n+\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\telse\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_addch(&buf, '\\n');\n+\t\tutil->diffsize++;\n+\t}\n+\tfclose(in);\n+\tstrbuf_release(&line);\n+\n+\tif (util)\n+\t\tstring_list_append(list, buf.buf)->util = util;\n+\tstrbuf_release(&buf);\n+\n+\tif (finish_command(&cp))\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+static int patch_util_cmp(const void *dummy, const struct patch_util *a,\n+\t\t     const struct patch_util *b, const char *keydata)\n+{\n+\treturn strcmp(a->diff, keydata ? keydata : b->diff);\n+}\n+\n+static void find_exact_matches(struct string_list *a, struct string_list *b)\n+{\n+\tstruct hashmap map;\n+\tint i;\n+\n+\thashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);\n+\n+\t/* First, add the patches of a to a hash map */\n+\tfor (i = 0; i < a->nr; i++) {\n+\t\tstruct patch_util *util = a->items[i].util;\n+\n+\t\tutil->i = i;\n+\t\tutil->patch = a->items[i].string;\n+\t\tutil->diff = util->patch + util->diff_offset;\n+\t\thashmap_entry_init(util, strhash(util->diff));\n+\t\thashmap_add(&map, util);\n+\t}\n+\n+\t/* Now try to find exact matches in b */\n+\tfor (i = 0; i < b->nr; i++) {\n+\t\tstruct patch_util *util = b->items[i].util, *other;\n+\n+\t\tutil->i = i;\n+\t\tutil->patch = b->items[i].string;\n+\t\tutil->diff = util->patch + util->diff_offset;\n+\t\thashmap_entry_init(util, strhash(util->diff));\n+\t\tother = hashmap_remove(&map, util, NULL);\n+\t\tif (other) {\n+\t\t\tif (other->matching >= 0)\n+\t\t\t\tBUG(\"already assigned!\");\n+\n+\t\t\tother->matching = i;\n+\t\t\tutil->matching = other->i;\n+\t\t}\n+\t}\n+\n+\thashmap_free(&map, 0);\n+}\n+\n+static void diffsize_consume(void *data, char *line, unsigned long len)\n+{\n+\t(*(int *)data)++;\n+}\n+\n+static int diffsize(const char *a, const char *b)\n+{\n+\txpparam_t pp = { 0 };\n+\txdemitconf_t cfg = { 0 };\n+\tmmfile_t mf1, mf2;\n+\tint count = 0;\n+\n+\tmf1.ptr = (char *)a;\n+\tmf1.size = strlen(a);\n+\tmf2.ptr = (char *)b;\n+\tmf2.size = strlen(b);\n+\n+\tcfg.ctxlen = 3;\n+\tif (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))\n+\t\treturn count;\n+\n+\terror(_(\"failed to generate diff\"));\n+\treturn COST_MAX;\n+}\n+\n+static void get_correspondences(struct string_list *a, struct string_list *b,\n+\t\t\t\tint creation_factor)\n+{\n+\tint n = a->nr + b->nr;\n+\tint *cost, c, *a2b, *b2a;\n+\tint i, j;\n+\n+\tALLOC_ARRAY(cost, st_mult(n, n));\n+\tALLOC_ARRAY(a2b, n);\n+\tALLOC_ARRAY(b2a, n);\n+\n+\tfor (i = 0; i < a->nr; i++) {\n+\t\tstruct patch_util *a_util = a->items[i].util;\n+\n+\t\tfor (j = 0; j < b->nr; j++) {\n+\t\t\tstruct patch_util *b_util = b->items[j].util;\n+\n+\t\t\tif (a_util->matching == j)\n+\t\t\t\tc = 0;\n+\t\t\telse if (a_util->matching < 0 && b_util->matching < 0)\n+\t\t\t\tc = diffsize(a_util->diff, b_util->diff);\n+\t\t\telse\n+\t\t\t\tc = COST_MAX;\n+\t\t\tcost[i + n * j] = c;\n+\t\t}\n+\n+\t\tc = a_util->matching < 0 ?\n+\t\t\ta_util->diffsize * creation_factor / 100 : COST_MAX;\n+\t\tfor (j = b->nr; j < n; j++)\n+\t\t\tcost[i + n * j] = c;\n+\t}\n+\n+\tfor (j = 0; j < b->nr; j++) {\n+\t\tstruct patch_util *util = b->items[j].util;\n+\n+\t\tc = util->matching < 0 ?\n+\t\t\tutil->diffsize * creation_factor / 100 : COST_MAX;\n+\t\tfor (i = a->nr; i < n; i++)\n+\t\t\tcost[i + n * j] = c;\n+\t}\n+\n+\tfor (i = a->nr; i < n; i++)\n+\t\tfor (j = b->nr; j < n; j++)\n+\t\t\tcost[i + n * j] = 0;\n+\n+\tcompute_assignment(n, n, cost, a2b, b2a);\n+\n+\tfor (i = 0; i < a->nr; i++)\n+\t\tif (a2b[i] >= 0 && a2b[i] < b->nr) {\n+\t\t\tstruct patch_util *a_util = a->items[i].util;\n+\t\t\tstruct patch_util *b_util = b->items[a2b[i]].util;\n+\n+\t\t\ta_util->matching = a2b[i];\n+\t\t\tb_util->matching = i;\n+\t\t}\n+\n+\tfree(cost);\n+\tfree(a2b);\n+\tfree(b2a);\n+}\n+\n+static const char *short_oid(struct patch_util *util)\n+{\n+\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n+}\n+\n+static void output(struct string_list *a, struct string_list *b)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < b->nr; i++) {\n+\t\tstruct patch_util *util = b->items[i].util, *prev;\n+\n+\t\tif (util->matching < 0)\n+\t\t\tprintf(\"-: -------- > %d: %s\\n\",\n+\t\t\t\t\ti + 1, short_oid(util));\n+\t\telse {\n+\t\t\tprev = a->items[util->matching].util;\n+\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n+\t\t\t       util->matching + 1, short_oid(prev),\n+\t\t\t       i + 1, short_oid(util));\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i < a->nr; i++) {\n+\t\tstruct patch_util *util = a->items[i].util;\n+\n+\t\tif (util->matching < 0)\n+\t\t\tprintf(\"%d: %s < -: --------\\n\",\n+\t\t\t       i + 1, short_oid(util));\n+\t}\n+}\n+\n+int show_range_diff(const char *range1, const char *range2,\n+\t\t    int creation_factor)\n+{\n+\tint res = 0;\n+\n+\tstruct string_list branch1 = STRING_LIST_INIT_DUP;\n+\tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n+\n+\tif (read_patches(range1, &branch1))\n+\t\tres = error(_(\"could not parse log for '%s'\"), range1);\n+\tif (!res && read_patches(range2, &branch2))\n+\t\tres = error(_(\"could not parse log for '%s'\"), range2);\n+\n+\tif (!res) {\n+\t\tfind_exact_matches(&branch1, &branch2);\n+\t\tget_correspondences(&branch1, &branch2, creation_factor);\n+\t\toutput(&branch1, &branch2);\n+\t}\n+\n+\tstring_list_clear(&branch1, 1);\n+\tstring_list_clear(&branch2, 1);\n+\n+\treturn res;\n+}\ndiff --git a/range-diff.h b/range-diff.h\nnew file mode 100644\nindex 000000000..dd30449c4\n--- /dev/null\n+++ b/range-diff.h\n@@ -0,0 +1,7 @@\n+#ifndef BRANCH_DIFF_H\n+#define BRANCH_DIFF_H\n+\n+int show_range_diff(const char *range1, const char *range2,\n+\t\t    int creation_factor);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"351586","messageId":"e98489c8cfd8d45fab5f70923a0788f5abe17789.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 04/20] range-diff: improve the order of the shown commits","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-05-02T10:22:29Z","receivedAt":"2018-05-02T10:22:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis patch lets `git range-diff` use the same order as tbdiff.\n\nThe idea is simple: for left-to-right readers, it is natural to assume\nthat the `git range-diff` is performed between an older vs a newer\nversion of the branch. As such, the user is probably more interested in\nthe question \"where did this come from?\" rather than \"where did that one\ngo?\".\n\nTo that end, we list the commits in the order of the second commit range\n(\"the newer version\"), inserting the unmatched commits of the first\ncommit range as soon as all their predecessors have been shown.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 59 +++++++++++++++++++++++++++++++++++-----------------\n 1 file changed, 40 insertions(+), 19 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex c374333a4..e71cf0ba7 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -12,7 +12,7 @@ struct patch_util {\n \tstruct hashmap_entry e;\n \tconst char *diff, *patch;\n \n-\tint i;\n+\tint i, shown;\n \tint diffsize;\n \tsize_t diff_offset;\n \t/* the index of the matching item in the other branch, or -1 */\n@@ -256,28 +256,49 @@ static const char *short_oid(struct patch_util *util)\n \n static void output(struct string_list *a, struct string_list *b)\n {\n-\tint i;\n-\n-\tfor (i = 0; i < b->nr; i++) {\n-\t\tstruct patch_util *util = b->items[i].util, *prev;\n+\tint i = 0, j = 0;\n+\n+\t/*\n+\t * We assume the user is really more interested in the second argument\n+\t * (\"newer\" version). To that end, we print the output in the order of\n+\t * the RHS (the `b` parameter). To put the LHS (the `a` parameter)\n+\t * commits that are no longer in the RHS into a good place, we place\n+\t * them once we have shown all of their predecessors in the LHS.\n+\t */\n+\n+\twhile (i < a->nr || j < b->nr) {\n+\t\tstruct patch_util *a_util, *b_util;\n+\t\ta_util = i < a->nr ? a->items[i].util : NULL;\n+\t\tb_util = j < b->nr ? b->items[j].util : NULL;\n+\n+\t\t/* Skip all the already-shown commits from the LHS. */\n+\t\twhile (i < a->nr && a_util->shown)\n+\t\t\ta_util = ++i < a->nr ? a->items[i].util : NULL;\n+\n+\t\t/* Show unmatched LHS commit whose predecessors were shown. */\n+\t\tif (i < a->nr && a_util->matching < 0) {\n+\t\t\tprintf(\"%d: %s < -: --------\\n\",\n+\t\t\t       i + 1, short_oid(a_util));\n+\t\t\ti++;\n+\t\t\tcontinue;\n+\t\t}\n \n-\t\tif (util->matching < 0)\n+\t\t/* Show unmatched RHS commits. */\n+\t\twhile (j < b->nr && b_util->matching < 0) {\n \t\t\tprintf(\"-: -------- > %d: %s\\n\",\n-\t\t\t\t\ti + 1, short_oid(util));\n-\t\telse {\n-\t\t\tprev = a->items[util->matching].util;\n-\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n-\t\t\t       util->matching + 1, short_oid(prev),\n-\t\t\t       i + 1, short_oid(util));\n+\t\t\t       j + 1, short_oid(b_util));\n+\t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n-\t}\n-\n-\tfor (i = 0; i < a->nr; i++) {\n-\t\tstruct patch_util *util = a->items[i].util;\n \n-\t\tif (util->matching < 0)\n-\t\t\tprintf(\"%d: %s < -: --------\\n\",\n-\t\t\t       i + 1, short_oid(util));\n+\t\t/* Show matching LHS/RHS pair. */\n+\t\tif (j < b->nr) {\n+\t\t\ta_util = a->items[b_util->matching].util;\n+\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n+\t\t\t       b_util->matching + 1, short_oid(a_util),\n+\t\t\t       j + 1, short_oid(b_util));\n+\t\t\ta_util->shown = 1;\n+\t\t\tj++;\n+\t\t}\n \t}\n }\n \n-- \ngitgitgadget\n\n"},{"id":"351590","messageId":"93ac1931dc6f1ed8b11f0ab38bdd108ae833e5ce.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 06/20] range-diff: right-trim commit messages","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-05-02T14:49:09Z","receivedAt":"2018-05-02T14:49:09Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen comparing commit messages, we need to keep in mind that they are\nindented by four spaces. That is, empty lines are no longer empty, but\nhave \"trailing whitespace\". When displaying them in color, that results\nin those nagging red lines.\n\nLet's just right-trim the lines in the commit message, it's not like\ntrailing white-space in the commit messages are important enough to care\nabout in `git range-diff`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 530f2fc32..8d3b96455 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -85,6 +85,7 @@ static int read_patches(const char *range, struct string_list *list)\n \t\t\t\tstrbuf_addbuf(&buf, &line);\n \t\t\t\tstrbuf_addstr(&buf, \"\\n\\n\");\n \t\t\t} else if (starts_with(line.buf, \"    \")) {\n+\t\t\t\tstrbuf_rtrim(&line);\n \t\t\t\tstrbuf_addbuf(&buf, &line);\n \t\t\t\tstrbuf_addch(&buf, '\\n');\n \t\t\t}\n-- \ngitgitgadget\n\n"},{"id":"351600","messageId":"ca5282815fa3f733c298dd412a61a585e441f278.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 07/20] range-diff: indent the diffs just like tbdiff","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-05-02T14:52:21Z","receivedAt":"2018-05-02T14:52:21Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe main information in the `range-diff` view comes from the list of\nmatching and non-matching commits, the diffs are additional information.\nIndenting them helps with the reading flow.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/range-diff.c | 10 ++++++++++\n 1 file changed, 10 insertions(+)\n\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 5f12bbfa9..660e1f961 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -11,6 +11,11 @@ N_(\"git range-diff [<options>] <base> <old-tip> <new-tip>\"),\n NULL\n };\n \n+static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)\n+{\n+\treturn data;\n+}\n+\n int cmd_range_diff(int argc, const char **argv, const char *prefix)\n {\n \tint creation_factor = 60;\n@@ -21,12 +26,16 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \tint i, j, res = 0;\n+\tstruct strbuf four_spaces = STRBUF_INIT;\n \tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n \n \tgit_config(git_diff_ui_config, NULL);\n \n \tdiff_setup(&diffopt);\n \tdiffopt.output_format = DIFF_FORMAT_PATCH;\n+\tdiffopt.output_prefix = output_prefix_cb;\n+\tstrbuf_addstr(&four_spaces, \"    \");\n+\tdiffopt.output_prefix_data = &four_spaces;\n \n \targc = parse_options(argc, argv, NULL, options,\n \t\t\tbuiltin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n@@ -78,6 +87,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \n \tstrbuf_release(&range1);\n \tstrbuf_release(&range2);\n+\tstrbuf_release(&four_spaces);\n \n \treturn res;\n }\n-- \ngitgitgadget\n\n"},{"id":"351588","messageId":"80622685f9bcc81bb93ad60d27b05c2d6c031e4b.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 08/20] range-diff: suppress the diff headers","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-05-02T14:53:50Z","receivedAt":"2018-05-02T14:53:50Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen showing the diff between corresponding patches of the two branch\nversions, we have to make up a fake filename to run the diff machinery.\n\nThat filename does not carry any meaningful information, hence tbdiff\nsuppresses it. So we should, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/range-diff.c | 1 +\n diff.c               | 5 ++++-\n diff.h               | 1 +\n 3 files changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 660e1f961..7c0967220 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -33,6 +33,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \n \tdiff_setup(&diffopt);\n \tdiffopt.output_format = DIFF_FORMAT_PATCH;\n+\tdiffopt.flags.suppress_diff_headers = 1;\n \tdiffopt.output_prefix = output_prefix_cb;\n \tstrbuf_addstr(&four_spaces, \"    \");\n \tdiffopt.output_prefix_data = &four_spaces;\ndiff --git a/diff.c b/diff.c\nindex 639eb646b..8c568cbe0 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -3189,13 +3189,16 @@ static void builtin_diff(const char *name_a,\n \t\tmemset(&xpp, 0, sizeof(xpp));\n \t\tmemset(&xecfg, 0, sizeof(xecfg));\n \t\tmemset(&ecbdata, 0, sizeof(ecbdata));\n+\t\tif (o->flags.suppress_diff_headers)\n+\t\t\tlbl[0] = NULL;\n \t\tecbdata.label_path = lbl;\n \t\tecbdata.color_diff = want_color(o->use_color);\n \t\tecbdata.ws_rule = whitespace_rule(name_b);\n \t\tif (ecbdata.ws_rule & WS_BLANK_AT_EOF)\n \t\t\tcheck_blank_at_eof(&mf1, &mf2, &ecbdata);\n \t\tecbdata.opt = o;\n-\t\tecbdata.header = header.len ? &header : NULL;\n+\t\tif (header.len && !o->flags.suppress_diff_headers)\n+\t\t\tecbdata.header = &header;\n \t\txpp.flags = o->xdl_opts;\n \t\txpp.anchors = o->anchors;\n \t\txpp.anchors_nr = o->anchors_nr;\ndiff --git a/diff.h b/diff.h\nindex dedac472c..928f48995 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -94,6 +94,7 @@ struct diff_flags {\n \tunsigned funccontext:1;\n \tunsigned default_follow_renames:1;\n \tunsigned stat_with_summary:1;\n+\tunsigned suppress_diff_headers:1;\n };\n \n static inline void diff_flags_or(struct diff_flags *a,\n-- \ngitgitgadget\n\n"},{"id":"351591","messageId":"3d9e5b0ba383bab3a30b74a96a1d78557e168b7f.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 11/20] range-diff: add tests","fromName":"Thomas Rast via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-05-02T15:19:37Z","receivedAt":"2018-05-02T15:19:37Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"From: Thomas Rast <tr@thomasrast.ch>\n\nThese are essentially lifted from https://github.com/trast/tbdiff, with\nlight touch-ups to account for the command now being an option of `git\nbranch`.\n\nApart from renaming `tbdiff` to `range-diff`, only one test case needed\nto be adjusted: 11 - 'changed message'.\n\nThe underlying reason it had to be adjusted is that diff generation is\nsometimes ambiguous. In this case, a comment line and an empty line are\nadded, but it is ambiguous whether they were added after the existing\nempty line, or whether an empty line and the comment line are added\n*before* the existing empty line. And apparently xdiff picks a different\noption here than Python's difflib.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/.gitattributes       |   1 +\n t/t3206-range-diff.sh  | 145 ++++++++++\n t/t3206/history.export | 604 +++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 750 insertions(+)\n create mode 100755 t/t3206-range-diff.sh\n create mode 100644 t/t3206/history.export\n\ndiff --git a/t/.gitattributes b/t/.gitattributes\nindex 3bd959ae5..af15d5aee 100644\n--- a/t/.gitattributes\n+++ b/t/.gitattributes\n@@ -18,5 +18,6 @@ t[0-9][0-9][0-9][0-9]/* -whitespace\n /t5515/* eol=lf\n /t556x_common eol=lf\n /t7500/* eol=lf\n+/t7910/* eol=lf\n /t8005/*.txt eol=lf\n /t9*/*.dump eol=lf\ndiff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh\nnew file mode 100755\nindex 000000000..2237c7f4a\n--- /dev/null\n+++ b/t/t3206-range-diff.sh\n@@ -0,0 +1,145 @@\n+#!/bin/sh\n+\n+test_description='range-diff tests'\n+\n+. ./test-lib.sh\n+\n+# Note that because of the range-diff's heuristics, test_commit does more\n+# harm than good.  We need some real history.\n+\n+test_expect_success 'setup' '\n+\tgit fast-import < \"$TEST_DIRECTORY\"/t3206/history.export\n+'\n+\n+test_expect_success 'simple A..B A..C (unmodified)' '\n+\tgit range-diff --no-color master..topic master..unmodified \\\n+\t\t>actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  35b9b25 s/5/A/\n+\t2:  fccce22 = 2:  de345ab s/4/A/\n+\t3:  147e64e = 3:  9af6654 s/11/B/\n+\t4:  a63e992 = 4:  2901f77 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'simple B...C (unmodified)' '\n+\tgit range-diff --no-color topic...unmodified >actual &&\n+\t# same \"expected\" as above\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'simple A B C (unmodified)' '\n+\tgit range-diff --no-color master topic unmodified >actual &&\n+\t# same \"expected\" as above\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'trivial reordering' '\n+\tgit range-diff --no-color master topic reordered >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  aca177a s/5/A/\n+\t3:  147e64e = 2:  14ad629 s/11/B/\n+\t4:  a63e992 = 3:  ee58208 s/12/B/\n+\t2:  fccce22 = 4:  307b27a s/4/A/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'removed a commit' '\n+\tgit range-diff --no-color master topic removed >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  7657159 s/5/A/\n+\t2:  fccce22 < -:  ------- s/4/A/\n+\t3:  147e64e = 2:  43d84d3 s/11/B/\n+\t4:  a63e992 = 3:  a740396 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'added a commit' '\n+\tgit range-diff --no-color master topic added >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  2716022 s/5/A/\n+\t2:  fccce22 = 2:  b62accd s/4/A/\n+\t-:  ------- > 3:  df46cfa s/6/A/\n+\t3:  147e64e = 4:  3e64548 s/11/B/\n+\t4:  a63e992 = 5:  12b4063 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'new base, A B C' '\n+\tgit range-diff --no-color master topic rebased >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  cc9c443 s/5/A/\n+\t2:  fccce22 = 2:  c5d9641 s/4/A/\n+\t3:  147e64e = 3:  28cc2b6 s/11/B/\n+\t4:  a63e992 = 4:  5628ab7 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'new base, B...C' '\n+\t# this syntax includes the commits from master!\n+\tgit range-diff --no-color topic...rebased >actual &&\n+\tcat >expected <<-EOF &&\n+\t-:  ------- > 1:  a31b12e unrelated\n+\t1:  4de457d = 2:  cc9c443 s/5/A/\n+\t2:  fccce22 = 3:  c5d9641 s/4/A/\n+\t3:  147e64e = 4:  28cc2b6 s/11/B/\n+\t4:  a63e992 = 5:  5628ab7 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'changed commit' '\n+\tgit range-diff --no-color topic...changed >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  a4b3333 s/5/A/\n+\t2:  fccce22 = 2:  f51d370 s/4/A/\n+\t3:  147e64e ! 3:  0559556 s/11/B/\n+\t    @@ -10,7 +10,7 @@\n+\t      9\n+\t      10\n+\t     -11\n+\t    -+B\n+\t    ++BB\n+\t      12\n+\t      13\n+\t      14\n+\t4:  a63e992 ! 4:  d966c5c s/12/B/\n+\t    @@ -8,7 +8,7 @@\n+\t     @@\n+\t      9\n+\t      10\n+\t    - B\n+\t    + BB\n+\t     -12\n+\t     +B\n+\t      13\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'changed message' '\n+\tgit range-diff --no-color topic...changed-message >actual &&\n+\tsed s/Z/\\ /g >expected <<-EOF &&\n+\t1:  4de457d = 1:  f686024 s/5/A/\n+\t2:  fccce22 ! 2:  4ab067d s/4/A/\n+\t    @@ -2,6 +2,8 @@\n+\t    Z\n+\t    Z    s/4/A/\n+\t    Z\n+\t    +    Also a silly comment here!\n+\t    +\n+\t    Zdiff --git a/file b/file\n+\t    Z--- a/file\n+\t    Z+++ b/file\n+\t3:  147e64e = 3:  b9cb956 s/11/B/\n+\t4:  a63e992 = 4:  8add5f1 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_done\ndiff --git a/t/t3206/history.export b/t/t3206/history.export\nnew file mode 100644\nindex 000000000..b8ffff094\n--- /dev/null\n+++ b/t/t3206/history.export\n@@ -0,0 +1,604 @@\n+blob\n+mark :1\n+data 51\n+1\n+2\n+3\n+4\n+5\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+reset refs/heads/removed\n+commit refs/heads/removed\n+mark :2\n+author Thomas Rast <trast@inf.ethz.ch> 1374424921 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374484724 +0200\n+data 8\n+initial\n+M 100644 :1 file\n+\n+blob\n+mark :3\n+data 51\n+1\n+2\n+3\n+4\n+A\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :4\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+blob\n+mark :5\n+data 51\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :6\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+data 7\n+s/4/A/\n+from :4\n+M 100644 :5 file\n+\n+blob\n+mark :7\n+data 50\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :8\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+data 8\n+s/11/B/\n+from :6\n+M 100644 :7 file\n+\n+blob\n+mark :9\n+data 49\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :10\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+data 8\n+s/12/B/\n+from :8\n+M 100644 :9 file\n+\n+blob\n+mark :11\n+data 10\n+unrelated\n+\n+commit refs/heads/master\n+mark :12\n+author Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200\n+data 10\n+unrelated\n+from :2\n+M 100644 :11 otherfile\n+\n+commit refs/heads/rebased\n+mark :13\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485137 +0200\n+data 7\n+s/5/A/\n+from :12\n+M 100644 :3 file\n+\n+commit refs/heads/rebased\n+mark :14\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n+data 7\n+s/4/A/\n+from :13\n+M 100644 :5 file\n+\n+commit refs/heads/rebased\n+mark :15\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n+data 8\n+s/11/B/\n+from :14\n+M 100644 :7 file\n+\n+commit refs/heads/rebased\n+mark :16\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n+data 8\n+s/12/B/\n+from :15\n+M 100644 :9 file\n+\n+commit refs/heads/added\n+mark :17\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/added\n+mark :18\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 7\n+s/4/A/\n+from :17\n+M 100644 :5 file\n+\n+blob\n+mark :19\n+data 51\n+1\n+2\n+3\n+A\n+A\n+A\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/added\n+mark :20\n+author Thomas Rast <trast@inf.ethz.ch> 1374485186 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 7\n+s/6/A/\n+from :18\n+M 100644 :19 file\n+\n+blob\n+mark :21\n+data 50\n+1\n+2\n+3\n+A\n+A\n+A\n+7\n+8\n+9\n+10\n+B\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/added\n+mark :22\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 8\n+s/11/B/\n+from :20\n+M 100644 :21 file\n+\n+blob\n+mark :23\n+data 49\n+1\n+2\n+3\n+A\n+A\n+A\n+7\n+8\n+9\n+10\n+B\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/added\n+mark :24\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 8\n+s/12/B/\n+from :22\n+M 100644 :23 file\n+\n+commit refs/heads/reordered\n+mark :25\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+blob\n+mark :26\n+data 50\n+1\n+2\n+3\n+4\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/reordered\n+mark :27\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 8\n+s/11/B/\n+from :25\n+M 100644 :26 file\n+\n+blob\n+mark :28\n+data 49\n+1\n+2\n+3\n+4\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/reordered\n+mark :29\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 8\n+s/12/B/\n+from :27\n+M 100644 :28 file\n+\n+commit refs/heads/reordered\n+mark :30\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 7\n+s/4/A/\n+from :29\n+M 100644 :9 file\n+\n+commit refs/heads/changed\n+mark :31\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/changed\n+mark :32\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 7\n+s/4/A/\n+from :31\n+M 100644 :5 file\n+\n+blob\n+mark :33\n+data 51\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+BB\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/changed\n+mark :34\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 8\n+s/11/B/\n+from :32\n+M 100644 :33 file\n+\n+blob\n+mark :35\n+data 50\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+BB\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/changed\n+mark :36\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 8\n+s/12/B/\n+from :34\n+M 100644 :35 file\n+\n+commit refs/heads/changed-message\n+mark :37\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/changed-message\n+mark :38\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200\n+data 35\n+s/4/A/\n+\n+Also a silly comment here!\n+from :37\n+M 100644 :5 file\n+\n+commit refs/heads/changed-message\n+mark :39\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200\n+data 8\n+s/11/B/\n+from :38\n+M 100644 :7 file\n+\n+commit refs/heads/changed-message\n+mark :40\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200\n+data 8\n+s/12/B/\n+from :39\n+M 100644 :9 file\n+\n+commit refs/heads/unmodified\n+mark :41\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/unmodified\n+mark :42\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200\n+data 7\n+s/4/A/\n+from :41\n+M 100644 :5 file\n+\n+commit refs/heads/unmodified\n+mark :43\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200\n+data 8\n+s/11/B/\n+from :42\n+M 100644 :7 file\n+\n+commit refs/heads/unmodified\n+mark :44\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200\n+data 8\n+s/12/B/\n+from :43\n+M 100644 :9 file\n+\n+commit refs/heads/removed\n+mark :45\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/removed\n+mark :46\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n+data 8\n+s/11/B/\n+from :45\n+M 100644 :26 file\n+\n+commit refs/heads/removed\n+mark :47\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n+data 8\n+s/12/B/\n+from :46\n+M 100644 :28 file\n+\n+reset refs/heads/removed\n+from :47\n+\n-- \ngitgitgadget\n\n"},{"id":"351592","messageId":"6b31cbf72c4752771965de333b3cb6e82cf90b2b.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 09/20] range-diff: adjust the output of the commit pairs","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-05-02T21:35:48Z","receivedAt":"2018-05-02T21:35:48Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis change brings `git range-diff` yet another step closer to\nfeature parity with tbdiff: it now shows the oneline, too, and indicates\nwith `=` when the commits have identical diffs.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 66 +++++++++++++++++++++++++++++++++++++++++++++-------\n 1 file changed, 57 insertions(+), 9 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 8d3b96455..0e0e77106 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -7,6 +7,8 @@\n #include \"xdiff-interface.h\"\n #include \"linear-assignment.h\"\n #include \"diffcore.h\"\n+#include \"commit.h\"\n+#include \"pretty.h\"\n \n struct patch_util {\n \t/* For the search for an exact match */\n@@ -251,9 +253,57 @@ static void get_correspondences(struct string_list *a, struct string_list *b,\n \tfree(b2a);\n }\n \n-static const char *short_oid(struct patch_util *util)\n+static void output_pair_header(struct strbuf *buf,\n+\t\t\t       struct patch_util *a_util,\n+\t\t\t       struct patch_util *b_util)\n {\n-\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n+\tstatic char *dashes;\n+\tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n+\tstruct commit *commit;\n+\n+\tif (!dashes) {\n+\t\tchar *p;\n+\n+\t\tdashes = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV));\n+\t\tfor (p = dashes; *p; p++)\n+\t\t\t*p = '-';\n+\t}\n+\n+\tstrbuf_reset(buf);\n+\tif (!a_util)\n+\t\tstrbuf_addf(buf, \"-:  %s \", dashes);\n+\telse\n+\t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n+\t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n+\n+\tif (!a_util)\n+\t\tstrbuf_addch(buf, '>');\n+\telse if (!b_util)\n+\t\tstrbuf_addch(buf, '<');\n+\telse if (strcmp(a_util->patch, b_util->patch))\n+\t\tstrbuf_addch(buf, '!');\n+\telse\n+\t\tstrbuf_addch(buf, '=');\n+\n+\tif (!b_util)\n+\t\tstrbuf_addf(buf, \" -:  %s\", dashes);\n+\telse\n+\t\tstrbuf_addf(buf, \" %d:  %s\", b_util->i + 1,\n+\t\t\t    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));\n+\n+\tcommit = lookup_commit_reference(oid);\n+\tif (commit) {\n+\t\tconst char *commit_buffer = get_commit_buffer(commit, NULL);\n+\t\tconst char *subject;\n+\n+\t\tfind_commit_subject(commit_buffer, &subject);\n+\t\tstrbuf_addch(buf, ' ');\n+\t\tformat_subject(buf, subject, \" \");\n+\t\tunuse_commit_buffer(commit, commit_buffer);\n+\t}\n+\tstrbuf_addch(buf, '\\n');\n+\n+\tfwrite(buf->buf, buf->len, 1, stdout);\n }\n \n static struct diff_filespec *get_filespec(const char *name, const char *p)\n@@ -282,6 +332,7 @@ static void patch_diff(const char *a, const char *b,\n static void output(struct string_list *a, struct string_list *b,\n \t\t   struct diff_options *diffopt)\n {\n+\tstruct strbuf buf = STRBUF_INIT;\n \tint i = 0, j = 0;\n \n \t/*\n@@ -303,25 +354,21 @@ static void output(struct string_list *a, struct string_list *b,\n \n \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n \t\tif (i < a->nr && a_util->matching < 0) {\n-\t\t\tprintf(\"%d: %s < -: --------\\n\",\n-\t\t\t       i + 1, short_oid(a_util));\n+\t\t\toutput_pair_header(&buf, a_util, NULL);\n \t\t\ti++;\n \t\t\tcontinue;\n \t\t}\n \n \t\t/* Show unmatched RHS commits. */\n \t\twhile (j < b->nr && b_util->matching < 0) {\n-\t\t\tprintf(\"-: -------- > %d: %s\\n\",\n-\t\t\t       j + 1, short_oid(b_util));\n+\t\t\toutput_pair_header(&buf, NULL, b_util);\n \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n \n \t\t/* Show matching LHS/RHS pair. */\n \t\tif (j < b->nr) {\n \t\t\ta_util = a->items[b_util->matching].util;\n-\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n-\t\t\t       b_util->matching + 1, short_oid(a_util),\n-\t\t\t       j + 1, short_oid(b_util));\n+\t\t\toutput_pair_header(&buf, a_util, b_util);\n \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n \t\t\t\t\t   b->items[j].string, diffopt);\n@@ -329,6 +376,7 @@ static void output(struct string_list *a, struct string_list *b,\n \t\t\tj++;\n \t\t}\n \t}\n+\tstrbuf_release(&buf);\n }\n \n int show_range_diff(const char *range1, const char *range2,\n-- \ngitgitgadget\n\n"},{"id":"351597","messageId":"7273cc647971368d087b2bc76d3f44d8ca166123.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 12/20] range-diff: use color for the commit pairs","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-05-02T23:32:19Z","receivedAt":"2018-05-02T23:32:19Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nArguably the most important part of `git range-diff`'s output is the\nlist of commits in the two branches, together with their relationships.\n\nFor that reason, tbdiff introduced color-coding that is pretty\nintuitive, especially for unchanged patches (all dim yellow, like the\nfirst line in `git show`'s output) vs modified patches (old commit is\nred, new commit is green). Let's imitate that color scheme.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 47 ++++++++++++++++++++++++++++++++++-------------\n 1 file changed, 34 insertions(+), 13 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 8df73da4e..870c3680c 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -254,13 +254,19 @@ static void get_correspondences(struct string_list *a, struct string_list *b,\n \tfree(b2a);\n }\n \n-static void output_pair_header(struct strbuf *buf,\n+static void output_pair_header(struct diff_options *diffopt, struct strbuf *buf,\n \t\t\t       struct patch_util *a_util,\n \t\t\t       struct patch_util *b_util)\n {\n \tstatic char *dashes;\n \tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n \tstruct commit *commit;\n+\tchar status;\n+\tconst char *color_reset = diff_get_color_opt(diffopt, DIFF_RESET);\n+\tconst char *color_old = diff_get_color_opt(diffopt, DIFF_FILE_OLD);\n+\tconst char *color_new = diff_get_color_opt(diffopt, DIFF_FILE_NEW);\n+\tconst char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT);\n+\tconst char *color;\n \n \tif (!dashes) {\n \t\tchar *p;\n@@ -270,21 +276,33 @@ static void output_pair_header(struct strbuf *buf,\n \t\t\t*p = '-';\n \t}\n \n+\tif (!b_util) {\n+\t\tcolor = color_old;\n+\t\tstatus = '<';\n+\t} else if (!a_util) {\n+\t\tcolor = color_new;\n+\t\tstatus = '>';\n+\t} else if (strcmp(a_util->patch, b_util->patch)) {\n+\t\tcolor = color_commit;\n+\t\tstatus = '!';\n+\t} else {\n+\t\tcolor = color_commit;\n+\t\tstatus = '=';\n+\t}\n+\n \tstrbuf_reset(buf);\n+\tstrbuf_addstr(buf, status == '!' ? color_old : color);\n \tif (!a_util)\n \t\tstrbuf_addf(buf, \"-:  %s \", dashes);\n \telse\n \t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n \t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n \n-\tif (!a_util)\n-\t\tstrbuf_addch(buf, '>');\n-\telse if (!b_util)\n-\t\tstrbuf_addch(buf, '<');\n-\telse if (strcmp(a_util->patch, b_util->patch))\n-\t\tstrbuf_addch(buf, '!');\n-\telse\n-\t\tstrbuf_addch(buf, '=');\n+\tif (status == '!')\n+\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color);\n+\tstrbuf_addch(buf, status);\n+\tif (status == '!')\n+\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color_new);\n \n \tif (!b_util)\n \t\tstrbuf_addf(buf, \" -:  %s\", dashes);\n@@ -297,12 +315,15 @@ static void output_pair_header(struct strbuf *buf,\n \t\tconst char *commit_buffer = get_commit_buffer(commit, NULL);\n \t\tconst char *subject;\n \n+\t\tif (status == '!')\n+\t\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color);\n+\n \t\tfind_commit_subject(commit_buffer, &subject);\n \t\tstrbuf_addch(buf, ' ');\n \t\tformat_subject(buf, subject, \" \");\n \t\tunuse_commit_buffer(commit, commit_buffer);\n \t}\n-\tstrbuf_addch(buf, '\\n');\n+\tstrbuf_addf(buf, \"%s\\n\", color_reset);\n \n \tfwrite(buf->buf, buf->len, 1, stdout);\n }\n@@ -360,21 +381,21 @@ static void output(struct string_list *a, struct string_list *b,\n \n \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n \t\tif (i < a->nr && a_util->matching < 0) {\n-\t\t\toutput_pair_header(&buf, a_util, NULL);\n+\t\t\toutput_pair_header(diffopt, &buf, a_util, NULL);\n \t\t\ti++;\n \t\t\tcontinue;\n \t\t}\n \n \t\t/* Show unmatched RHS commits. */\n \t\twhile (j < b->nr && b_util->matching < 0) {\n-\t\t\toutput_pair_header(&buf, NULL, b_util);\n+\t\t\toutput_pair_header(diffopt, &buf, NULL, b_util);\n \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n \n \t\t/* Show matching LHS/RHS pair. */\n \t\tif (j < b->nr) {\n \t\t\ta_util = a->items[b_util->matching].util;\n-\t\t\toutput_pair_header(&buf, a_util, b_util);\n+\t\t\toutput_pair_header(diffopt, &buf, a_util, b_util);\n \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n \t\t\t\t\t   b->items[j].string, diffopt);\n-- \ngitgitgadget\n\n"},{"id":"351599","messageId":"96a3073fb3499fa3f87606a678f5d3e6c9710ccc.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 13/20] color: add the meta color GIT_COLOR_REVERSE","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-05-03T00:14:31Z","receivedAt":"2018-05-03T00:14:31Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis \"color\" simply reverts background and foreground. It will be used\nin the upcoming \"dual color\" mode of `git range-diff`, where we will\nreverse colors for the -/+ markers and the fragment headers of the\n\"outer\" diff.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n color.h | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/color.h b/color.h\nindex 5b744e1bc..33e786342 100644\n--- a/color.h\n+++ b/color.h\n@@ -44,6 +44,7 @@ struct strbuf;\n #define GIT_COLOR_BG_CYAN\t\"\\033[46m\"\n #define GIT_COLOR_FAINT\t\t\"\\033[2m\"\n #define GIT_COLOR_FAINT_ITALIC\t\"\\033[2;3m\"\n+#define GIT_COLOR_REVERSE\t\"\\033[7m\"\n \n /* A special value meaning \"no color selected\" */\n #define GIT_COLOR_NIL \"NIL\"\n-- \ngitgitgadget\n\n"},{"id":"351601","messageId":"6be4baf60b0376e77ec164cedea2b58fc21d16ee.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 14/20] diff: add an internal option to dual-color diffs of diffs","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-05-03T00:17:02Z","receivedAt":"2018-05-03T00:17:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen diffing diffs, it can be quite daunting to figure out what the heck\nis going on, as there are nested +/- signs.\n\nLet's make this easier by adding a flag in diff_options that allows\ncolor-coding the outer diff sign with inverted colors, so that the\npreimage and postimage is colored like the diff it is.\n\nOf course, this really only makes sense when the preimage and postimage\n*are* diffs. So let's not expose this flag via a command-line option for\nnow.\n\nThis is a feature that was invented by git-tbdiff, and it will be used\nby `git range-diff` in the next commit, by offering it via a new option:\n`--dual-color`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n diff.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++-----------\n diff.h |  1 +\n 2 files changed, 69 insertions(+), 15 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 8c568cbe0..26445ffa1 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -562,14 +562,18 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,\n \tecbdata->blank_at_eof_in_postimage = (at - l2) + 1;\n }\n \n-static void emit_line_0(struct diff_options *o, const char *set, const char *reset,\n+static void emit_line_0(struct diff_options *o,\n+\t\t\tconst char *set, unsigned reverse, const char *reset,\n \t\t\tint first, const char *line, int len)\n {\n \tint has_trailing_newline, has_trailing_carriage_return;\n \tint nofirst;\n \tFILE *file = o->file;\n \n-\tfputs(diff_line_prefix(o), file);\n+\tif (first)\n+\t\tfputs(diff_line_prefix(o), file);\n+\telse if (!len)\n+\t\treturn;\n \n \tif (len == 0) {\n \t\thas_trailing_newline = (first == '\\n');\n@@ -587,8 +591,10 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res\n \t}\n \n \tif (len || !nofirst) {\n+\t\tif (reverse && want_color(o->use_color))\n+\t\t\tfputs(GIT_COLOR_REVERSE, file);\n \t\tfputs(set, file);\n-\t\tif (!nofirst)\n+\t\tif (first && !nofirst)\n \t\t\tfputc(first, file);\n \t\tfwrite(line, len, 1, file);\n \t\tfputs(reset, file);\n@@ -602,7 +608,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res\n static void emit_line(struct diff_options *o, const char *set, const char *reset,\n \t\t      const char *line, int len)\n {\n-\temit_line_0(o, set, reset, line[0], line+1, len-1);\n+\temit_line_0(o, set, 0, reset, line[0], line+1, len-1);\n }\n \n enum diff_symbol {\n@@ -962,7 +968,8 @@ static void dim_moved_lines(struct diff_options *o)\n \n static void emit_line_ws_markup(struct diff_options *o,\n \t\t\t\tconst char *set, const char *reset,\n-\t\t\t\tconst char *line, int len, char sign,\n+\t\t\t\tconst char *line, int len,\n+\t\t\t\tconst char *set_sign, char sign,\n \t\t\t\tunsigned ws_rule, int blank_at_eof)\n {\n \tconst char *ws = NULL;\n@@ -973,14 +980,20 @@ static void emit_line_ws_markup(struct diff_options *o,\n \t\t\tws = NULL;\n \t}\n \n-\tif (!ws)\n-\t\temit_line_0(o, set, reset, sign, line, len);\n-\telse if (blank_at_eof)\n+\tif (!ws && !set_sign)\n+\t\temit_line_0(o, set, 0, reset, sign, line, len);\n+\telse if (!ws) {\n+\t\t/* Emit just the prefix, then the rest. */\n+\t\temit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n+\t\t\t    sign, \"\", 0);\n+\t\temit_line_0(o, set, 0, reset, 0, line, len);\n+\t} else if (blank_at_eof)\n \t\t/* Blank line at EOF - paint '+' as well */\n-\t\temit_line_0(o, ws, reset, sign, line, len);\n+\t\temit_line_0(o, ws, 0, reset, sign, line, len);\n \telse {\n \t\t/* Emit just the prefix, then the rest. */\n-\t\temit_line_0(o, set, reset, sign, \"\", 0);\n+\t\temit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n+\t\t\t    sign, \"\", 0);\n \t\tws_check_emit(line, len, ws_rule,\n \t\t\t      o->file, set, reset, ws);\n \t}\n@@ -990,7 +1003,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\t\t\t struct emitted_diff_symbol *eds)\n {\n \tstatic const char *nneof = \" No newline at end of file\\n\";\n-\tconst char *context, *reset, *set, *meta, *fraginfo;\n+\tconst char *context, *reset, *set, *set_sign, *meta, *fraginfo;\n \tstruct strbuf sb = STRBUF_INIT;\n \n \tenum diff_symbol s = eds->s;\n@@ -1003,7 +1016,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\tcontext = diff_get_color_opt(o, DIFF_CONTEXT);\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n \t\tputc('\\n', o->file);\n-\t\temit_line_0(o, context, reset, '\\\\',\n+\t\temit_line_0(o, context, 0, reset, '\\\\',\n \t\t\t    nneof, strlen(nneof));\n \t\tbreak;\n \tcase DIFF_SYMBOL_SUBMODULE_HEADER:\n@@ -1030,7 +1043,18 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \tcase DIFF_SYMBOL_CONTEXT:\n \t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n-\t\temit_line_ws_markup(o, set, reset, line, len, ' ',\n+\t\tset_sign = NULL;\n+\t\tif (o->flags.dual_color_diffed_diffs) {\n+\t\t\tchar c = !len ? 0 : line[0];\n+\n+\t\t\tif (c == '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n+\t\t\telse if (c == '@')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n+\t\t\telse if (c == '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n+\t\t}\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',\n \t\t\t\t    flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);\n \t\tbreak;\n \tcase DIFF_SYMBOL_PLUS:\n@@ -1057,7 +1081,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n \t\t}\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n-\t\temit_line_ws_markup(o, set, reset, line, len, '+',\n+\t\tif (!o->flags.dual_color_diffed_diffs)\n+\t\t\tset_sign = NULL;\n+\t\telse {\n+\t\t\tchar c = !len ? 0 : line[0];\n+\n+\t\t\tset_sign = set;\n+\t\t\tif (c == '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n+\t\t\telse if (c == '@')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n+\t\t\telse if (c != '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t}\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK,\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);\n \t\tbreak;\n@@ -1085,7 +1122,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n \t\t}\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n-\t\temit_line_ws_markup(o, set, reset, line, len, '-',\n+\t\tif (!o->flags.dual_color_diffed_diffs)\n+\t\t\tset_sign = NULL;\n+\t\telse {\n+\t\t\tchar c = !len ? 0 : line[0];\n+\n+\t\t\tset_sign = set;\n+\t\t\tif (c == '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n+\t\t\telse if (c == '@')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n+\t\t\telse if (c != '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t}\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '-',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);\n \t\tbreak;\n \tcase DIFF_SYMBOL_WORDS_PORCELAIN:\n@@ -1276,6 +1326,7 @@ static void emit_hunk_header(struct emit_callback *ecbdata,\n \tconst char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);\n \tconst char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);\n \tconst char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);\n+\tconst char *reverse = ecbdata->color_diff ? GIT_COLOR_REVERSE : \"\";\n \tstatic const char atat[2] = { '@', '@' };\n \tconst char *cp, *ep;\n \tstruct strbuf msgbuf = STRBUF_INIT;\n@@ -1296,6 +1347,8 @@ static void emit_hunk_header(struct emit_callback *ecbdata,\n \tep += 2; /* skip over @@ */\n \n \t/* The hunk header in fraginfo color */\n+\tif (ecbdata->opt->flags.dual_color_diffed_diffs)\n+\t\tstrbuf_addstr(&msgbuf, reverse);\n \tstrbuf_addstr(&msgbuf, frag);\n \tstrbuf_add(&msgbuf, line, ep - line);\n \tstrbuf_addstr(&msgbuf, reset);\ndiff --git a/diff.h b/diff.h\nindex 928f48995..79beb6eea 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -95,6 +95,7 @@ struct diff_flags {\n \tunsigned default_follow_renames:1;\n \tunsigned stat_with_summary:1;\n \tunsigned suppress_diff_headers:1;\n+\tunsigned dual_color_diffed_diffs:1;\n };\n \n static inline void diff_flags_or(struct diff_flags *a,\n-- \ngitgitgadget\n\n"},{"id":"351602","messageId":"02e13c0c6836bb0d16e0241df5ae8907b6fb4c4d.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 15/20] range-diff: offer to dual-color the diffs","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-05-03T01:01:00Z","receivedAt":"2018-05-03T01:01:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen showing what changed between old and new commits, we show a diff of\nthe patches. This diff is a diff between diffs, therefore there are\nnested +/- signs, and it can be relatively hard to understand what is\ngoing on.\n\nWith the --dual-color option, the preimage and the postimage are colored\nlike the diffs they are, and the *outer* +/- sign is inverted for\nclarity.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/range-diff.c | 8 ++++++++\n 1 file changed, 8 insertions(+)\n\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 7c0967220..e8f7fe452 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -20,9 +20,12 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n {\n \tint creation_factor = 60;\n \tstruct diff_options diffopt = { NULL };\n+\tint dual_color = 0;\n \tstruct option options[] = {\n \t\tOPT_INTEGER(0, \"creation-factor\", &creation_factor,\n \t\t\t    N_(\"Percentage by which creation is weighted\")),\n+\t\tOPT_BOOL(0, \"dual-color\", &dual_color,\n+\t\t\t    N_(\"color both diff and diff-between-diffs\")),\n \t\tOPT_END()\n \t};\n \tint i, j, res = 0;\n@@ -50,6 +53,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \targc = j;\n \tdiff_setup_done(&diffopt);\n \n+\tif (dual_color) {\n+\t\tdiffopt.use_color = 1;\n+\t\tdiffopt.flags.dual_color_diffed_diffs = 1;\n+\t}\n+\n \tif (argc == 2) {\n \t\tif (!strstr(argv[0], \"..\"))\n \t\t\twarning(_(\"no .. in range: '%s'\"), argv[0]);\n-- \ngitgitgadget\n\n"},{"id":"351593","messageId":"dfa7b1e71f7a39dfa608e1e205579d3b95d8a34f.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 16/20] range-diff --dual-color: work around bogus white-space warning","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-05-03T01:11:58Z","receivedAt":"2018-05-03T01:11:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen displaying a diff of diffs, it is possible that there is an outer\n`+` before a context line. That happens when the context changed between\nold and new commit. When that context line starts with a tab (after the\nspace that marks it as context line), our diff machinery spits out a\nwhite-space error (space before tab), but in this case, that is\nincorrect.\n\nWork around this by detecting that situation and simply *not* printing\nthe space in that case.\n\nThis is slightly improper a fix because it is conceivable that an\noutput_prefix might be configured with *just* the right length to let\nthat tab jump to a different tab stop depending whether we emit that\nspace or not.\n\nHowever, the proper fix would be relatively ugly and intrusive because\nit would have to *weaken* the WS_SPACE_BEFORE_TAB option in ws.c.\nBesides, we do not expose the --dual-color option in cases other than\nthe `git range-diff` command, which only uses a hard-coded\noutput_prefix of four spaces (which misses the problem by one\ncolumn... ;-)).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n diff.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/diff.c b/diff.c\nindex 26445ffa1..325007167 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -1093,6 +1093,12 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n \t\t\telse if (c != '+')\n \t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t\t/* Avoid space-before-tab warning */\n+\t\t\tif (c == ' ' && (len < 2 || line[1] == '\\t' ||\n+\t\t\t\t\t line[1] == '\\r' || line[1] == '\\n')) {\n+\t\t\t\tline++;\n+\t\t\t\tlen--;\n+\t\t\t}\n \t\t}\n \t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK,\n-- \ngitgitgadget\n\n"},{"id":"351598","messageId":"799da25ef35d2b23dc0df1e6af0772e634f39f19.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 17/20] range-diff: add a man page","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-05-03T13:50:53Z","receivedAt":"2018-05-03T13:50:53Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe bulk of this patch consists of a heavily butchered version of\ntbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from\nhttps://github.com/trast/tbdiff.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-range-diff.txt | 235 +++++++++++++++++++++++++++++++\n 1 file changed, 235 insertions(+)\n create mode 100644 Documentation/git-range-diff.txt\n\ndiff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\nnew file mode 100644\nindex 000000000..189236cc6\n--- /dev/null\n+++ b/Documentation/git-range-diff.txt\n@@ -0,0 +1,235 @@\n+git-range-diff(1)\n+==================\n+\n+NAME\n+----\n+git-range-diff - Compare two commit ranges (e.g. two versions of a branch)\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]\n+\t[--dual-color] [--creation-factor=<factor>]\n+\t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n+\n+DESCRIPTION\n+-----------\n+\n+This command shows the differences between two versions of a patch\n+series, or more generally, two commit ranges (ignoring merges).\n+\n+To that end, it first finds pairs of commits from both commit ranges\n+that correspond with each other. Two commits are said to correspond when\n+the diff between their patches (i.e. the author information, the commit\n+message and the commit diff) is reasonably small compared to the\n+patches' size. See ``Algorithm` below for details.\n+\n+Finally, the list of matching commits is shown in the order of the\n+second commit range, with unmatched commits being inserted just after\n+all of their ancestors have been shown.\n+\n+\n+OPTIONS\n+-------\n+--dual-color::\n+\tWhen the commit diffs differ, recreate the original diffs'\n+\tcoloring, and add outer -/+ diff markers with the *background*\n+\tbeing red/green to make it easier to see e.g. when there was a\n+\tchange in what exact lines were added.\n+\n+--creation-factor=<percent>::\n+\tSet the creation/deletion cost fudge factor to `<percent>`.\n+\tDefaults to 60. Try a larger value if `git range-diff` erroneously\n+\tconsiders a large change a total rewrite (deletion of one commit\n+\tand addition of another), and a smaller one in the reverse case.\n+\tSee the ``Algorithm`` section below for an explanation why this is\n+\tneeded.\n+\n+<range1> <range2>::\n+\tCompare the commits specified by the two ranges, where\n+\t`<range1>` is considered an older version of `<range2>`.\n+\n+<rev1>...<rev2>::\n+\tEquivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.\n+\n+<base> <rev1> <rev2>::\n+\tEquivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.\n+\tNote that `<base>` does not need to be the exact branch point\n+\tof the branches. Example: after rebasing a branch `my-topic`,\n+\t`git range-diff my-topic@{u} my-topic@{1} my-topic` would\n+\tshow the differences introduced by the rebase.\n+\n+`git range-diff` also accepts the regular diff options (see\n+linkgit:git-diff[1]), most notably the `--color=[<when>]` and\n+`--no-color` options. These options are used when generating the \"diff\n+between patches\", i.e. to compare the author, commit message and diff of\n+corresponding old/new commits. There is currently no means to tweak the\n+diff options passed to `git log` when generating those patches.\n+\n+\n+CONFIGURATION\n+-------------\n+This command uses the `diff.color.*` and `pager.range-diff` settings\n+(the latter is on by default).\n+See linkgit:git-config[1].\n+\n+\n+EXAMPLES\n+--------\n+\n+When a rebase required merge conflicts to be resolved, compare the changes\n+introduced by the rebase directly afterwards using:\n+\n+------------\n+$ git range-diff @{u} @{1} @\n+------------\n+\n+\n+A typical output of `git range-diff` would look like this:\n+\n+------------\n+-:  ------- > 1:  0ddba11 Prepare for the inevitable!\n+1:  c0debee = 2:  cab005e Add a helpful message at the start\n+2:  f00dbal ! 3:  decafe1 Describe a bug\n+    @@ -1,3 +1,3 @@\n+     Author: A U Thor <author@example.com>\n+\n+    -TODO: Describe a bug\n+    +Describe a bug\n+    @@ -324,5 +324,6\n+      This is expected.\n+\n+    -+What is unexpected is that it will also crash.\n+    ++Unexpectedly, it also crashes. This is a bug, and the jury is\n+    ++still out there how to fix it best. See ticket #314 for details.\n+\n+      Contact\n+3:  bedead < -:  ------- TO-UNDO\n+------------\n+\n+In this example, there are 3 old and 3 new commits, where the developer\n+removed the 3rd, added a new one before the first two, and modified the\n+commit message of the 2nd commit as well its diff.\n+\n+When the output goes to a terminal, it is color-coded by default, just\n+like regular `git diff`'s output. In addition, the first line (adding a\n+commit) is green, the last line (deleting a commit) is red, the second\n+line (with a perfect match) is yellow like the commit header of `git\n+show`'s output, and the third line colors the old commit red, the new\n+one green and the rest like `git show`'s commit header.\n+\n+The color-coded diff is actually a bit hard to read, though, as it\n+colors the entire lines red or green. The line that added \"What is\n+unexpected\" in the old commit, for example, is completely red, even if\n+the intent of the old commit was to add something.\n+\n+To help with that, use the `--dual-color` mode. In this mode, the diff\n+of diffs will retain the original diff colors, and prefix the lines with\n+-/+ markers that have their *background* red or green, to make it more\n+obvious that they describe how the diff itself changed.\n+\n+\n+Algorithm\n+---------\n+\n+The general idea is this: we generate a cost matrix between the commits\n+in both commit ranges, then solve the least-cost assignment.\n+\n+To avoid false positives (e.g. when a patch has been removed, and an\n+unrelated patch has been added between two iterations of the same patch\n+series), the cost matrix is extended to allow for that, by adding\n+fixed-cost entries for wholesale deletes/adds.\n+\n+Example: Let commits `1--2` be the first iteration of a patch series and\n+`A--C` the second iteration. Let's assume that `A` is a cherry-pick of\n+`2,` and `C` is a cherry-pick of `1` but with a small modification (say,\n+a fixed typo). Visualize the commits as a bipartite graph:\n+\n+------------\n+    1            A\n+\n+    2            B\n+\n+\t\t C\n+------------\n+\n+We are looking for a \"best\" explanation of the new series in terms of\n+the old one. We can represent an \"explanation\" as an edge in the graph:\n+\n+\n+------------\n+    1            A\n+\t       /\n+    2 --------'  B\n+\n+\t\t C\n+------------\n+\n+This explanation comes for \"free\" because there was no change. Similarly\n+`C` could be explained using `1`, but that comes at some cost c>0\n+because of the modification:\n+\n+------------\n+    1 ----.      A\n+\t  |    /\n+    2 ----+---'  B\n+\t  |\n+\t  `----- C\n+\t  c>0\n+------------\n+\n+In mathematical terms, what we are looking for is some sort of a minimum\n+cost bipartite matching; `1` is matched to `C` at some cost, etc. The\n+underlying graph is in fact a complete bipartite graph; the cost we\n+associate with every edge is the size of the diff between the two\n+commits' patches. To explain also new commits, we introduce dummy nodes\n+on both sides:\n+\n+------------\n+    1 ----.      A\n+\t  |    /\n+    2 ----+---'  B\n+\t  |\n+    o     `----- C\n+\t  c>0\n+    o            o\n+\n+    o            o\n+------------\n+\n+The cost of an edge `o--C` is the size of `C`'s diff, modified by a\n+fudge factor that should be smaller than 100%. The cost of an edge\n+`o--o` is free. The fudge factor is necessary because even if `1` and\n+`C` have nothing in common, they may still share a few empty lines and\n+such, possibly making the assignment `1--C`, `o--o` slightly cheaper\n+than `1--o`, `o--C` even if `1` and `C` have nothing in common. With the\n+fudge factor we require a much larger common part to consider patches as\n+corresponding.\n+\n+The overall time needed to compute this algorithm is the time needed to\n+compute n+m commit diffs and then n*m diffs of patches, plus the time\n+needed to compute the least-cost assigment between n and m diffs. Git\n+uses an implementation of the Jonker-Volgenant algorithm to solve the\n+assignment problem, which has cubic runtime complexity. The matching\n+found in this case will look like this:\n+\n+------------\n+    1 ----.      A\n+\t  |    /\n+    2 ----+---'  B\n+       .--+-----'\n+    o -'  `----- C\n+\t  c>0\n+    o ---------- o\n+\n+    o ---------- o\n+------------\n+\n+\n+SEE ALSO\n+--------\n+linkgit:git-log[1]\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\n-- \ngitgitgadget\n\n"},{"id":"351594","messageId":"d05b54c603dc68acf198bb8826e3af4f2f11021f.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 18/20] completion: support `git range-diff`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-05-03T14:44:25Z","receivedAt":"2018-05-03T14:44:25Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nTab completion of `git range-diff` is very convenient, especially\ngiven that the revision arguments to specify the commit ranges to\ncompare are typically more complex than, say, your grandfather's `git\nlog` arguments.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nsquash! WIP completion: support `git range-diff`\n\nRevert \"WIP completion: support `git range-diff`\"\n\nThis reverts commit 2e7af652af9e53a19fd947f8ebe37a78043afa49.\n---\n contrib/completion/git-completion.bash | 14 ++++++++++++++\n 1 file changed, 14 insertions(+)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 94c95516e..402490673 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1976,6 +1976,20 @@ _git_push ()\n \t__git_complete_remote_or_refspec\n }\n \n+_git_range_diff ()\n+{\n+  case \"$cur\" in\n+  --*)\n+          __gitcomp \"\n+\t  \t--creation-factor= --dual-color\n+                  $__git_diff_common_options\n+                  \"\n+          return\n+          ;;\n+  esac\n+  __git_complete_revlist\n+}\n+\n _git_rebase ()\n {\n \t__git_find_repo_path\n-- \ngitgitgadget\n\n"},{"id":"346518","messageId":"cover.1525361419.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":null,"subject":"[PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T15:30:20Z","receivedAt":"2018-05-03T15:30:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The incredibly useful `git-tbdiff` tool to compare patch series (say, to see\nwhat changed between two iterations sent to the Git mailing list) is slightly\nless useful for this developer due to the fact that it requires the `hungarian`\nand `numpy` Python packages which are for some reason really hard to build in\nMSYS2. So hard that I even had to give up, because it was simply easier to\nreimplement the whole shebang as a builtin command.\n\nThe project at https://github.com/trast/tbdiff seems to be dormant, anyway.\nFunny (and true) story: I looked at the open Pull Requests to see how active\nthat project is, only to find to my surprise that I had submitted one in August\n2015, and that it was still unanswered let alone merged.\n\nWhile at it, I forward-ported AEvar's patch to force `--decorate=no` because\n`git -p tbdiff` would fail otherwise.\n\n\nJohannes Schindelin (17):\n  Add a function to solve least-cost assignment problems\n  Add a new builtin: branch-diff\n  branch-diff: first rudimentary implementation\n  branch-diff: improve the order of the shown commits\n  branch-diff: also show the diff between patches\n  branch-diff: right-trim commit messages\n  branch-diff: indent the diffs just like tbdiff\n  branch-diff: suppress the diff headers\n  branch-diff: adjust the output of the commit pairs\n  branch-diff: do not show \"function names\" in hunk headers\n  branch-diff: use color for the commit pairs\n  color: provide inverted colors, too\n  diff: add an internal option to dual-color diffs of diffs\n  branch-diff: offer to dual-color the diffs\n  branch-diff --dual-color: work around bogus white-space warning\n  branch-diff: add a man page\n  completion: support branch-diff\n\nThomas Rast (1):\n  branch-diff: add tests\n\n .gitignore                             |   1 +\n Documentation/git-branch-diff.txt      | 239 ++++++++++\n Makefile                               |   2 +\n builtin.h                              |   1 +\n builtin/branch-diff.c                  | 531 ++++++++++++++++++++++\n color.h                                |   6 +\n command-list.txt                       |   1 +\n contrib/completion/git-completion.bash |  18 +\n diff.c                                 |  76 +++-\n diff.h                                 |   6 +-\n git.c                                  |   1 +\n hungarian.c                            | 205 +++++++++\n hungarian.h                            |  19 +\n t/.gitattributes                       |   1 +\n t/t7910-branch-diff.sh                 | 144 ++++++\n t/t7910/history.export                 | 604 +++++++++++++++++++++++++\n 16 files changed, 1843 insertions(+), 12 deletions(-)\n create mode 100644 Documentation/git-branch-diff.txt\n create mode 100644 builtin/branch-diff.c\n create mode 100644 hungarian.c\n create mode 100644 hungarian.h\n create mode 100755 t/t7910-branch-diff.sh\n create mode 100644 t/t7910/history.export\n\n\nbase-commit: 1f1cddd558b54bb0ce19c8ace353fd07b758510d\nPublished-As: https://github.com/dscho/git/releases/tag/branch-diff-v1\nFetch-It-Via: git fetch https://github.com/dscho/git branch-diff-v1\n-- \n2.17.0.395.g6a618d6010f.dirty\n\n"},{"id":"346519","messageId":"3f51970cbc44bfe34133c48c0844ed3723e83808.1525361419.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"[PATCH 01/18] Add a function to solve least-cost assignment problems","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T15:30:26Z","receivedAt":"2018-05-03T15:30:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The Jonker-Volgenant algorithm was implemented to answer questions such\nas: given two different versions of a topic branch (or iterations of a\npatch series), what is the best pairing of commits/patches between the\ndifferent versions?\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Makefile    |   1 +\n hungarian.c | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n hungarian.h |  19 +++++\n 3 files changed, 225 insertions(+)\n create mode 100644 hungarian.c\n create mode 100644 hungarian.h\n\ndiff --git a/Makefile b/Makefile\nindex 50da82b0169..96f2e76a904 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -829,6 +829,7 @@ LIB_OBJS += gpg-interface.o\n LIB_OBJS += graph.o\n LIB_OBJS += grep.o\n LIB_OBJS += hashmap.o\n+LIB_OBJS += hungarian.o\n LIB_OBJS += help.o\n LIB_OBJS += hex.o\n LIB_OBJS += ident.o\ndiff --git a/hungarian.c b/hungarian.c\nnew file mode 100644\nindex 00000000000..346299a97d9\n--- /dev/null\n+++ b/hungarian.c\n@@ -0,0 +1,205 @@\n+/*\n+ * Based on: Jonker, R., & Volgenant, A. (1987). <i>A shortest augmenting path\n+ * algorithm for dense and sparse linear assignment problems</i>. Computing,\n+ * 38(4), 325-340.\n+ */\n+#include \"cache.h\"\n+#include \"hungarian.h\"\n+#include <float.h>\n+\n+#define COST(column, row) cost[(column) + column_count * (row)]\n+\n+/*\n+ * The parameter `cost` is the cost matrix: the cost to assign column j to row\n+ * i is `cost[j + column_count * i].\n+ */\n+int compute_assignment(int column_count, int row_count, double *cost,\n+\t\t       int *column2row, int *row2column)\n+{\n+\tdouble *v = xmalloc(sizeof(double) * column_count), *d;\n+\tint *free_row, free_count = 0, saved_free_count, *pred, *col;\n+\tint i, j, phase;\n+\n+\tmemset(column2row, -1, sizeof(int) * column_count);\n+\tmemset(row2column, -1, sizeof(int) * row_count);\n+\n+\t/* column reduction */\n+\tfor (j = column_count - 1; j >= 0; j--) {\n+\t\tint i1 = 0;\n+\n+\t\tfor (i = 1; i < row_count; i++)\n+\t\t\tif (COST(j, i1) > COST(j, i))\n+\t\t\t\ti1 = i;\n+\t\tv[j] = COST(j, i1);\n+\t\tif (row2column[i1] == -1) {\n+\t\t\t/* row i1 unassigned */\n+\t\t\trow2column[i1] = j;\n+\t\t\tcolumn2row[j] = i1;\n+\t\t} else {\n+\t\t\tif (row2column[i1] >= 0)\n+\t\t\t\trow2column[i1] = -2 - row2column[i1];\n+\t\t\tcolumn2row[j] = -1;\n+\t\t}\n+\t}\n+\n+\t/* reduction transfer */\n+\tfree_row = xmalloc(sizeof(int) * row_count);\n+\tfor (int i = 0; i < row_count; i++) {\n+\t\tint j1 = row2column[i];\n+\t\tif (j1 == -1)\n+\t\t\tfree_row[free_count++] = i;\n+\t\telse if (j1 < -1)\n+\t\t\trow2column[i] = -2 - j1;\n+\t\telse {\n+\t\t\tdouble min = COST(!j1, i) - v[!j1];\n+\t\t\tfor (j = 1; j < column_count; j++)\n+\t\t\t\tif (j != j1 && min > COST(j, i) - v[j])\n+\t\t\t\t\tmin = COST(j, i) - v[j];\n+\t\t\tv[j1] -= min;\n+\t\t}\n+\t}\n+\n+\tif (free_count ==\n+\t    (column_count < row_count ? row_count - column_count : 0)) {\n+\t\tfree(v);\n+\t\tfree(free_row);\n+\t\treturn 0;\n+\t}\n+\n+\t/* augmenting row reduction */\n+\tfor (phase = 0; phase < 2; phase++) {\n+\t\tint k = 0;\n+\n+\t\tsaved_free_count = free_count;\n+\t\tfree_count = 0;\n+\t\twhile (k < saved_free_count) {\n+\t\t\tdouble u1, u2;\n+\t\t\tint j1 = 0, j2, i0;\n+\n+\t\t\ti = free_row[k++];\n+\t\t\tu1 = COST(j1, i) - v[j1];\n+\t\t\tj2 = -1;\n+\t\t\tu2 = DBL_MAX;\n+\t\t\tfor (j = 1; j < column_count; j++) {\n+\t\t\t\tdouble c = COST(j, i) - v[j];\n+\t\t\t\tif (u2 > c) {\n+\t\t\t\t\tif (u1 < c) {\n+\t\t\t\t\t\tu2 = c;\n+\t\t\t\t\t\tj2 = j;\n+\t\t\t\t\t} else {\n+\t\t\t\t\t\tu2 = u1;\n+\t\t\t\t\t\tu1 = c;\n+\t\t\t\t\t\tj2 = j1;\n+\t\t\t\t\t\tj1 = j;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tif (j2 < 0) {\n+\t\t\t\tj2 = j1;\n+\t\t\t\tu2 = u1;\n+\t\t\t}\n+\n+\t\t\ti0 = column2row[j1];\n+\t\t\tif (u1 < u2)\n+\t\t\t\tv[j1] -= u2 - u1;\n+\t\t\telse if (i0 >= 0) {\n+\t\t\t\tj1 = j2;\n+\t\t\t\ti0 = column2row[j1];\n+\t\t\t}\n+\n+\t\t\tif (i0 >= 0) {\n+\t\t\t\tif (u1 < u2)\n+\t\t\t\t\tfree_row[--k] = i0;\n+\t\t\t\telse\n+\t\t\t\t\tfree_row[free_count++] = i0;\n+\t\t\t}\n+\t\t\trow2column[i] = j1;\n+\t\t\tcolumn2row[j1] = i;\n+\t\t}\n+\t}\n+\n+\t/* augmentation */\n+\tsaved_free_count = free_count;\n+\td = xmalloc(sizeof(double) * column_count);\n+\tpred = xmalloc(sizeof(int) * column_count);\n+\tcol = xmalloc(sizeof(int) * column_count);\n+\tfor (free_count = 0; free_count < saved_free_count; free_count++) {\n+\t\tint i1 = free_row[free_count], low = 0, up = 0, last, k;\n+\t\tdouble min, c, u1;\n+\n+\t\tfor (j = 0; j < column_count; j++) {\n+\t\t\td[j] = COST(j, i1) - v[j];\n+\t\t\tpred[j] = i1;\n+\t\t\tcol[j] = j;\n+\t\t}\n+\n+\t\tj = -1;\n+\t\tdo {\n+\t\t\tlast = low;\n+\t\t\tmin = d[col[up++]];\n+\t\t\tfor (k = up; k < column_count; k++) {\n+\t\t\t\tj = col[k];\n+\t\t\t\tc = d[j];\n+\t\t\t\tif (c <= min) {\n+\t\t\t\t\tif (c < min) {\n+\t\t\t\t\t\tup = low;\n+\t\t\t\t\t\tmin = c;\n+\t\t\t\t\t}\n+\t\t\t\t\tcol[k] = col[up];\n+\t\t\t\t\tcol[up++] = j;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tfor (k = low; k < up; k++)\n+\t\t\t\tif (column2row[col[k]] == -1)\n+\t\t\t\t\tgoto update;\n+\n+\t\t\t/* scan a row */\n+\t\t\tdo {\n+\t\t\t\tint j1 = col[low++];\n+\n+\t\t\t\ti = column2row[j1];\n+\t\t\t\tu1 = COST(j1, i) - v[j1] - min;\n+\t\t\t\tfor (k = up; k < column_count; k++) {\n+\t\t\t\t\tj = col[k];\n+\t\t\t\t\tc = COST(j, i) - v[j] - u1;\n+\t\t\t\t\tif (c < d[j]) {\n+\t\t\t\t\t\td[j] = c;\n+\t\t\t\t\t\tpred[j] = i;\n+\t\t\t\t\t\tif (c == min) {\n+\t\t\t\t\t\t\tif (column2row[j] == -1)\n+\t\t\t\t\t\t\t\tgoto update;\n+\t\t\t\t\t\t\tcol[k] = col[up];\n+\t\t\t\t\t\t\tcol[up++] = j;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t} while (low != up);\n+\t\t} while (low == up);\n+\n+update:\n+\t\t/* updating of the column pieces */\n+\t\tfor (k = 0; k < last; k++) {\n+\t\t\tint j1 = col[k];\n+\t\t\tv[j1] += d[j1] - min;\n+\t\t}\n+\n+\t\t/* augmentation */\n+\t\tdo {\n+\t\t\tif (j < 0)\n+\t\t\t\tBUG(\"negative j: %d\", j);\n+\t\t\ti = pred[j];\n+\t\t\tcolumn2row[j] = i;\n+\t\t\tk = j;\n+\t\t\tj = row2column[i];\n+\t\t\trow2column[i] = k;\n+\t\t} while (i1 != i);\n+\t}\n+\n+\tfree(col);\n+\tfree(pred);\n+\tfree(d);\n+\tfree(v);\n+\tfree(free_row);\n+\n+\treturn 0;\n+}\ndiff --git a/hungarian.h b/hungarian.h\nnew file mode 100644\nindex 00000000000..e7cee4bb039\n--- /dev/null\n+++ b/hungarian.h\n@@ -0,0 +1,19 @@\n+#ifndef HUNGARIAN_H\n+#define HUNGARIAN_H\n+\n+/*\n+ * Compute an assignment of columns -> rows (and vice versa) such that every\n+ * column is assigned to at most one row (and vice versa) minimizing the\n+ * overall cost.\n+ *\n+ * The parameter `cost` is the cost matrix: the cost to assign column j to row\n+ * i is `cost[j + column_count * i].\n+ *\n+ * The arrays column2row and row2column will be populated with the respective\n+ * assignments (-1 for unassigned, which can happen only if column_count !=\n+ * row_count).\n+ */\n+int compute_assignment(int column_count, int row_count, double *cost,\n+\t\t       int *column2row, int *row2column);\n+\n+#endif\n-- \n2.17.0.395.g6a618d6010f.dirty\n\n\n"},{"id":"346520","messageId":"8bc517e35d4842f8d9d98f3b99adb9475d6db2d2.1525361419.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"[PATCH 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T15:30:30Z","receivedAt":"2018-05-03T15:30:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This builtin does not do a whole lot so far, apart from showing a usage\nthat is oddly similar to that of `git tbdiff`. And for a good reason:\nthe next commits will turn `branch-diff` into a full-blown replacement\nfor `tbdiff`.\n\nAt this point, we ignore tbdiff's color options, as they will all be\nimplemented later and require some patches to the diff machinery.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n .gitignore            |  1 +\n Makefile              |  1 +\n builtin.h             |  1 +\n builtin/branch-diff.c | 40 ++++++++++++++++++++++++++++++++++++++++\n command-list.txt      |  1 +\n git.c                 |  1 +\n 6 files changed, 45 insertions(+)\n create mode 100644 builtin/branch-diff.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 833ef3b0b78..1346a64492f 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -20,6 +20,7 @@\n /git-bisect--helper\n /git-blame\n /git-branch\n+/git-branch-diff\n /git-bundle\n /git-cat-file\n /git-check-attr\ndiff --git a/Makefile b/Makefile\nindex 96f2e76a904..9b1984776d8 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -953,6 +953,7 @@ BUILTIN_OBJS += builtin/archive.o\n BUILTIN_OBJS += builtin/bisect--helper.o\n BUILTIN_OBJS += builtin/blame.o\n BUILTIN_OBJS += builtin/branch.o\n+BUILTIN_OBJS += builtin/branch-diff.o\n BUILTIN_OBJS += builtin/bundle.o\n BUILTIN_OBJS += builtin/cat-file.o\n BUILTIN_OBJS += builtin/check-attr.o\ndiff --git a/builtin.h b/builtin.h\nindex 42378f3aa47..e1c4d2a529a 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -135,6 +135,7 @@ extern int cmd_archive(int argc, const char **argv, const char *prefix);\n extern int cmd_bisect__helper(int argc, const char **argv, const char *prefix);\n extern int cmd_blame(int argc, const char **argv, const char *prefix);\n extern int cmd_branch(int argc, const char **argv, const char *prefix);\n+extern int cmd_branch_diff(int argc, const char **argv, const char *prefix);\n extern int cmd_bundle(int argc, const char **argv, const char *prefix);\n extern int cmd_cat_file(int argc, const char **argv, const char *prefix);\n extern int cmd_checkout(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nnew file mode 100644\nindex 00000000000..97266cd326d\n--- /dev/null\n+++ b/builtin/branch-diff.c\n@@ -0,0 +1,40 @@\n+#include \"cache.h\"\n+#include \"parse-options.h\"\n+\n+static const char * const builtin_branch_diff_usage[] = {\n+\tN_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n+\tNULL\n+};\n+\n+#define COLOR_DUAL_MODE 2\n+\n+static int parse_creation_weight(const struct option *opt, const char *arg,\n+\t\t\t\t int unset)\n+{\n+\tdouble *d = opt->value;\n+\tif (unset)\n+\t\t*d = 0.6;\n+\telse\n+\t\t*d = atof(arg);\n+\treturn 0;\n+}\n+\n+int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n+{\n+\tint no_patches = 0;\n+\tdouble creation_weight = 0.6;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"no-patches\", &no_patches,\n+\t\t\t N_(\"short format (no diffs)\")),\n+\t\t{ OPTION_CALLBACK,\n+\t\t\t0, \"creation-weight\", &creation_weight, N_(\"factor\"),\n+\t\t\tN_(\"Fudge factor by which creation is weighted [0.6]\"),\n+\t\t\t0, parse_creation_weight },\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, options,\n+\t\t\tbuiltin_branch_diff_usage, 0);\n+\n+\treturn 0;\n+}\ndiff --git a/command-list.txt b/command-list.txt\nindex a1fad28fd82..c89ac8f417f 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -19,6 +19,7 @@ git-archive                             mainporcelain\n git-bisect                              mainporcelain           info\n git-blame                               ancillaryinterrogators\n git-branch                              mainporcelain           history\n+git-branch-diff                         mainporcelain           info\n git-bundle                              mainporcelain\n git-cat-file                            plumbinginterrogators\n git-check-attr                          purehelpers\ndiff --git a/git.c b/git.c\nindex f598fae7b7a..d2794fb6f5d 100644\n--- a/git.c\n+++ b/git.c\n@@ -377,6 +377,7 @@ static struct cmd_struct commands[] = {\n \t{ \"bisect--helper\", cmd_bisect__helper, RUN_SETUP },\n \t{ \"blame\", cmd_blame, RUN_SETUP },\n \t{ \"branch\", cmd_branch, RUN_SETUP | DELAY_PAGER_CONFIG },\n+\t{ \"branch-diff\", cmd_branch_diff, RUN_SETUP | USE_PAGER },\n \t{ \"bundle\", cmd_bundle, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"cat-file\", cmd_cat_file, RUN_SETUP },\n \t{ \"check-attr\", cmd_check_attr, RUN_SETUP },\n-- \n2.17.0.395.g6a618d6010f.dirty\n\n\n"},{"id":"346521","messageId":"ec51c71779a325263c1b705a6b1bfb003fcd528a.1525361419.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"[PATCH 03/18] branch-diff: first rudimentary implementation","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T15:30:31Z","receivedAt":"2018-05-03T15:30:46Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"At this stage, `git branch-diff` can determine corresponding commits of\ntwo related commit ranges. This makes use of the recently introduced\nimplementation of the Hungarian algorithm.\n\nThe core of this patch is a straight port of the ideas of tbdiff, the\nseemingly dormant project at https://github.com/trast/tbdiff.\n\nThe output does not at all match `tbdiff`'s output yet, as this patch\nreally concentrates on getting the patch matching part right.\n\nNote: due to differences in the diff algorithm (`tbdiff` uses the\nPythong module `difflib`, Git uses its xdiff fork), the cost matrix\ncalculated by `branch-diff` is different (but very similar) to the one\ncalculated by `tbdiff`. Therefore, it is possible that they find\ndifferent matching commits in corner cases (e.g. when a patch was split\ninto two patches of roughly equal length).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 337 +++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 334 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex 97266cd326d..02dc06a57ca 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -1,13 +1,17 @@\n #include \"cache.h\"\n #include \"parse-options.h\"\n+#include \"string-list.h\"\n+#include \"run-command.h\"\n+#include \"argv-array.h\"\n+#include \"hashmap.h\"\n+#include \"xdiff-interface.h\"\n+#include \"hungarian.h\"\n \n static const char * const builtin_branch_diff_usage[] = {\n \tN_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n \tNULL\n };\n \n-#define COLOR_DUAL_MODE 2\n-\n static int parse_creation_weight(const struct option *opt, const char *arg,\n \t\t\t\t int unset)\n {\n@@ -19,6 +23,279 @@ static int parse_creation_weight(const struct option *opt, const char *arg,\n \treturn 0;\n }\n \n+struct patch_util {\n+\t/* For the search for an exact match */\n+\tstruct hashmap_entry e;\n+\tconst char *diff, *patch;\n+\n+\tint i;\n+\tint diffsize;\n+\tsize_t diff_offset;\n+\t/* the index of the matching item in the other branch, or -1 */\n+\tint matching;\n+\tstruct object_id oid;\n+};\n+\n+/*\n+ * Reads the patches into a string list, with the `util` field being populated\n+ * as struct object_id (will need to be free()d).\n+ */\n+static int read_patches(const char *range, struct string_list *list)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tFILE *in;\n+\tstruct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;\n+\tstruct patch_util *util = NULL;\n+\tint in_header = 1;\n+\n+\targv_array_pushl(&cp.args, \"log\", \"--no-color\", \"-p\", \"--no-merges\",\n+\t\t\t\"--reverse\", \"--date-order\", \"--decorate=no\",\n+\t\t\t\"--no-abbrev-commit\", range,\n+\t\t\tNULL);\n+\tcp.out = -1;\n+\tcp.no_stdin = 1;\n+\tcp.git_cmd = 1;\n+\n+\tif (start_command(&cp))\n+\t\treturn error_errno(_(\"could not start `log`\"));\n+\tin = fdopen(cp.out, \"r\");\n+\tif (!in) {\n+\t\terror_errno(_(\"could not read `log` output\"));\n+\t\tfinish_command(&cp);\n+\t\treturn -1;\n+\t}\n+\n+\twhile (strbuf_getline(&line, in) != EOF) {\n+\t\tconst char *p;\n+\n+\t\tif (skip_prefix(line.buf, \"commit \", &p)) {\n+\t\t\tif (util) {\n+\t\t\t\tstring_list_append(list, buf.buf)->util = util;\n+\t\t\t\tstrbuf_reset(&buf);\n+\t\t\t}\n+\t\t\tutil = xcalloc(sizeof(*util), 1);\n+\t\t\tif (get_oid(p, &util->oid)) {\n+\t\t\t\terror(_(\"could not parse commit '%s'\"), p);\n+\t\t\t\tfree(util);\n+\t\t\t\tstring_list_clear(list, 1);\n+\t\t\t\tstrbuf_release(&buf);\n+\t\t\t\tstrbuf_release(&line);\n+\t\t\t\tfclose(in);\n+\t\t\t\tfinish_command(&cp);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t\tutil->matching = -1;\n+\t\t\tin_header = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (starts_with(line.buf, \"diff --git\")) {\n+\t\t\tin_header = 0;\n+\t\t\tstrbuf_addch(&buf, '\\n');\n+\t\t\tif (!util->diff_offset)\n+\t\t\t\tutil->diff_offset = buf.len;\n+\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\t} else if (in_header) {\n+\t\t\tif (starts_with(line.buf, \"Author: \")) {\n+\t\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\t\t\tstrbuf_addstr(&buf, \"\\n\\n\");\n+\t\t\t} else if (starts_with(line.buf, \"    \")) {\n+\t\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\t\t\tstrbuf_addch(&buf, '\\n');\n+\t\t\t}\n+\n+\t\t\tcontinue;\n+\t\t} else if (starts_with(line.buf, \"@@ \"))\n+\t\t\tstrbuf_addstr(&buf, \"@@\");\n+\t\telse if (line.buf[0] && !starts_with(line.buf, \"index \"))\n+\t\t\t/*\n+\t\t\t * A completely blank (not ' \\n', which is context)\n+\t\t\t * line is not valid in a diff.  We skip it\n+\t\t\t * silently, because this neatly handles the blank\n+\t\t\t * separator line between commits in git-log\n+\t\t\t * output.\n+\t\t\t */\n+\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\telse\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_addch(&buf, '\\n');\n+\t\tutil->diffsize++;\n+\t}\n+\tfclose(in);\n+\n+\tif (util)\n+\t\tstring_list_append(list, buf.buf)->util = util;\n+\tstrbuf_release(&buf);\n+\n+\tif (finish_command(&cp))\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+static int patch_util_cmp(const void *dummy, const struct patch_util *a,\n+\t\t     const struct patch_util *b, const char *keydata)\n+{\n+\treturn strcmp(a->diff, keydata ? keydata : b->diff);\n+}\n+\n+static void find_exact_matches(struct string_list *a, struct string_list *b)\n+{\n+\tstruct hashmap map;\n+\tint i;\n+\n+\thashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);\n+\n+\t/* First, add the patches of a to a hash map */\n+\tfor (i = 0; i < a->nr; i++) {\n+\t\tstruct patch_util *util = a->items[i].util;\n+\n+\t\tutil->i = i;\n+\t\tutil->patch = a->items[i].string;\n+\t\tutil->diff = util->patch + util->diff_offset;\n+\t\thashmap_entry_init(util, strhash(util->diff));\n+\t\thashmap_add(&map, util);\n+\t}\n+\n+\t/* Now try to find exact matches in b */\n+\tfor (i = 0; i < b->nr; i++) {\n+\t\tstruct patch_util *util = b->items[i].util, *other;\n+\n+\t\tutil->i = i;\n+\t\tutil->patch = b->items[i].string;\n+\t\tutil->diff = util->patch + util->diff_offset;\n+\t\thashmap_entry_init(util, strhash(util->diff));\n+\t\tother = hashmap_remove(&map, util, NULL);\n+\t\tif (other) {\n+\t\t\tif (other->matching >= 0)\n+\t\t\t\tBUG(\"already assigned!\");\n+\n+\t\t\tother->matching = i;\n+\t\t\tutil->matching = other->i;\n+\t\t}\n+\t}\n+\n+\thashmap_free(&map, 0);\n+}\n+\n+static void diffsize_consume(void *data, char *line, unsigned long len)\n+{\n+\t(*(int *)data)++;\n+}\n+\n+static int diffsize(const char *a, const char *b)\n+{\n+\txpparam_t pp = { 0 };\n+\txdemitconf_t cfg = { 0 };\n+\tmmfile_t mf1, mf2;\n+\tint count = 0;\n+\n+\tmf1.ptr = (char *)a;\n+\tmf1.size = strlen(a);\n+\tmf2.ptr = (char *)b;\n+\tmf2.size = strlen(b);\n+\n+\tcfg.ctxlen = 3;\n+\tif (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))\n+\t\treturn count;\n+\n+\terror(_(\"failed to generate diff\"));\n+\treturn INT_MAX;\n+}\n+\n+static int get_correspondences(struct string_list *a, struct string_list *b,\n+\t\t\t       double creation_weight)\n+{\n+\tint n = a->nr + b->nr;\n+\tdouble *cost = xmalloc(sizeof(double) * n * n), c;\n+\tint *a2b = xmalloc(sizeof(int) * n), *b2a = xmalloc(sizeof(int) * n);\n+\tint i, j, res;\n+\n+\tfor (i = 0; i < a->nr; i++) {\n+\t\tstruct patch_util *a_util = a->items[i].util;\n+\n+\t\tfor (j = 0; j < b->nr; j++) {\n+\t\t\tstruct patch_util *b_util = b->items[j].util;\n+\n+\t\t\tif (a_util->matching == j)\n+\t\t\t\tc = 0;\n+\t\t\telse if (a_util->matching < 0 && b_util->matching < 0)\n+\t\t\t\tc = diffsize(a_util->diff, b_util->diff);\n+\t\t\telse\n+\t\t\t\tc = INT_MAX;\n+\t\t\tcost[i + n * j] = c;\n+\t\t}\n+\n+\t\tc = a_util->matching < 0 ?\n+\t\t\ta_util->diffsize * creation_weight : INT_MAX;\n+\t\tfor (j = b->nr; j < n; j++)\n+\t\t\tcost[i + n * j] = c;\n+\t}\n+\n+\tfor (j = 0; j < b->nr; j++) {\n+\t\tstruct patch_util *util = b->items[j].util;\n+\n+\t\tc = util->matching < 0 ?\n+\t\t\tutil->diffsize * creation_weight : INT_MAX;\n+\t\tfor (i = a->nr; i < n; i++)\n+\t\t\tcost[i + n * j] = c;\n+\t}\n+\n+\tfor (i = a->nr; i < n; i++)\n+\t\tfor (j = b->nr; j < n; j++)\n+\t\t\tcost[i + n * j] = 0;\n+\n+\tres = compute_assignment(n, n, cost, a2b, b2a);\n+\n+\tfor (i = 0; i < a->nr; i++)\n+\t\tif (a2b[i] >= 0 && a2b[i] < b->nr) {\n+\t\t\tstruct patch_util *a_util = a->items[i].util;\n+\t\t\tstruct patch_util *b_util = b->items[a2b[i]].util;\n+\n+\t\t\ta_util->matching = a2b[i];\n+\t\t\tb_util->matching = i;\n+\t\t}\n+\n+\tfree(cost);\n+\tfree(a2b);\n+\tfree(b2a);\n+\n+\treturn res;\n+}\n+\n+static const char *short_oid(struct patch_util *util)\n+{\n+\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n+}\n+\n+static void output(struct string_list *a, struct string_list *b)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < b->nr; i++) {\n+\t\tstruct patch_util *util = b->items[i].util, *prev;\n+\n+\t\tif (util->matching < 0)\n+\t\t\tprintf(\"-: -------- > %d: %s\\n\",\n+\t\t\t\t\ti + 1, short_oid(util));\n+\t\telse {\n+\t\t\tprev = a->items[util->matching].util;\n+\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n+\t\t\t       util->matching + 1, short_oid(prev),\n+\t\t\t       i + 1, short_oid(util));\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i < a->nr; i++) {\n+\t\tstruct patch_util *util = a->items[i].util;\n+\n+\t\tif (util->matching < 0)\n+\t\t\tprintf(\"%d: %s < -: --------\\n\",\n+\t\t\t       i + 1, short_oid(util));\n+\t}\n+}\n+\n int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n {\n \tint no_patches = 0;\n@@ -32,9 +309,63 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n \t\t\t0, parse_creation_weight },\n \t\tOPT_END()\n \t};\n+\tint res = 0;\n+\tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n+\tstruct string_list branch1 = STRING_LIST_INIT_DUP;\n+\tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n \n \targc = parse_options(argc, argv, NULL, options,\n \t\t\tbuiltin_branch_diff_usage, 0);\n \n-\treturn 0;\n+\tif (argc == 2) {\n+\t\tif (!strstr(argv[0], \"..\"))\n+\t\t\twarning(_(\"no .. in range: '%s'\"), argv[0]);\n+\t\tstrbuf_addstr(&range1, argv[0]);\n+\n+\t\tif (!strstr(argv[1], \"..\"))\n+\t\t\twarning(_(\"no .. in range: '%s'\"), argv[1]);\n+\t\tstrbuf_addstr(&range2, argv[1]);\n+\t} else if (argc == 3) {\n+\t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n+\t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n+\t} else if (argc == 1) {\n+\t\tconst char *b = strstr(argv[0], \"...\"), *a = argv[0];\n+\t\tint a_len;\n+\n+\t\tif (!b)\n+\t\t\tdie(_(\"single arg format requires a symmetric range\"));\n+\n+\t\ta_len = (int)(b - a);\n+\t\tif (!a_len) {\n+\t\t\ta = \"HEAD\";\n+\t\t\ta_len = strlen(a);\n+\t\t}\n+\t\tb += 3;\n+\t\tif (!*b)\n+\t\t\tb = \"HEAD\";\n+\t\tstrbuf_addf(&range1, \"%s..%.*s\", b, a_len, a);\n+\t\tstrbuf_addf(&range2, \"%.*s..%s\", a_len, a, b);\n+\t} else {\n+\t\terror(\"Need two commit ranges\");\n+\t\tusage_with_options(builtin_branch_diff_usage, options);\n+\t}\n+\n+\tif (read_patches(range1.buf, &branch1))\n+\t\tres = error(_(\"could not parse log for '%s'\"), range1.buf);\n+\tif (!res && read_patches(range2.buf, &branch2))\n+\t\tres = error(_(\"could not parse log for '%s'\"), range2.buf);\n+\n+\tif (!res) {\n+\t\tfind_exact_matches(&branch1, &branch2);\n+\t\tres = get_correspondences(&branch1, &branch2, creation_weight);\n+\t\tif (!res)\n+\t\t\toutput(&branch1, &branch2);\n+\t}\n+\n+\tstrbuf_release(&range1);\n+\tstrbuf_release(&range2);\n+\tstring_list_clear(&branch1, 1);\n+\tstring_list_clear(&branch2, 1);\n+\n+\treturn !!res;\n }\n-- \n2.17.0.395.g6a618d6010f.dirty\n\n\n"},{"id":"346522","messageId":"6a618d6010f5767590ba88480e22835b1257316b.1525361419.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"[PATCH 04/18] branch-diff: improve the order of the shown commits","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T15:30:36Z","receivedAt":"2018-05-03T15:30:49Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch lets branch-diff use the same order as tbdiff.\n\nThe idea is simple: for left-to-right readers, it is natural to assume\nthat the branch-diff is performed between an older vs a newer version of\nthe branch. As such, the user is probably more interested in the\nquestion \"where did this come from?\" rather than \"where did that one\ngo?\".\n\nTo that end, we list the commits in the order of the second commit range\n(\"the newer version\"), inserting the unmatched commits of the first\ncommit range as soon as all their predecessors have been shown.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 59 +++++++++++++++++++++++++++++--------------\n 1 file changed, 40 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex 02dc06a57ca..59423498194 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -28,7 +28,7 @@ struct patch_util {\n \tstruct hashmap_entry e;\n \tconst char *diff, *patch;\n \n-\tint i;\n+\tint i, shown;\n \tint diffsize;\n \tsize_t diff_offset;\n \t/* the index of the matching item in the other branch, or -1 */\n@@ -271,28 +271,49 @@ static const char *short_oid(struct patch_util *util)\n \n static void output(struct string_list *a, struct string_list *b)\n {\n-\tint i;\n-\n-\tfor (i = 0; i < b->nr; i++) {\n-\t\tstruct patch_util *util = b->items[i].util, *prev;\n+\tint i = 0, j = 0;\n+\n+\t/*\n+\t * We assume the user is really more interested in the second argument\n+\t * (\"newer\" version). To that end, we print the output in the order of\n+\t * the RHS (the `b` parameter). To put the LHS (the `a` parameter)\n+\t * commits that are no longer in the RHS into a good place, we place\n+\t * them once we have shown all of their predecessors in the LHS.\n+\t */\n+\n+\twhile (i < a->nr || j < b->nr) {\n+\t\tstruct patch_util *a_util, *b_util;\n+\t\ta_util = i < a->nr ? a->items[i].util : NULL;\n+\t\tb_util = j < b->nr ? b->items[j].util : NULL;\n+\n+\t\t/* Skip all the already-shown commits from the LHS. */\n+\t\twhile (i < a->nr && a_util->shown)\n+\t\t\ta_util = ++i < a->nr ? a->items[i].util : NULL;\n+\n+\t\t/* Show unmatched LHS commit whose predecessors were shown. */\n+\t\tif (i < a->nr && a_util->matching < 0) {\n+\t\t\tprintf(\"%d: %s < -: --------\\n\",\n+\t\t\t       i + 1, short_oid(a_util));\n+\t\t\ti++;\n+\t\t\tcontinue;\n+\t\t}\n \n-\t\tif (util->matching < 0)\n+\t\t/* Show unmatched RHS commits. */\n+\t\twhile (j < b->nr && b_util->matching < 0) {\n \t\t\tprintf(\"-: -------- > %d: %s\\n\",\n-\t\t\t\t\ti + 1, short_oid(util));\n-\t\telse {\n-\t\t\tprev = a->items[util->matching].util;\n-\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n-\t\t\t       util->matching + 1, short_oid(prev),\n-\t\t\t       i + 1, short_oid(util));\n+\t\t\t       j + 1, short_oid(b_util));\n+\t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n-\t}\n-\n-\tfor (i = 0; i < a->nr; i++) {\n-\t\tstruct patch_util *util = a->items[i].util;\n \n-\t\tif (util->matching < 0)\n-\t\t\tprintf(\"%d: %s < -: --------\\n\",\n-\t\t\t       i + 1, short_oid(util));\n+\t\t/* Show matching LHS/RHS pair. */\n+\t\tif (j < b->nr) {\n+\t\t\ta_util = a->items[b_util->matching].util;\n+\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n+\t\t\t       b_util->matching + 1, short_oid(a_util),\n+\t\t\t       j + 1, short_oid(b_util));\n+\t\t\ta_util->shown = 1;\n+\t\t\tj++;\n+\t\t}\n \t}\n }\n \n-- \n2.17.0.395.g6a618d6010f.dirty\n\n\n"},{"id":"346523","messageId":"141e5b63e4511c13380216fad9b8601d2bc6051e.1525361419.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"[PATCH 05/18] branch-diff: also show the diff between patches","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T15:30:43Z","receivedAt":"2018-05-03T15:30:53Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Just like tbdiff, we now show the diff between matching patches. This is\na \"diff of two diffs\", so it can be a bit daunting to read for the\nbeginnger.\n\nThis brings branch-diff closer to be feature-complete with regard to\ntbdiff.\n\nAn alternative would be to display an interdiff, i.e. the hypothetical\ndiff which is the result of first reverting the old diff and then\napplying the new diff.\n\nEspecially when rebasing often, an interdiff is often not feasible,\nthough: if the old diff cannot be applied in reverse (due to a moving\nupstream), an interdiff can simply not be inferred.\n\nNote: while we now parse diff options such as --color, the effect is not\nyet the same as in tbdiff, where also the commit pairs would be colored.\nThis is left for a later commit.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 56 +++++++++++++++++++++++++++++++++++++------\n 1 file changed, 49 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex 59423498194..3b565a37492 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -6,6 +6,8 @@\n #include \"hashmap.h\"\n #include \"xdiff-interface.h\"\n #include \"hungarian.h\"\n+#include \"diff.h\"\n+#include \"diffcore.h\"\n \n static const char * const builtin_branch_diff_usage[] = {\n \tN_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n@@ -269,7 +271,31 @@ static const char *short_oid(struct patch_util *util)\n \treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n }\n \n-static void output(struct string_list *a, struct string_list *b)\n+static struct diff_filespec *get_filespec(const char *name, const char *p)\n+{\n+\tstruct diff_filespec *spec = alloc_filespec(name);\n+\n+\tfill_filespec(spec, &null_oid, 0, 0644);\n+\tspec->data = (char *)p;\n+\tspec->size = strlen(p);\n+\tspec->should_munmap = 0;\n+\tspec->is_stdin = 1;\n+\n+\treturn spec;\n+}\n+\n+static void patch_diff(const char *a, const char *b,\n+\t\t\t      struct diff_options *diffopt)\n+{\n+\tdiff_queue(&diff_queued_diff,\n+\t\t   get_filespec(\"a\", a), get_filespec(\"b\", b));\n+\n+\tdiffcore_std(diffopt);\n+\tdiff_flush(diffopt);\n+}\n+\n+static void output(struct string_list *a, struct string_list *b,\n+\t\t   struct diff_options *diffopt)\n {\n \tint i = 0, j = 0;\n \n@@ -311,6 +337,9 @@ static void output(struct string_list *a, struct string_list *b)\n \t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n \t\t\t       b_util->matching + 1, short_oid(a_util),\n \t\t\t       j + 1, short_oid(b_util));\n+\t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n+\t\t\t\tpatch_diff(a->items[b_util->matching].string,\n+\t\t\t\t\t   b->items[j].string, diffopt);\n \t\t\ta_util->shown = 1;\n \t\t\tj++;\n \t\t}\n@@ -319,24 +348,37 @@ static void output(struct string_list *a, struct string_list *b)\n \n int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n {\n-\tint no_patches = 0;\n+\tstruct diff_options diffopt = { 0 };\n \tdouble creation_weight = 0.6;\n \tstruct option options[] = {\n-\t\tOPT_BOOL(0, \"no-patches\", &no_patches,\n-\t\t\t N_(\"short format (no diffs)\")),\n+\t\tOPT_SET_INT(0, \"no-patches\", &diffopt.output_format,\n+\t\t\t    N_(\"short format (no diffs)\"),\n+\t\t\t    DIFF_FORMAT_NO_OUTPUT),\n \t\t{ OPTION_CALLBACK,\n \t\t\t0, \"creation-weight\", &creation_weight, N_(\"factor\"),\n \t\t\tN_(\"Fudge factor by which creation is weighted [0.6]\"),\n \t\t\t0, parse_creation_weight },\n \t\tOPT_END()\n \t};\n-\tint res = 0;\n+\tint i, j, res = 0;\n \tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n \tstruct string_list branch1 = STRING_LIST_INIT_DUP;\n \tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n \n+\tdiff_setup(&diffopt);\n+\tdiffopt.output_format = DIFF_FORMAT_PATCH;\n+\n \targc = parse_options(argc, argv, NULL, options,\n-\t\t\tbuiltin_branch_diff_usage, 0);\n+\t\t\tbuiltin_branch_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n+\n+\tfor (i = j = 0; i < argc; i++) {\n+\t\tint c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);\n+\n+\t\tif (!c)\n+\t\t\targv[j++] = argv[i];\n+\t}\n+\targc = j;\n+\tdiff_setup_done(&diffopt);\n \n \tif (argc == 2) {\n \t\tif (!strstr(argv[0], \"..\"))\n@@ -380,7 +422,7 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n \t\tfind_exact_matches(&branch1, &branch2);\n \t\tres = get_correspondences(&branch1, &branch2, creation_weight);\n \t\tif (!res)\n-\t\t\toutput(&branch1, &branch2);\n+\t\t\toutput(&branch1, &branch2, &diffopt);\n \t}\n \n \tstrbuf_release(&range1);\n-- \n2.17.0.395.g6a618d6010f.dirty\n\n\n"},{"id":"346524","messageId":"303419c56c4ea7d59381f48b3b613418dfd0043b.1525361419.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"[PATCH 06/18] branch-diff: right-trim commit messages","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T15:30:45Z","receivedAt":"2018-05-03T15:30:56Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"When comparing commit messages, we need to keep in mind that they are\nindented by four spaces. That is, empty lines are no longer empty, but\nhave \"trailing whitespace\". When displaying them in color, that results\nin those nagging red lines.\n\nLet's just right-trim the lines in the commit message, it's not like\ntrailing white-space in the commit messages are important enough to care\nabout in branch-diff.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex 3b565a37492..9dc581087bb 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -102,6 +102,7 @@ static int read_patches(const char *range, struct string_list *list)\n \t\t\t\tstrbuf_addbuf(&buf, &line);\n \t\t\t\tstrbuf_addstr(&buf, \"\\n\\n\");\n \t\t\t} else if (starts_with(line.buf, \"    \")) {\n+\t\t\t\tstrbuf_rtrim(&line);\n \t\t\t\tstrbuf_addbuf(&buf, &line);\n \t\t\t\tstrbuf_addch(&buf, '\\n');\n \t\t\t}\n-- \n2.17.0.395.g6a618d6010f.dirty\n\n\n"},{"id":"346525","messageId":"218e56a69e0e4a721a3ace7e05fc4b2c3e0d0e32.1525361419.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"[PATCH 07/18] branch-diff: indent the diffs just like tbdiff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T15:30:46Z","receivedAt":"2018-05-03T15:30:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The main information in the branch-diff view comes from the list of\nmatching and non-matching commits, the diffs are additional information.\nIndenting them helps with the reading flow.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 9 +++++++++\n 1 file changed, 9 insertions(+)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex 9dc581087bb..a4e602deb5d 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -272,6 +272,11 @@ static const char *short_oid(struct patch_util *util)\n \treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n }\n \n+static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)\n+{\n+\treturn data;\n+}\n+\n static struct diff_filespec *get_filespec(const char *name, const char *p)\n {\n \tstruct diff_filespec *spec = alloc_filespec(name);\n@@ -350,6 +355,7 @@ static void output(struct string_list *a, struct string_list *b,\n int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n {\n \tstruct diff_options diffopt = { 0 };\n+\tstruct strbuf four_spaces = STRBUF_INIT;\n \tdouble creation_weight = 0.6;\n \tstruct option options[] = {\n \t\tOPT_SET_INT(0, \"no-patches\", &diffopt.output_format,\n@@ -368,6 +374,9 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n \n \tdiff_setup(&diffopt);\n \tdiffopt.output_format = DIFF_FORMAT_PATCH;\n+\tdiffopt.output_prefix = output_prefix_cb;\n+\tstrbuf_addstr(&four_spaces, \"    \");\n+\tdiffopt.output_prefix_data = &four_spaces;\n \n \targc = parse_options(argc, argv, NULL, options,\n \t\t\tbuiltin_branch_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n-- \n2.17.0.395.g6a618d6010f.dirty\n\n\n"},{"id":"346526","messageId":"40471263d3cbf5b3a3b195ecfe51921dfd53a7c6.1525361419.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"[PATCH 09/18] branch-diff: adjust the output of the commit pairs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T15:30:51Z","receivedAt":"2018-05-03T15:31:01Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This change brings branch-diff yet another step closer to feature parity\nwith tbdiff: it now shows the oneline, too, and indicates with `=` when\nthe commits have identical diffs.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 67 +++++++++++++++++++++++++++++++++++++------\n 1 file changed, 58 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex 9edc5a0e89b..0a0564b8ec2 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -8,6 +8,8 @@\n #include \"hungarian.h\"\n #include \"diff.h\"\n #include \"diffcore.h\"\n+#include \"commit.h\"\n+#include \"pretty.h\"\n \n static const char * const builtin_branch_diff_usage[] = {\n \tN_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n@@ -267,9 +269,57 @@ static int get_correspondences(struct string_list *a, struct string_list *b,\n \treturn res;\n }\n \n-static const char *short_oid(struct patch_util *util)\n+static void output_pair_header(struct strbuf *buf,\n+\t\t\t       int i, struct patch_util *a_util,\n+\t\t\t       int j, struct patch_util *b_util)\n {\n-\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n+\tstatic char *dashes;\n+\tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n+\tstruct commit *commit;\n+\n+\tif (!dashes) {\n+\t\tchar *p;\n+\n+\t\tdashes = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV));\n+\t\tfor (p = dashes; *p; p++)\n+\t\t\t*p = '-';\n+\t}\n+\n+\tstrbuf_reset(buf);\n+\tif (i < 0)\n+\t\tstrbuf_addf(buf, \"-:  %s \", dashes);\n+\telse\n+\t\tstrbuf_addf(buf, \"%d:  %s \", i + 1,\n+\t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n+\n+\tif (i < 0)\n+\t\tstrbuf_addch(buf, '>');\n+\telse if (j < 0)\n+\t\tstrbuf_addch(buf, '<');\n+\telse if (strcmp(a_util->patch, b_util->patch))\n+\t\tstrbuf_addch(buf, '!');\n+\telse\n+\t\tstrbuf_addch(buf, '=');\n+\n+\tif (j < 0)\n+\t\tstrbuf_addf(buf, \" -:  %s\", dashes);\n+\telse\n+\t\tstrbuf_addf(buf, \" %d:  %s\", j + 1,\n+\t\t\t    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));\n+\n+\tcommit = lookup_commit_reference(oid);\n+\tif (commit) {\n+\t\tconst char *commit_buffer = get_commit_buffer(commit, NULL);\n+\t\tconst char *subject;\n+\n+\t\tfind_commit_subject(commit_buffer, &subject);\n+\t\tstrbuf_addch(buf, ' ');\n+\t\tformat_subject(buf, subject, \" \");\n+\t\tunuse_commit_buffer(commit, commit_buffer);\n+\t}\n+\tstrbuf_addch(buf, '\\n');\n+\n+\tfwrite(buf->buf, buf->len, 1, stdout);\n }\n \n static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)\n@@ -303,6 +353,7 @@ static void patch_diff(const char *a, const char *b,\n static void output(struct string_list *a, struct string_list *b,\n \t\t   struct diff_options *diffopt)\n {\n+\tstruct strbuf buf = STRBUF_INIT;\n \tint i = 0, j = 0;\n \n \t/*\n@@ -324,25 +375,22 @@ static void output(struct string_list *a, struct string_list *b,\n \n \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n \t\tif (i < a->nr && a_util->matching < 0) {\n-\t\t\tprintf(\"%d: %s < -: --------\\n\",\n-\t\t\t       i + 1, short_oid(a_util));\n+\t\t\toutput_pair_header(&buf, i, a_util, -1, NULL);\n \t\t\ti++;\n \t\t\tcontinue;\n \t\t}\n \n \t\t/* Show unmatched RHS commits. */\n \t\twhile (j < b->nr && b_util->matching < 0) {\n-\t\t\tprintf(\"-: -------- > %d: %s\\n\",\n-\t\t\t       j + 1, short_oid(b_util));\n+\t\t\toutput_pair_header(&buf, -1, NULL, j, b_util);\n \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n \n \t\t/* Show matching LHS/RHS pair. */\n \t\tif (j < b->nr) {\n \t\t\ta_util = a->items[b_util->matching].util;\n-\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n-\t\t\t       b_util->matching + 1, short_oid(a_util),\n-\t\t\t       j + 1, short_oid(b_util));\n+\t\t\toutput_pair_header(&buf,\n+\t\t\t\t\t   b_util->matching, a_util, j, b_util);\n \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n \t\t\t\t\t   b->items[j].string, diffopt);\n@@ -350,6 +398,7 @@ static void output(struct string_list *a, struct string_list *b,\n \t\t\tj++;\n \t\t}\n \t}\n+\tstrbuf_release(&buf);\n }\n \n int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n-- \n2.17.0.395.g6a618d6010f.dirty\n\n\n"},{"id":"346527","messageId":"00ea45123ae87205820ec3d00e83ded6ea25eb4b.1525361419.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"[PATCH 08/18] branch-diff: suppress the diff headers","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T15:30:47Z","receivedAt":"2018-05-03T15:31:03Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"When showing the diff between corresponding patches of the two branch\nversions, we have to make up a fake filename to run the diff machinery.\n\nThat filename does not carry any meaningful information, hence tbdiff\nsuppresses it. So we should, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 1 +\n diff.c                | 5 ++++-\n diff.h                | 1 +\n 3 files changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex a4e602deb5d..9edc5a0e89b 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -374,6 +374,7 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n \n \tdiff_setup(&diffopt);\n \tdiffopt.output_format = DIFF_FORMAT_PATCH;\n+\tdiffopt.flags.suppress_diff_headers = 1;\n \tdiffopt.output_prefix = output_prefix_cb;\n \tstrbuf_addstr(&four_spaces, \"    \");\n \tdiffopt.output_prefix_data = &four_spaces;\ndiff --git a/diff.c b/diff.c\nindex 1289df4b1f9..f1bda0db3f5 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -3197,13 +3197,16 @@ static void builtin_diff(const char *name_a,\n \t\tmemset(&xpp, 0, sizeof(xpp));\n \t\tmemset(&xecfg, 0, sizeof(xecfg));\n \t\tmemset(&ecbdata, 0, sizeof(ecbdata));\n+\t\tif (o->flags.suppress_diff_headers)\n+\t\t\tlbl[0] = NULL;\n \t\tecbdata.label_path = lbl;\n \t\tecbdata.color_diff = want_color(o->use_color);\n \t\tecbdata.ws_rule = whitespace_rule(name_b);\n \t\tif (ecbdata.ws_rule & WS_BLANK_AT_EOF)\n \t\t\tcheck_blank_at_eof(&mf1, &mf2, &ecbdata);\n \t\tecbdata.opt = o;\n-\t\tecbdata.header = header.len ? &header : NULL;\n+\t\tif (header.len && !o->flags.suppress_diff_headers)\n+\t\t\tecbdata.header = &header;\n \t\txpp.flags = o->xdl_opts;\n \t\txpp.anchors = o->anchors;\n \t\txpp.anchors_nr = o->anchors_nr;\ndiff --git a/diff.h b/diff.h\nindex d29560f822c..0dd6a71af60 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -94,6 +94,7 @@ struct diff_flags {\n \tunsigned funccontext:1;\n \tunsigned default_follow_renames:1;\n \tunsigned stat_with_summary:1;\n+\tunsigned suppress_diff_headers:1;\n };\n \n static inline void diff_flags_or(struct diff_flags *a,\n-- \n2.17.0.395.g6a618d6010f.dirty\n\n\n"},{"id":"346528","messageId":"c89937afc2820bb105f59963176a2024e79d6095.1525361419.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"[PATCH 12/18] branch-diff: use color for the commit pairs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T15:30:55Z","receivedAt":"2018-05-03T15:31:08Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Arguably the most important part of branch-diff's output is the list of\ncommits in the two branches, together with their relationships.\n\nFor that reason, tbdiff introduced color-coding that is pretty\nintuitive, especially for unchanged patches (all dim yellow, like the\nfirst line in `git show`'s output) vs modified patches (old commit is\nred, new commit is green). Let's imitate that color scheme.\n\nWhile at it, also copy tbdiff's change of the fragment color to magenta.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 49 +++++++++++++++++++++++++++++++------------\n 1 file changed, 36 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex 7625da09e6f..e505b696d11 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -270,13 +270,19 @@ static int get_correspondences(struct string_list *a, struct string_list *b,\n \treturn res;\n }\n \n-static void output_pair_header(struct strbuf *buf,\n+static void output_pair_header(struct diff_options *diffopt, struct strbuf *buf,\n \t\t\t       int i, struct patch_util *a_util,\n \t\t\t       int j, struct patch_util *b_util)\n {\n \tstatic char *dashes;\n \tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n \tstruct commit *commit;\n+\tchar status;\n+\tconst char *color_reset = diff_get_color_opt(diffopt, DIFF_RESET);\n+\tconst char *color_old = diff_get_color_opt(diffopt, DIFF_FILE_OLD);\n+\tconst char *color_new = diff_get_color_opt(diffopt, DIFF_FILE_NEW);\n+\tconst char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT);\n+\tconst char *color;\n \n \tif (!dashes) {\n \t\tchar *p;\n@@ -286,21 +292,33 @@ static void output_pair_header(struct strbuf *buf,\n \t\t\t*p = '-';\n \t}\n \n+\tif (j < 0) {\n+\t\tcolor = color_old;\n+\t\tstatus = '<';\n+\t} else if (i < 0) {\n+\t\tcolor = color_new;\n+\t\tstatus = '>';\n+\t} else if (strcmp(a_util->patch, b_util->patch)) {\n+\t\tcolor = color_commit;\n+\t\tstatus = '!';\n+\t} else {\n+\t\tcolor = color_commit;\n+\t\tstatus = '=';\n+\t}\n+\n \tstrbuf_reset(buf);\n+\tstrbuf_addstr(buf, status == '!' ? color_old : color);\n \tif (i < 0)\n \t\tstrbuf_addf(buf, \"-:  %s \", dashes);\n \telse\n \t\tstrbuf_addf(buf, \"%d:  %s \", i + 1,\n \t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n \n-\tif (i < 0)\n-\t\tstrbuf_addch(buf, '>');\n-\telse if (j < 0)\n-\t\tstrbuf_addch(buf, '<');\n-\telse if (strcmp(a_util->patch, b_util->patch))\n-\t\tstrbuf_addch(buf, '!');\n-\telse\n-\t\tstrbuf_addch(buf, '=');\n+\tif (status == '!')\n+\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color);\n+\tstrbuf_addch(buf, status);\n+\tif (status == '!')\n+\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color_new);\n \n \tif (j < 0)\n \t\tstrbuf_addf(buf, \" -:  %s\", dashes);\n@@ -313,12 +331,15 @@ static void output_pair_header(struct strbuf *buf,\n \t\tconst char *commit_buffer = get_commit_buffer(commit, NULL);\n \t\tconst char *subject;\n \n+\t\tif (status == '!')\n+\t\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color);\n+\n \t\tfind_commit_subject(commit_buffer, &subject);\n \t\tstrbuf_addch(buf, ' ');\n \t\tformat_subject(buf, subject, \" \");\n \t\tunuse_commit_buffer(commit, commit_buffer);\n \t}\n-\tstrbuf_addch(buf, '\\n');\n+\tstrbuf_addf(buf, \"%s\\n\", color_reset);\n \n \tfwrite(buf->buf, buf->len, 1, stdout);\n }\n@@ -381,21 +402,21 @@ static void output(struct string_list *a, struct string_list *b,\n \n \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n \t\tif (i < a->nr && a_util->matching < 0) {\n-\t\t\toutput_pair_header(&buf, i, a_util, -1, NULL);\n+\t\t\toutput_pair_header(diffopt, &buf, i, a_util, -1, NULL);\n \t\t\ti++;\n \t\t\tcontinue;\n \t\t}\n \n \t\t/* Show unmatched RHS commits. */\n \t\twhile (j < b->nr && b_util->matching < 0) {\n-\t\t\toutput_pair_header(&buf, -1, NULL, j, b_util);\n+\t\t\toutput_pair_header(diffopt, &buf, -1, NULL, j, b_util);\n \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n \n \t\t/* Show matching LHS/RHS pair. */\n \t\tif (j < b->nr) {\n \t\t\ta_util = a->items[b_util->matching].util;\n-\t\t\toutput_pair_header(&buf,\n+\t\t\toutput_pair_header(diffopt, &buf,\n \t\t\t\t\t   b_util->matching, a_util, j, b_util);\n \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n@@ -427,6 +448,8 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n \tstruct string_list branch1 = STRING_LIST_INIT_DUP;\n \tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n \n+\tgit_diff_basic_config(\"diff.color.frag\", \"magenta\", NULL);\n+\n \tdiff_setup(&diffopt);\n \tdiffopt.output_format = DIFF_FORMAT_PATCH;\n \tdiffopt.flags.suppress_diff_headers = 1;\n-- \n2.17.0.395.g6a618d6010f.dirty\n\n\n"},{"id":"346529","messageId":"fe12b99a0b4f78ab75fcfbcf51c5edffb190c4e8.1525361419.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"[PATCH 11/18] branch-diff: add tests","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T15:30:53Z","receivedAt":"2018-05-03T15:31:09Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Thomas Rast <tr@thomasrast.ch>\n\nThese are essentially lifted from https://github.com/trast/tbdiff, with\nlight touch-ups to account for the new command name.\n\nApart from renaming `tbdiff` to `branch-diff`, only one test case needed\nto be adjusted: 11 - 'changed message'.\n\nThe underlying reason it had to be adjusted is that diff generation is\nsometimes ambiguous. In this case, a comment line and an empty line are\nadded, but it is ambiguous whether they were added after the existing\nempty line, or whether an empty line and the comment line are added\n*before* the existing emtpy line. And apparently xdiff picks a different\noption here than Python's difflib.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/.gitattributes       |   1 +\n t/t7910-branch-diff.sh | 144 ++++++++++\n t/t7910/history.export | 604 +++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 749 insertions(+)\n create mode 100755 t/t7910-branch-diff.sh\n create mode 100644 t/t7910/history.export\n\ndiff --git a/t/.gitattributes b/t/.gitattributes\nindex 3bd959ae523..af15d5aeedd 100644\n--- a/t/.gitattributes\n+++ b/t/.gitattributes\n@@ -18,5 +18,6 @@ t[0-9][0-9][0-9][0-9]/* -whitespace\n /t5515/* eol=lf\n /t556x_common eol=lf\n /t7500/* eol=lf\n+/t7910/* eol=lf\n /t8005/*.txt eol=lf\n /t9*/*.dump eol=lf\ndiff --git a/t/t7910-branch-diff.sh b/t/t7910-branch-diff.sh\nnew file mode 100755\nindex 00000000000..a7fece88045\n--- /dev/null\n+++ b/t/t7910-branch-diff.sh\n@@ -0,0 +1,144 @@\n+#!/bin/sh\n+\n+test_description='branch-diff tests'\n+\n+. ./test-lib.sh\n+\n+# Note that because of git-branch-diff's heuristics, test_commit does more\n+# harm than good.  We need some real history.\n+\n+test_expect_success 'setup' '\n+\tgit fast-import < \"$TEST_DIRECTORY\"/t7910/history.export\n+'\n+\n+test_expect_success 'simple A..B A..C (unmodified)' '\n+\tgit branch-diff --no-color master..topic master..unmodified >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  35b9b25 s/5/A/\n+\t2:  fccce22 = 2:  de345ab s/4/A/\n+\t3:  147e64e = 3:  9af6654 s/11/B/\n+\t4:  a63e992 = 4:  2901f77 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'simple B...C (unmodified)' '\n+\tgit branch-diff --no-color topic...unmodified >actual &&\n+\t# same \"expected\" as above\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'simple A B C (unmodified)' '\n+\tgit branch-diff --no-color master topic unmodified >actual &&\n+\t# same \"expected\" as above\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'trivial reordering' '\n+\tgit branch-diff --no-color master topic reordered >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  aca177a s/5/A/\n+\t3:  147e64e = 2:  14ad629 s/11/B/\n+\t4:  a63e992 = 3:  ee58208 s/12/B/\n+\t2:  fccce22 = 4:  307b27a s/4/A/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'removed a commit' '\n+\tgit branch-diff --no-color master topic removed >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  7657159 s/5/A/\n+\t2:  fccce22 < -:  ------- s/4/A/\n+\t3:  147e64e = 2:  43d84d3 s/11/B/\n+\t4:  a63e992 = 3:  a740396 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'added a commit' '\n+\tgit branch-diff --no-color master topic added >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  2716022 s/5/A/\n+\t2:  fccce22 = 2:  b62accd s/4/A/\n+\t-:  ------- > 3:  df46cfa s/6/A/\n+\t3:  147e64e = 4:  3e64548 s/11/B/\n+\t4:  a63e992 = 5:  12b4063 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'new base, A B C' '\n+\tgit branch-diff --no-color master topic rebased >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  cc9c443 s/5/A/\n+\t2:  fccce22 = 2:  c5d9641 s/4/A/\n+\t3:  147e64e = 3:  28cc2b6 s/11/B/\n+\t4:  a63e992 = 4:  5628ab7 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'new base, B...C' '\n+\t# this syntax includes the commits from master!\n+\tgit branch-diff --no-color topic...rebased >actual &&\n+\tcat >expected <<-EOF &&\n+\t-:  ------- > 1:  a31b12e unrelated\n+\t1:  4de457d = 2:  cc9c443 s/5/A/\n+\t2:  fccce22 = 3:  c5d9641 s/4/A/\n+\t3:  147e64e = 4:  28cc2b6 s/11/B/\n+\t4:  a63e992 = 5:  5628ab7 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'changed commit' '\n+\tgit branch-diff --no-color topic...changed >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  a4b3333 s/5/A/\n+\t2:  fccce22 = 2:  f51d370 s/4/A/\n+\t3:  147e64e ! 3:  0559556 s/11/B/\n+\t    @@ -10,7 +10,7 @@\n+\t      9\n+\t      10\n+\t     -11\n+\t    -+B\n+\t    ++BB\n+\t      12\n+\t      13\n+\t      14\n+\t4:  a63e992 ! 4:  d966c5c s/12/B/\n+\t    @@ -8,7 +8,7 @@\n+\t     @@\n+\t      9\n+\t      10\n+\t    - B\n+\t    + BB\n+\t     -12\n+\t     +B\n+\t      13\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'changed message' '\n+\tgit branch-diff --no-color topic...changed-message >actual &&\n+\tsed s/Z/\\ /g >expected <<-EOF &&\n+\t1:  4de457d = 1:  f686024 s/5/A/\n+\t2:  fccce22 ! 2:  4ab067d s/4/A/\n+\t    @@ -2,6 +2,8 @@\n+\t    Z\n+\t    Z    s/4/A/\n+\t    Z\n+\t    +    Also a silly comment here!\n+\t    +\n+\t    Zdiff --git a/file b/file\n+\t    Z--- a/file\n+\t    Z+++ b/file\n+\t3:  147e64e = 3:  b9cb956 s/11/B/\n+\t4:  a63e992 = 4:  8add5f1 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_done\ndiff --git a/t/t7910/history.export b/t/t7910/history.export\nnew file mode 100644\nindex 00000000000..b8ffff0940d\n--- /dev/null\n+++ b/t/t7910/history.export\n@@ -0,0 +1,604 @@\n+blob\n+mark :1\n+data 51\n+1\n+2\n+3\n+4\n+5\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+reset refs/heads/removed\n+commit refs/heads/removed\n+mark :2\n+author Thomas Rast <trast@inf.ethz.ch> 1374424921 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374484724 +0200\n+data 8\n+initial\n+M 100644 :1 file\n+\n+blob\n+mark :3\n+data 51\n+1\n+2\n+3\n+4\n+A\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :4\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+blob\n+mark :5\n+data 51\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :6\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+data 7\n+s/4/A/\n+from :4\n+M 100644 :5 file\n+\n+blob\n+mark :7\n+data 50\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :8\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+data 8\n+s/11/B/\n+from :6\n+M 100644 :7 file\n+\n+blob\n+mark :9\n+data 49\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :10\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+data 8\n+s/12/B/\n+from :8\n+M 100644 :9 file\n+\n+blob\n+mark :11\n+data 10\n+unrelated\n+\n+commit refs/heads/master\n+mark :12\n+author Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200\n+data 10\n+unrelated\n+from :2\n+M 100644 :11 otherfile\n+\n+commit refs/heads/rebased\n+mark :13\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485137 +0200\n+data 7\n+s/5/A/\n+from :12\n+M 100644 :3 file\n+\n+commit refs/heads/rebased\n+mark :14\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n+data 7\n+s/4/A/\n+from :13\n+M 100644 :5 file\n+\n+commit refs/heads/rebased\n+mark :15\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n+data 8\n+s/11/B/\n+from :14\n+M 100644 :7 file\n+\n+commit refs/heads/rebased\n+mark :16\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n+data 8\n+s/12/B/\n+from :15\n+M 100644 :9 file\n+\n+commit refs/heads/added\n+mark :17\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/added\n+mark :18\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 7\n+s/4/A/\n+from :17\n+M 100644 :5 file\n+\n+blob\n+mark :19\n+data 51\n+1\n+2\n+3\n+A\n+A\n+A\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/added\n+mark :20\n+author Thomas Rast <trast@inf.ethz.ch> 1374485186 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 7\n+s/6/A/\n+from :18\n+M 100644 :19 file\n+\n+blob\n+mark :21\n+data 50\n+1\n+2\n+3\n+A\n+A\n+A\n+7\n+8\n+9\n+10\n+B\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/added\n+mark :22\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 8\n+s/11/B/\n+from :20\n+M 100644 :21 file\n+\n+blob\n+mark :23\n+data 49\n+1\n+2\n+3\n+A\n+A\n+A\n+7\n+8\n+9\n+10\n+B\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/added\n+mark :24\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 8\n+s/12/B/\n+from :22\n+M 100644 :23 file\n+\n+commit refs/heads/reordered\n+mark :25\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+blob\n+mark :26\n+data 50\n+1\n+2\n+3\n+4\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/reordered\n+mark :27\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 8\n+s/11/B/\n+from :25\n+M 100644 :26 file\n+\n+blob\n+mark :28\n+data 49\n+1\n+2\n+3\n+4\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/reordered\n+mark :29\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 8\n+s/12/B/\n+from :27\n+M 100644 :28 file\n+\n+commit refs/heads/reordered\n+mark :30\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 7\n+s/4/A/\n+from :29\n+M 100644 :9 file\n+\n+commit refs/heads/changed\n+mark :31\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/changed\n+mark :32\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 7\n+s/4/A/\n+from :31\n+M 100644 :5 file\n+\n+blob\n+mark :33\n+data 51\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+BB\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/changed\n+mark :34\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 8\n+s/11/B/\n+from :32\n+M 100644 :33 file\n+\n+blob\n+mark :35\n+data 50\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+BB\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/changed\n+mark :36\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 8\n+s/12/B/\n+from :34\n+M 100644 :35 file\n+\n+commit refs/heads/changed-message\n+mark :37\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/changed-message\n+mark :38\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200\n+data 35\n+s/4/A/\n+\n+Also a silly comment here!\n+from :37\n+M 100644 :5 file\n+\n+commit refs/heads/changed-message\n+mark :39\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200\n+data 8\n+s/11/B/\n+from :38\n+M 100644 :7 file\n+\n+commit refs/heads/changed-message\n+mark :40\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200\n+data 8\n+s/12/B/\n+from :39\n+M 100644 :9 file\n+\n+commit refs/heads/unmodified\n+mark :41\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/unmodified\n+mark :42\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200\n+data 7\n+s/4/A/\n+from :41\n+M 100644 :5 file\n+\n+commit refs/heads/unmodified\n+mark :43\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200\n+data 8\n+s/11/B/\n+from :42\n+M 100644 :7 file\n+\n+commit refs/heads/unmodified\n+mark :44\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200\n+data 8\n+s/12/B/\n+from :43\n+M 100644 :9 file\n+\n+commit refs/heads/removed\n+mark :45\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/removed\n+mark :46\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n+data 8\n+s/11/B/\n+from :45\n+M 100644 :26 file\n+\n+commit refs/heads/removed\n+mark :47\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n+data 8\n+s/12/B/\n+from :46\n+M 100644 :28 file\n+\n+reset refs/heads/removed\n+from :47\n+\n-- \n2.17.0.395.g6a618d6010f.dirty\n\n\n"},{"id":"346530","messageId":"a94e94edf652244f52f921522922cfdf89a762c7.1525361419.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"[PATCH 13/18] color: provide inverted colors, too","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T15:30:58Z","receivedAt":"2018-05-03T15:31:15Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"For every regular color, there exists the inverted equivalent where\nbackground and foreground colors are exchanged.\n\nWe will use this in the next commit to allow inverting *just* the +/-\nsigns in a diff.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n color.h | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/color.h b/color.h\nindex cd0bcedd084..f0984b09583 100644\n--- a/color.h\n+++ b/color.h\n@@ -36,6 +36,12 @@ struct strbuf;\n #define GIT_COLOR_BOLD_BLUE\t\"\\033[1;34m\"\n #define GIT_COLOR_BOLD_MAGENTA\t\"\\033[1;35m\"\n #define GIT_COLOR_BOLD_CYAN\t\"\\033[1;36m\"\n+#define GIT_COLOR_INV_RED\t\"\\033[7;31m\"\n+#define GIT_COLOR_INV_GREEN\t\"\\033[7;32m\"\n+#define GIT_COLOR_INV_YELLOW\t\"\\033[7;33m\"\n+#define GIT_COLOR_INV_BLUE\t\"\\033[7;34m\"\n+#define GIT_COLOR_INV_MAGENTA\t\"\\033[7;35m\"\n+#define GIT_COLOR_INV_CYAN\t\"\\033[7;36m\"\n #define GIT_COLOR_BG_RED\t\"\\033[41m\"\n #define GIT_COLOR_BG_GREEN\t\"\\033[42m\"\n #define GIT_COLOR_BG_YELLOW\t\"\\033[43m\"\n-- \n2.17.0.395.g6a618d6010f.dirty\n\n\n"},{"id":"346531","messageId":"2f8017c732fce38f45074125554fddf6e072ea99.1525361419.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"[PATCH 15/18] branch-diff: offer to dual-color the diffs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T15:31:01Z","receivedAt":"2018-05-03T15:31:18Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"When showing what changed between old and new commits, we show a diff of\nthe patches. This diff is a diff between diffs, therefore there are\nnested +/- signs, and it can be relatively hard to understand what is\ngoing on.\n\nWith the --dual-color option, the preimage and the postimage are colored\nlike the diffs they are, and the *outer* +/- sign is inverted for\nclarity.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 8 ++++++++\n 1 file changed, 8 insertions(+)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex e505b696d11..edf80ecb736 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -432,8 +432,11 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n {\n \tstruct diff_options diffopt = { 0 };\n \tstruct strbuf four_spaces = STRBUF_INIT;\n+\tint dual_color = 0;\n \tdouble creation_weight = 0.6;\n \tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"dual-color\", &dual_color,\n+\t\t\t    N_(\"color both diff and diff-between-diffs\")),\n \t\tOPT_SET_INT(0, \"no-patches\", &diffopt.output_format,\n \t\t\t    N_(\"short format (no diffs)\"),\n \t\t\t    DIFF_FORMAT_NO_OUTPUT),\n@@ -469,6 +472,11 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n \targc = j;\n \tdiff_setup_done(&diffopt);\n \n+\tif (dual_color) {\n+\t\tdiffopt.use_color = 1;\n+\t\tdiffopt.flags.dual_color_diffed_diffs = 1;\n+\t}\n+\n \tif (argc == 2) {\n \t\tif (!strstr(argv[0], \"..\"))\n \t\t\twarning(_(\"no .. in range: '%s'\"), argv[0]);\n-- \n2.17.0.395.g6a618d6010f.dirty\n\n\n"},{"id":"346532","messageId":"9810869ced952dbed3cb0972d1d1abd65cf14b0e.1525361419.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"[PATCH 10/18] branch-diff: do not show \"function names\" in hunk headers","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T15:30:52Z","receivedAt":"2018-05-03T15:31:19Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"We are comparing complete, formatted commit messages with patches. There\nare no function names here, so stop looking for them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex 0a0564b8ec2..7625da09e6f 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -10,6 +10,7 @@\n #include \"diffcore.h\"\n #include \"commit.h\"\n #include \"pretty.h\"\n+#include \"userdiff.h\"\n \n static const char * const builtin_branch_diff_usage[] = {\n \tN_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n@@ -327,6 +328,10 @@ static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)\n \treturn data;\n }\n \n+static struct userdiff_driver no_func_name = {\n+\t.funcname = { \"$^\", 0 }\n+};\n+\n static struct diff_filespec *get_filespec(const char *name, const char *p)\n {\n \tstruct diff_filespec *spec = alloc_filespec(name);\n@@ -336,6 +341,7 @@ static struct diff_filespec *get_filespec(const char *name, const char *p)\n \tspec->size = strlen(p);\n \tspec->should_munmap = 0;\n \tspec->is_stdin = 1;\n+\tspec->driver = &no_func_name;\n \n \treturn spec;\n }\n-- \n2.17.0.395.g6a618d6010f.dirty\n\n\n"},{"id":"346533","messageId":"5d047a830f12d29e7bc631fe166bbce7d5904a7b.1525361419.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"[PATCH 18/18] completion: support branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T15:31:08Z","receivedAt":"2018-05-03T15:31:23Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Tab completion of `branch-diff` is very convenient, especially given\nthat the revision arguments that need to be passed to `git branch-diff`\nare typically more complex than, say, your grandfather's `git log`\narguments.\n\nWithout this patch, we would only complete the `branch-diff` part but\nnot the options and other arguments.\n\nThis of itself may already be slightly disruptive for well-trained\nfingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to\n`git branch origin/master`, as we now no longer automatically append a\nspace after completing `git branch`: this is now ambiguous.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n contrib/completion/git-completion.bash | 18 ++++++++++++++++++\n 1 file changed, 18 insertions(+)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 01dd9ff07a2..45addd525ac 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1496,6 +1496,24 @@ _git_format_patch ()\n \t__git_complete_revlist\n }\n \n+__git_branch_diff_options=\"\n+\t--no-patches --creation-weight= --dual-color\n+\"\n+\n+_git_branch_diff ()\n+{\n+\tcase \"$cur\" in\n+\t--*)\n+\t\t__gitcomp \"\n+\t\t\t$__git_branch_diff_options\n+\t\t\t$__git_diff_common_options\n+\t\t\t\"\n+\t\treturn\n+\t\t;;\n+\tesac\n+\t__git_complete_revlist\n+}\n+\n _git_fsck ()\n {\n \tcase \"$cur\" in\n-- \n2.17.0.395.g6a618d6010f.dirty\n"},{"id":"346534","messageId":"edb34bd4f8da7437efb20e442780f17e9f84fc73.1525361419.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"[PATCH 17/18] branch-diff: add a man page","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T15:31:07Z","receivedAt":"2018-05-03T15:31:24Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is a heavily butchered version of the README written by Thomas\nRast and Thomas Gummerer, lifted from https://github.com/trast/tbdiff.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-branch-diff.txt | 239 ++++++++++++++++++++++++++++++\n 1 file changed, 239 insertions(+)\n create mode 100644 Documentation/git-branch-diff.txt\n\ndiff --git a/Documentation/git-branch-diff.txt b/Documentation/git-branch-diff.txt\nnew file mode 100644\nindex 00000000000..b841586735c\n--- /dev/null\n+++ b/Documentation/git-branch-diff.txt\n@@ -0,0 +1,239 @@\n+git-branch-diff(1)\n+==================\n+\n+NAME\n+----\n+git-branch-diff - Compare two versions of a branch\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git branch-diff' [--color=[<when>]] [--no-color] [<diff-options>]\n+\t[--dual-color] [--no-patches] [--creation-weight=<weight>]\n+\t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n+\n+DESCRIPTION\n+-----------\n+\n+This command shows the differences between two versions of a patch\n+series, or more generally, two commit ranges (ignoring merges).\n+\n+To that end, it first finds pairs of commits from both commit ranges\n+that correspond with each other. Two commits are said to correspond when\n+the diff between their patches (i.e. the author information, the commit\n+message and the commit diff) is reasonably small compared to the\n+patches' size. See ``Algorithm` below for details.\n+\n+Finally, the list of matching commits is shown in the order of the\n+second commit range, with unmatched commits being inserted just after\n+all of their ancestors have been shown.\n+\n+\n+OPTIONS\n+-------\n+--no-patches::\n+\tSuppress the diffs between commit pairs that were deemed to\n+\tcorrespond; only show the pairings.\n+\n+--dual-color::\n+\tWhen the commit diffs differ, recreate the original diffs'\n+\tcoloring, and add outer -/+ diff markers with the *background*\n+\tbeing red/green to make it easier to see e.g. when there was a\n+\tchange in what exact lines were added.\n+\n+--creation-weight=<factor>::\n+\tSet the creation/deletion cost fudge factor to `<factor>`.\n+\tDefaults to 0.6. Try a larger value if `git branch-diff`\n+\terroneously considers a large change a total rewrite (deletion\n+\tof one commit and addition of another), and a smaller one in\n+\tthe reverse case. See the ``Algorithm`` section below for an\n+\texplanation why this is needed.\n+\n+<range1> <range2>::\n+\tCompare the commits specified by the two ranges, where\n+\t`<range1>` is considered an older version of `<range2>`.\n+\n+<rev1>...<rev2>::\n+\tEquivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.\n+\n+<base> <rev1> <rev2>::\n+\tEquivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.\n+\tNote that `<base>` does not need to be the exact branch point\n+\tof the branches. Example: after rebasing a branch `my-topic`,\n+\t`git branch-diff my-topic@{u} my-topic@{1} my-topic` would\n+\tshow the differences introduced by the rebase.\n+\n+`git branch-diff` also accepts the regular diff options (see\n+linkgit:git-diff[1]), most notably the `--color=[<when>]` and\n+`--no-color` options. These options are used when generating the \"diff\n+between patches\", i.e. to compare the author, commit message and diff of\n+corresponding old/new commits. There is currently no means to tweak the\n+diff options passed to `git log` when generating those patches.\n+\n+\n+CONFIGURATION\n+-------------\n+This command uses the `diff.color.*` and `pager.branch-diff` settings\n+(the latter is on by default).\n+See linkgit:git-config[1].\n+\n+\n+Examples\n+--------\n+\n+When a rebase required merge conflicts to be resolved, compare the changes\n+introduced by the rebase directly afterwards using:\n+\n+------------\n+$ git branch-diff @{u} @{1} @\n+------------\n+\n+\n+A typical output of `git branch-diff` would look like this:\n+\n+------------\n+-:  ------- > 1:  0ddba11 Prepare for the inevitable!\n+1:  c0debee = 2:  cab005e Add a helpful message at the start\n+2:  f00dbal ! 3:  decafe1 Describe a bug\n+    @@ -1,3 +1,3 @@\n+     Author: A U Thor <author@example.com>\n+\n+    -TODO: Describe a bug\n+    +Describe a bug\n+    @@ -324,5 +324,6\n+      This is expected.\n+\n+    -+What is unexpected is that it will also crash.\n+    ++Unexpectedly, it also crashes. This is a bug, and the jury is\n+    ++still out there how to fix it best. See ticket #314 for details.\n+\n+      Contact\n+3:  bedead < -:  ------- TO-UNDO\n+------------\n+\n+In this example, there are 3 old and 3 new commits, where the developer\n+removed the 3rd, added a new one before the first two, and modified the\n+commit message of the 2nd commit as well its diff.\n+\n+When the output goes to a terminal, it is color-coded by default, just\n+like regular `git diff`'s output. In addition, the first line (adding a\n+commit) is green, the last line (deleting a commit) is red, the second\n+line (with a perfect match) is yellow like the commit header of `git\n+show`'s output, and the third line colors the old commit red, the new\n+one green and the rest like `git show`'s commit header.\n+\n+The color-coded diff is actually a bit hard to read, though, as it\n+colors the entire lines red or green. The line that added \"What is\n+unexpected\" in the old commit, for example, is completely red, even if\n+the intent of the old commit was to add something.\n+\n+To help with that, use the `--dual-color` mode. In this mode, the diff\n+of diffs will retain the original diff colors, and prefix the lines with\n+-/+ markers that have their *background* red or green, to make it more\n+obvious that they describe how the diff itself changed.\n+\n+\n+Algorithm\n+---------\n+\n+The general idea is this: we generate a cost matrix between the commits\n+in both commit ranges, then solve the least-cost assignment.\n+\n+To avoid false positives (e.g. when a patch has been removed, and an\n+unrelated patch has been added between two iterations of the same patch\n+series), the cost matrix is extended to allow for that, by adding\n+fixed-cost entries for wholesale deletes/adds.\n+\n+Example: let commits `1--2` be the first iteration of a patch series and\n+`A--C` the second iteration. Let's assume that `A` is a cherry-pick of\n+`2,` and `C` is a cherry-pick of `1` but with a small modification (say,\n+a fixed typo). Visualize the commits as a bipartite graph:\n+\n+------------\n+    1            A\n+\n+    2            B\n+\n+\t\t C\n+------------\n+\n+We are looking for a \"best\" explanation of the new series in terms of\n+the old one. We can represent an \"explanation\" as an edge in the graph:\n+\n+\n+------------\n+    1            A\n+\t       /\n+    2 --------'  B\n+\n+\t\t C\n+------------\n+\n+This explanation comes for \"free\" because there was no change. Similarly\n+`C` could be explained using `1`, but that comes at some cost c>0\n+because of the modification:\n+\n+------------\n+    1 ----.      A\n+\t  |    /\n+    2 ----+---'  B\n+\t  |\n+\t  `----- C\n+\t  c>0\n+------------\n+\n+In mathematical terms, what we are looking for is some sort of a minimum\n+cost bipartite matching; `1` is matched to `C` at some cost, etc. The\n+underlying graph is in fact a complete bipartite graph; the cost we\n+associate with every edge is the size of the diff between the two\n+commits' patches. To explain also new commits, we introduce dummy nodes\n+on both sides:\n+\n+------------\n+    1 ----.      A\n+\t  |    /\n+    2 ----+---'  B\n+\t  |\n+    o     `----- C\n+\t  c>0\n+    o            o\n+\n+    o            o\n+------------\n+\n+The cost of an edge `o--C` is the size of `C`'s diff, modified by a\n+fudge factor that should be smaller than 1.0. The cost of an edge `o--o`\n+is free. The fudge factor is necessary because even if `1` and `C` have\n+nothing in common, they may still share a few empty lines and such,\n+possibly making the assignment `1--C`, `o--o` slightly cheaper than\n+`1--o`, `o--C` even if `1` and `C` have nothing in common. With the\n+fudge factor we require a much larger common part to consider patches as\n+corresponding.\n+\n+The overall time needed to compute this algorithm is the time needed to\n+compute n+m commit diffs and then n*m diffs of patches, plus the time\n+needed to compute the least-cost assigment between n and m diffs. Git\n+uses an implementation of the Jonker-Volgenant algorithm to solve the\n+assignment problem, which has cubic runtime complexity. The matching\n+found in this case will look like this:\n+\n+------------\n+    1 ----.      A\n+\t  |    /\n+    2 ----+---'  B\n+       .--+-----'\n+    o -'  `----- C\n+\t  c>0\n+    o ---------- o\n+\n+    o ---------- o\n+------------\n+\n+\n+SEE ALSO\n+--------\n+linkgit:git-log[1]\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\n-- \n2.17.0.395.g6a618d6010f.dirty\n\n\n"},{"id":"346535","messageId":"62f0e2cf73fb392114e0ded73af79b7dfa9ab66f.1525361419.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"[PATCH 16/18] branch-diff --dual-color: work around bogus white-space warning","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T15:31:02Z","receivedAt":"2018-05-03T15:31:27Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"When displaying a diff of diffs, it is possible that there is an outer\n`+` before a context line. That happens when the context changed between\nold and new commit. When that context line starts with a tab (after the\nspace that marks it as context line), our diff machinery spits out a\nwhite-space error (space before tab), but in this case, that is\nincorrect.\n\nWork around this by detecting that situation and simply *not* printing\nthe space in that case.\n\nThis is slightly improper a fix because it is conceivable that an\noutput_prefix might be configured with *just* the right length to let\nthat tab jump to a different tab stop depending whether we emit that\nspace or not.\n\nHowever, the proper fix would be relatively ugly and intrusive because\nit would have to *weaken* the WS_SPACE_BEFORE_TAB option in ws.c.\nBesides, we do not expose the --dual-color option in cases other than\nthe `branch-diff` command, which only uses a hard-coded output_prefix of\nfour spaces (which misses the problem by one column ;-)).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n diff.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/diff.c b/diff.c\nindex 98a41e88620..b98a18fe014 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -1098,6 +1098,12 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n \t\t\telse if (c != '+')\n \t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t\t/* Avoid space-before-tab warning */\n+\t\t\tif (c == ' ' && (len < 2 || line[1] == '\\t' ||\n+\t\t\t\t\t line[1] == '\\r' || line[1] == '\\n')) {\n+\t\t\t\tline++;\n+\t\t\t\tlen--;\n+\t\t\t}\n \t\t}\n \t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK,\n-- \n2.17.0.395.g6a618d6010f.dirty\n\n\n"},{"id":"346536","messageId":"153425de2df882395e0f54d0a8fb58b7298e9e1b.1525361419.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"[PATCH 14/18] diff: add an internal option to dual-color diffs of diffs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T15:30:59Z","receivedAt":"2018-05-03T15:31:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"When diffing diffs, it can be quite daunting to figure out what the heck\nis going on, as there are nested +/- signs.\n\nLet's make this easier by adding a flag in diff_options that allows\ncolor-coding the outer diff sign with inverted colors, so that the\npreimage and postimage is colored like the diff it is.\n\nOf course, this really only makes sense when the preimage and postimage\n*are* diffs. So let's not expose this flag via a command-line option for\nnow.\n\nThis is a feature that was invented by git-tbdiff, and it will be used\nin `branch-diff` in the next commit.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n diff.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++---------\n diff.h |  5 ++++-\n 2 files changed, 59 insertions(+), 11 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex f1bda0db3f5..98a41e88620 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -67,6 +67,8 @@ static char diff_colors[][COLOR_MAXLEN] = {\n \tGIT_COLOR_BOLD_YELLOW,\t/* NEW_MOVED ALTERNATIVE */\n \tGIT_COLOR_FAINT,\t/* NEW_MOVED_DIM */\n \tGIT_COLOR_FAINT_ITALIC,\t/* NEW_MOVED_ALTERNATIVE_DIM */\n+\tGIT_COLOR_INV_RED,\t/* OLD_INV */\n+\tGIT_COLOR_INV_GREEN,\t/* NEW_INV */\n };\n \n static NORETURN void die_want_option(const char *option_name)\n@@ -108,6 +110,10 @@ static int parse_diff_color_slot(const char *var)\n \t\treturn DIFF_FILE_NEW_MOVED_DIM;\n \tif (!strcasecmp(var, \"newmovedalternativedimmed\"))\n \t\treturn DIFF_FILE_NEW_MOVED_ALT_DIM;\n+\tif (!strcasecmp(var, \"oldinv\"))\n+\t\treturn DIFF_FILE_OLD_INV;\n+\tif (!strcasecmp(var, \"newinv\"))\n+\t\treturn DIFF_FILE_NEW_INV;\n \treturn -1;\n }\n \n@@ -577,7 +583,10 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res\n \tint nofirst;\n \tFILE *file = o->file;\n \n-\tfputs(diff_line_prefix(o), file);\n+\tif (first)\n+\t\tfputs(diff_line_prefix(o), file);\n+\telse if (!len)\n+\t\treturn;\n \n \tif (len == 0) {\n \t\thas_trailing_newline = (first == '\\n');\n@@ -596,7 +605,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res\n \n \tif (len || !nofirst) {\n \t\tfputs(set, file);\n-\t\tif (!nofirst)\n+\t\tif (first && !nofirst)\n \t\t\tfputc(first, file);\n \t\tfwrite(line, len, 1, file);\n \t\tfputs(reset, file);\n@@ -970,7 +979,8 @@ static void dim_moved_lines(struct diff_options *o)\n \n static void emit_line_ws_markup(struct diff_options *o,\n \t\t\t\tconst char *set, const char *reset,\n-\t\t\t\tconst char *line, int len, char sign,\n+\t\t\t\tconst char *line, int len,\n+\t\t\t\tconst char *set_sign, char sign,\n \t\t\t\tunsigned ws_rule, int blank_at_eof)\n {\n \tconst char *ws = NULL;\n@@ -981,14 +991,18 @@ static void emit_line_ws_markup(struct diff_options *o,\n \t\t\tws = NULL;\n \t}\n \n-\tif (!ws)\n+\tif (!ws && set_sign == set)\n \t\temit_line_0(o, set, reset, sign, line, len);\n-\telse if (blank_at_eof)\n+\telse if (!ws) {\n+\t\t/* Emit just the prefix, then the rest. */\n+\t\temit_line_0(o, set_sign, reset, sign, \"\", 0);\n+\t\temit_line_0(o, set, reset, 0, line, len);\n+\t} else if (blank_at_eof)\n \t\t/* Blank line at EOF - paint '+' as well */\n \t\temit_line_0(o, ws, reset, sign, line, len);\n \telse {\n \t\t/* Emit just the prefix, then the rest. */\n-\t\temit_line_0(o, set, reset, sign, \"\", 0);\n+\t\temit_line_0(o, set_sign, reset, sign, \"\", 0);\n \t\tws_check_emit(line, len, ws_rule,\n \t\t\t      o->file, set, reset, ws);\n \t}\n@@ -998,7 +1012,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\t\t\t struct emitted_diff_symbol *eds)\n {\n \tstatic const char *nneof = \" No newline at end of file\\n\";\n-\tconst char *context, *reset, *set, *meta, *fraginfo;\n+\tconst char *context, *reset, *set, *set_sign, *meta, *fraginfo;\n \tstruct strbuf sb = STRBUF_INIT;\n \n \tenum diff_symbol s = eds->s;\n@@ -1038,7 +1052,16 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \tcase DIFF_SYMBOL_CONTEXT:\n \t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n-\t\temit_line_ws_markup(o, set, reset, line, len, ' ',\n+\t\tset_sign = set;\n+\t\tif (o->flags.dual_color_diffed_diffs) {\n+\t\t\tchar c = !len ? 0 : line[0];\n+\n+\t\t\tif (c == '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n+\t\t\telse if (c == '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n+\t\t}\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',\n \t\t\t\t    flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);\n \t\tbreak;\n \tcase DIFF_SYMBOL_PLUS:\n@@ -1065,7 +1088,18 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n \t\t}\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n-\t\temit_line_ws_markup(o, set, reset, line, len, '+',\n+\t\tif (!o->flags.dual_color_diffed_diffs)\n+\t\t\tset_sign = set;\n+\t\telse {\n+\t\t\tchar c = !len ? 0 : line[0];\n+\n+\t\t\tset_sign = diff_get_color_opt(o, DIFF_FILE_NEW_INV);\n+\t\t\tif (c == '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n+\t\t\telse if (c != '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t}\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK,\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);\n \t\tbreak;\n@@ -1093,7 +1127,18 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n \t\t}\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n-\t\temit_line_ws_markup(o, set, reset, line, len, '-',\n+\t\tif (!o->flags.dual_color_diffed_diffs)\n+\t\t\tset_sign = set;\n+\t\telse {\n+\t\t\tchar c = !len ? 0 : line[0];\n+\n+\t\t\tset_sign = diff_get_color_opt(o, DIFF_FILE_OLD_INV);\n+\t\t\tif (c == '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n+\t\t\telse if (c != '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t}\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '-',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);\n \t\tbreak;\n \tcase DIFF_SYMBOL_WORDS_PORCELAIN:\ndiff --git a/diff.h b/diff.h\nindex 0dd6a71af60..c3e5d27967c 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -95,6 +95,7 @@ struct diff_flags {\n \tunsigned default_follow_renames:1;\n \tunsigned stat_with_summary:1;\n \tunsigned suppress_diff_headers:1;\n+\tunsigned dual_color_diffed_diffs:1;\n };\n \n static inline void diff_flags_or(struct diff_flags *a,\n@@ -242,7 +243,9 @@ enum color_diff {\n \tDIFF_FILE_NEW_MOVED = 13,\n \tDIFF_FILE_NEW_MOVED_ALT = 14,\n \tDIFF_FILE_NEW_MOVED_DIM = 15,\n-\tDIFF_FILE_NEW_MOVED_ALT_DIM = 16\n+\tDIFF_FILE_NEW_MOVED_ALT_DIM = 16,\n+\tDIFF_FILE_OLD_INV = 17,\n+\tDIFF_FILE_NEW_INV = 18\n };\n const char *diff_get_color(int diff_use_color, enum color_diff ix);\n #define diff_get_color_opt(o, ix) \\\n-- \n2.17.0.395.g6a618d6010f.dirty\n\n\n"},{"id":"346538","messageId":"71b00bbf-07e7-11e1-046b-f0241b82ebd3@ramsayjones.plus.com","threadId":"48405","inReplyTo":"8bc517e35d4842f8d9d98f3b99adb9475d6db2d2.1525361419.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Ramsay Jones","fromEmail":"ramsay@ramsayjones.plus.com","sentAt":"2018-05-03T16:10:11Z","receivedAt":"2018-05-03T16:10:17Z","isPatch":true,"sender":{"key":"ramsay@ramsayjones.plus.com","avatar":"https://avatars.githubusercontent.com/u/33702710?v=4"},"body":"\n\nOn 03/05/18 16:30, Johannes Schindelin wrote:\n> This builtin does not do a whole lot so far, apart from showing a usage\n> that is oddly similar to that of `git tbdiff`. And for a good reason:\n> the next commits will turn `branch-diff` into a full-blown replacement\n> for `tbdiff`.\n> \n> At this point, we ignore tbdiff's color options, as they will all be\n> implemented later and require some patches to the diff machinery.\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  .gitignore            |  1 +\n>  Makefile              |  1 +\n>  builtin.h             |  1 +\n>  builtin/branch-diff.c | 40 ++++++++++++++++++++++++++++++++++++++++\n>  command-list.txt      |  1 +\n>  git.c                 |  1 +\n>  6 files changed, 45 insertions(+)\n>  create mode 100644 builtin/branch-diff.c\n> \n> diff --git a/.gitignore b/.gitignore\n> index 833ef3b0b78..1346a64492f 100644\n> --- a/.gitignore\n> +++ b/.gitignore\n> @@ -20,6 +20,7 @@\n>  /git-bisect--helper\n>  /git-blame\n>  /git-branch\n> +/git-branch-diff\n>  /git-bundle\n>  /git-cat-file\n>  /git-check-attr\n> diff --git a/Makefile b/Makefile\n> index 96f2e76a904..9b1984776d8 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -953,6 +953,7 @@ BUILTIN_OBJS += builtin/archive.o\n>  BUILTIN_OBJS += builtin/bisect--helper.o\n>  BUILTIN_OBJS += builtin/blame.o\n>  BUILTIN_OBJS += builtin/branch.o\n> +BUILTIN_OBJS += builtin/branch-diff.o\n>  BUILTIN_OBJS += builtin/bundle.o\n>  BUILTIN_OBJS += builtin/cat-file.o\n>  BUILTIN_OBJS += builtin/check-attr.o\n> diff --git a/builtin.h b/builtin.h\n> index 42378f3aa47..e1c4d2a529a 100644\n> --- a/builtin.h\n> +++ b/builtin.h\n> @@ -135,6 +135,7 @@ extern int cmd_archive(int argc, const char **argv, const char *prefix);\n>  extern int cmd_bisect__helper(int argc, const char **argv, const char *prefix);\n>  extern int cmd_blame(int argc, const char **argv, const char *prefix);\n>  extern int cmd_branch(int argc, const char **argv, const char *prefix);\n> +extern int cmd_branch_diff(int argc, const char **argv, const char *prefix);\n>  extern int cmd_bundle(int argc, const char **argv, const char *prefix);\n>  extern int cmd_cat_file(int argc, const char **argv, const char *prefix);\n>  extern int cmd_checkout(int argc, const char **argv, const char *prefix);\n> diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n> new file mode 100644\n> index 00000000000..97266cd326d\n> --- /dev/null\n> +++ b/builtin/branch-diff.c\n> @@ -0,0 +1,40 @@\n> +#include \"cache.h\"\n> +#include \"parse-options.h\"\n> +\n> +static const char * const builtin_branch_diff_usage[] = {\n> +\tN_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n\ns/rebase--helper/branch-diff/\n\nATB,\nRamsay Jones\n\n"},{"id":"346541","messageId":"24af8d6e-da6f-2d0b-22a0-6a2a215ac55f@ramsayjones.plus.com","threadId":"48405","inReplyTo":"ec51c71779a325263c1b705a6b1bfb003fcd528a.1525361419.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 03/18] branch-diff: first rudimentary implementation","fromName":"Ramsay Jones","fromEmail":"ramsay@ramsayjones.plus.com","sentAt":"2018-05-03T16:30:57Z","receivedAt":"2018-05-03T16:31:02Z","isPatch":true,"sender":{"key":"ramsay@ramsayjones.plus.com","avatar":"https://avatars.githubusercontent.com/u/33702710?v=4"},"body":"\n\nOn 03/05/18 16:30, Johannes Schindelin wrote:\n> At this stage, `git branch-diff` can determine corresponding commits of\n> two related commit ranges. This makes use of the recently introduced\n> implementation of the Hungarian algorithm.\n> \n> The core of this patch is a straight port of the ideas of tbdiff, the\n> seemingly dormant project at https://github.com/trast/tbdiff.\n> \n> The output does not at all match `tbdiff`'s output yet, as this patch\n> really concentrates on getting the patch matching part right.\n> \n> Note: due to differences in the diff algorithm (`tbdiff` uses the\n> Pythong module `difflib`, Git uses its xdiff fork), the cost matrix\n> calculated by `branch-diff` is different (but very similar) to the one\n> calculated by `tbdiff`. Therefore, it is possible that they find\n> different matching commits in corner cases (e.g. when a patch was split\n> into two patches of roughly equal length).\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  builtin/branch-diff.c | 337 +++++++++++++++++++++++++++++++++++++++++-\n>  1 file changed, 334 insertions(+), 3 deletions(-)\n> \n> diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n> index 97266cd326d..02dc06a57ca 100644\n> --- a/builtin/branch-diff.c\n> +++ b/builtin/branch-diff.c\n> @@ -1,13 +1,17 @@\n>  #include \"cache.h\"\n>  #include \"parse-options.h\"\n> +#include \"string-list.h\"\n> +#include \"run-command.h\"\n> +#include \"argv-array.h\"\n> +#include \"hashmap.h\"\n> +#include \"xdiff-interface.h\"\n> +#include \"hungarian.h\"\n>  \n>  static const char * const builtin_branch_diff_usage[] = {\n>  \tN_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n>  \tNULL\n>  };\n>  \n> -#define COLOR_DUAL_MODE 2\n> -\n\nThis #define was introduced in the previous patch, without being\nused in that patch, and is now deleted here.\n\nATB,\nRamsay Jones\n"},{"id":"346543","messageId":"CACsJy8DF8twvST0tcHfFqYWaV_0dVRCfJj-QuuCK=0h+gjJ0wQ@mail.gmail.com","threadId":"48405","inReplyTo":"8bc517e35d4842f8d9d98f3b99adb9475d6db2d2.1525361419.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2018-05-03T16:41:04Z","receivedAt":"2018-05-03T16:41:38Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, May 3, 2018 at 5:30 PM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> diff --git a/command-list.txt b/command-list.txt\n> index a1fad28fd82..c89ac8f417f 100644\n> --- a/command-list.txt\n> +++ b/command-list.txt\n> @@ -19,6 +19,7 @@ git-archive                             mainporcelain\n>  git-bisect                              mainporcelain           info\n>  git-blame                               ancillaryinterrogators\n>  git-branch                              mainporcelain           history\n> +git-branch-diff                         mainporcelain           info\n\nMaking it part of \"git help\" with the info keywords at this stage may\nbe premature. \"git help\" is about _common_ commands and we don't know\n(yet) how popular this will be.\n\n(I'm not complaining though, I almost wrote \"what witchcraft is this\nand where can I get it\" when I see branch-diff output mentioned in\nAEvar's mail)\n-- \nDuy\n"},{"id":"346544","messageId":"CAGZ79kZAidPafdfu1NGwwpVo1Vy=vKOV+EREE2=-ct_sbo7Gkg@mail.gmail.com","threadId":"48405","inReplyTo":"8bc517e35d4842f8d9d98f3b99adb9475d6db2d2.1525361419.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-05-03T16:43:42Z","receivedAt":"2018-05-03T16:43:48Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Hi Johannes,\n\nOn Thu, May 3, 2018 at 8:30 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> This builtin does not do a whole lot so far, apart from showing a usage\n> that is oddly similar to that of `git tbdiff`. And for a good reason:\n> the next commits will turn `branch-diff` into a full-blown replacement\n> for `tbdiff`.\n\nWhile I appreciate the 1:1 re-implementation, I'll comment as if this\nwas a newly invented tool, questioning design choices. They are probably\nchosen pretty well, and fudge facotrs as below are at tweaked to a reasonable\nfactor, but I'll try to look with fresh eyes.\n\n>\n> At this point, we ignore tbdiff's color options, as they will all be\n> implemented later and require some patches to the diff machinery.\n\nSpeaking of colors, for origin/sb/blame-color Junio hinted at re-using\ncyan for \"uninteresting\" parts to deliver a consistent color scheme for\nGit. Eventually he dreams of having 2 layers of indirection IIUC, with\n    \"uninteresting\" -> cyan\n    \"repeated lines in blame\" -> uninteresting\n\nMaybe we can fit the coloring of this tool in this scheme, too?\n\n> +       double creation_weight = 0.6;\n\nI wondered if we use doubles in Gits code base at all,\nand I found\n\nkhash.h:59:static const double __ac_HASH_UPPER = 0.77;\npack-bitmap-write.c:248:        static const double\nREUSE_BITMAP_THRESHOLD = 0.2;\npack-bitmap.c:751:      static const double REUSE_PERCENT = 0.9;\n\nall other occurrences of `git grep double` are mentioning it in other\ncontexts (e.g. \"double linked list\" or comments).\n\nWhen implementing diff heuristics in 433860f3d0b (diff: improve\npositioning of add/delete blocks in diffs, 2016-09-05), Michael broke\nit down to fixed integers instead of floating point.\n\nDo we need to dynamic of a floating point, or would a rather small range\nsuffice here? (Also see rename detection settings, that take percents as\nintegers)\n\nThanks,\nStefan\n"},{"id":"346546","messageId":"87r2msy8yf.fsf@evledraar.gmail.com","threadId":"48405","inReplyTo":"fe12b99a0b4f78ab75fcfbcf51c5edffb190c4e8.1525361419.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 11/18] branch-diff: add tests","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2018-05-03T16:56:08Z","receivedAt":"2018-05-03T16:56:15Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, May 03 2018, Johannes Schindelin wrote:\n\n> *before* the existing emtpy line. And apparently xdiff picks a different\n\ns/emtpy/empty/\n"},{"id":"346547","messageId":"CAGZ79kYzZkdZKdR4hMK0V6D6=cm4damct01MGidGA0g-dtW+gQ@mail.gmail.com","threadId":"48405","inReplyTo":"ec51c71779a325263c1b705a6b1bfb003fcd528a.1525361419.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 03/18] branch-diff: first rudimentary implementation","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-05-03T17:06:29Z","receivedAt":"2018-05-03T17:06:33Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Thu, May 3, 2018 at 8:30 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n\n> Note: due to differences in the diff algorithm (`tbdiff` uses the\n> Pythong module `difflib`, Git uses its xdiff fork), the cost matrix\n> calculated by `branch-diff` is different (but very similar) to the one\n> calculated by `tbdiff`. Therefore, it is possible that they find\n> different matching commits in corner cases (e.g. when a patch was split\n> into two patches of roughly equal length).\n\nDoes that mean, we may want to tweak the underlying diff parameters for\nthis special use case eventually?\n\n>\n> -#define COLOR_DUAL_MODE 2\n> -\n\nLeave this out in the first patch?\n\n> @@ -19,6 +23,279 @@ static int parse_creation_weight(const struct option *opt, const char *arg,\n>         return 0;\n>  }\n>\n> +struct patch_util {\n> +       /* For the search for an exact match */\n> +       struct hashmap_entry e;\n> +       const char *diff, *patch;\n> +\n> +       int i;\n> +       int diffsize;\n> +       size_t diff_offset;\n> +       /* the index of the matching item in the other branch, or -1 */\n> +       int matching;\n> +       struct object_id oid;\n> +};\n> +\n> +/*\n> + * Reads the patches into a string list, with the `util` field being populated\n> + * as struct object_id (will need to be free()d).\n> + */\n> +static int read_patches(const char *range, struct string_list *list)\n> +{\n> +       struct child_process cp = CHILD_PROCESS_INIT;\n> +       FILE *in;\n> +       struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;\n> +       struct patch_util *util = NULL;\n> +       int in_header = 1;\n> +\n> +       argv_array_pushl(&cp.args, \"log\", \"--no-color\", \"-p\", \"--no-merges\",\n> +                       \"--reverse\", \"--date-order\", \"--decorate=no\",\n> +                       \"--no-abbrev-commit\", range,\n> +                       NULL);\n> +       cp.out = -1;\n> +       cp.no_stdin = 1;\n> +       cp.git_cmd = 1;\n> +\n> +       if (start_command(&cp))\n> +               return error_errno(_(\"could not start `log`\"));\n> +       in = fdopen(cp.out, \"r\");\n> +       if (!in) {\n> +               error_errno(_(\"could not read `log` output\"));\n> +               finish_command(&cp);\n> +               return -1;\n> +       }\n\nWith the implementation of --color-moved, there is an option\nto buffer all diff output in memory e6e045f8031 (diff.c: buffer\nall output if asked to, 2017-06-29), so I posit that running this\ndiff in-core may be \"not too hard\". Famous last words.\n\nIn addition to that patch, we'd have to buffer commit messages\nand buffer multiple commits, as that only buffers a diff of a single\ncommit.\n\nThe benefit would be no invocation of new processes, letting us\ndo more in core. This would allow for tweaking revision walking\ninternally, e.g. passing of options to this command such as rename\ndetection factors, can be passed through easily without the need\nof translating it back to the command line.\nLet's read on.\n\n> +\n> +               if (starts_with(line.buf, \"diff --git\")) {\n\nWhen using the internal buffers, you would not need to\nstring compare, but could just check for the\nDIFF_SYMBOL_HEADER.\n\n> +               } else if (starts_with(line.buf, \"@@ \"))\n> +                       strbuf_addstr(&buf, \"@@\");\n\nSo we omit line information for hunks. Makes sense,\nthough then we could also skip the \"index ...\" lines?\n\nStefan\n"},{"id":"346548","messageId":"CAGZ79kY_kXpiXScjE+cNWRN1r71B3KkGNGZQjCHpKNe+tdMipw@mail.gmail.com","threadId":"48405","inReplyTo":"fe12b99a0b4f78ab75fcfbcf51c5edffb190c4e8.1525361419.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 11/18] branch-diff: add tests","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-05-03T17:11:50Z","receivedAt":"2018-05-03T17:11:54Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Thu, May 3, 2018 at 8:30 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> From: Thomas Rast <tr@thomasrast.ch>\n>\n> These are essentially lifted from https://github.com/trast/tbdiff, with\n> light touch-ups to account for the new command name.\n>\n> Apart from renaming `tbdiff` to `branch-diff`, only one test case needed\n> to be adjusted: 11 - 'changed message'.\n>\n> The underlying reason it had to be adjusted is that diff generation is\n> sometimes ambiguous. In this case, a comment line and an empty line are\n> added, but it is ambiguous whether they were added after the existing\n> empty line, or whether an empty line and the comment line are added\n> *before* the existing emtpy line. And apparently xdiff picks a different\n> option here than Python's difflib.\n\nI think that is the fallout of the diff heuristics. If you are keen on\na 1:1 port, you can disable the diff sliding heuristics and it should produce\nthe same diff with trailing new lines.\n"},{"id":"346558","messageId":"87po2cy5qd.fsf@evledraar.gmail.com","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2018-05-03T18:05:46Z","receivedAt":"2018-05-03T18:05:52Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, May 03 2018, Johannes Schindelin wrote:\n\n> The incredibly useful `git-tbdiff` tool to compare patch series (say, to see\n> what changed between two iterations sent to the Git mailing list) is slightly\n> less useful for this developer due to the fact that it requires the `hungarian`\n> and `numpy` Python packages which are for some reason really hard to build in\n> MSYS2. So hard that I even had to give up, because it was simply easier to\n> reimplement the whole shebang as a builtin command.\n>\n> The project at https://github.com/trast/tbdiff seems to be dormant, anyway.\n> Funny (and true) story: I looked at the open Pull Requests to see how active\n> that project is, only to find to my surprise that I had submitted one in August\n> 2015, and that it was still unanswered let alone merged.\n\nI've been using branch-diff and haven't found issues with it yet, it\nworks like tbdiff but better. Faster, uses the same diff as git\n(better), and spews to the pager by default.\n"},{"id":"346574","messageId":"nycvar.QRO.7.76.6.1805032224150.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"71b00bbf-07e7-11e1-046b-f0241b82ebd3@ramsayjones.plus.com","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T20:25:28Z","receivedAt":"2018-05-03T20:25:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Ramsay,\n\nOn Thu, 3 May 2018, Ramsay Jones wrote:\n\n> On 03/05/18 16:30, Johannes Schindelin wrote:\n> > This builtin does not do a whole lot so far, apart from showing a usage\n> > that is oddly similar to that of `git tbdiff`. And for a good reason:\n> > the next commits will turn `branch-diff` into a full-blown replacement\n> > for `tbdiff`.\n> > \n> > At this point, we ignore tbdiff's color options, as they will all be\n> > implemented later and require some patches to the diff machinery.\n> > \n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> >  .gitignore            |  1 +\n> >  Makefile              |  1 +\n> >  builtin.h             |  1 +\n> >  builtin/branch-diff.c | 40 ++++++++++++++++++++++++++++++++++++++++\n> >  command-list.txt      |  1 +\n> >  git.c                 |  1 +\n> >  6 files changed, 45 insertions(+)\n> >  create mode 100644 builtin/branch-diff.c\n> > \n> > diff --git a/.gitignore b/.gitignore\n> > index 833ef3b0b78..1346a64492f 100644\n> > --- a/.gitignore\n> > +++ b/.gitignore\n> > @@ -20,6 +20,7 @@\n> >  /git-bisect--helper\n> >  /git-blame\n> >  /git-branch\n> > +/git-branch-diff\n> >  /git-bundle\n> >  /git-cat-file\n> >  /git-check-attr\n> > diff --git a/Makefile b/Makefile\n> > index 96f2e76a904..9b1984776d8 100644\n> > --- a/Makefile\n> > +++ b/Makefile\n> > @@ -953,6 +953,7 @@ BUILTIN_OBJS += builtin/archive.o\n> >  BUILTIN_OBJS += builtin/bisect--helper.o\n> >  BUILTIN_OBJS += builtin/blame.o\n> >  BUILTIN_OBJS += builtin/branch.o\n> > +BUILTIN_OBJS += builtin/branch-diff.o\n> >  BUILTIN_OBJS += builtin/bundle.o\n> >  BUILTIN_OBJS += builtin/cat-file.o\n> >  BUILTIN_OBJS += builtin/check-attr.o\n> > diff --git a/builtin.h b/builtin.h\n> > index 42378f3aa47..e1c4d2a529a 100644\n> > --- a/builtin.h\n> > +++ b/builtin.h\n> > @@ -135,6 +135,7 @@ extern int cmd_archive(int argc, const char **argv, const char *prefix);\n> >  extern int cmd_bisect__helper(int argc, const char **argv, const char *prefix);\n> >  extern int cmd_blame(int argc, const char **argv, const char *prefix);\n> >  extern int cmd_branch(int argc, const char **argv, const char *prefix);\n> > +extern int cmd_branch_diff(int argc, const char **argv, const char *prefix);\n> >  extern int cmd_bundle(int argc, const char **argv, const char *prefix);\n> >  extern int cmd_cat_file(int argc, const char **argv, const char *prefix);\n> >  extern int cmd_checkout(int argc, const char **argv, const char *prefix);\n> > diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n> > new file mode 100644\n> > index 00000000000..97266cd326d\n> > --- /dev/null\n> > +++ b/builtin/branch-diff.c\n> > @@ -0,0 +1,40 @@\n> > +#include \"cache.h\"\n> > +#include \"parse-options.h\"\n> > +\n> > +static const char * const builtin_branch_diff_usage[] = {\n> > +\tN_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n> \n> s/rebase--helper/branch-diff/\n\nWhoops!\n\nBTW funny side note: when I saw that you replied, I instinctively thought\n\"oh no, I forgot to mark a function as `static`!\" ;-)\n\nThank you for helping me improve the patches,\nDscho\n"},{"id":"346575","messageId":"nycvar.QRO.7.76.6.1805032229050.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CACsJy8DF8twvST0tcHfFqYWaV_0dVRCfJj-QuuCK=0h+gjJ0wQ@mail.gmail.com","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T20:30:23Z","receivedAt":"2018-05-03T20:30:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Duy,\n\nOn Thu, 3 May 2018, Duy Nguyen wrote:\n\n> On Thu, May 3, 2018 at 5:30 PM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > diff --git a/command-list.txt b/command-list.txt\n> > index a1fad28fd82..c89ac8f417f 100644\n> > --- a/command-list.txt\n> > +++ b/command-list.txt\n> > @@ -19,6 +19,7 @@ git-archive                             mainporcelain\n> >  git-bisect                              mainporcelain           info\n> >  git-blame                               ancillaryinterrogators\n> >  git-branch                              mainporcelain           history\n> > +git-branch-diff                         mainporcelain           info\n> \n> Making it part of \"git help\" with the info keywords at this stage may\n> be premature. \"git help\" is about _common_ commands and we don't know\n> (yet) how popular this will be.\n\nMakes sense. I removed the `mainporcelain` keyword locally.\n\n> (I'm not complaining though, I almost wrote \"what witchcraft is this\n> and where can I get it\" when I see branch-diff output mentioned in\n> AEvar's mail)\n\nYeah, it is pretty neat.\n\nCiao,\nDscho\n"},{"id":"346576","messageId":"nycvar.QRO.7.76.6.1805032232080.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805032229050.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T20:32:38Z","receivedAt":"2018-05-03T20:32:56Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Duy,\n\nOn Thu, 3 May 2018, Johannes Schindelin wrote:\n\n> On Thu, 3 May 2018, Duy Nguyen wrote:\n> \n> > On Thu, May 3, 2018 at 5:30 PM, Johannes Schindelin\n> > <johannes.schindelin@gmx.de> wrote:\n> > > diff --git a/command-list.txt b/command-list.txt\n> > > index a1fad28fd82..c89ac8f417f 100644\n> > > --- a/command-list.txt\n> > > +++ b/command-list.txt\n> > > @@ -19,6 +19,7 @@ git-archive                             mainporcelain\n> > >  git-bisect                              mainporcelain           info\n> > >  git-blame                               ancillaryinterrogators\n> > >  git-branch                              mainporcelain           history\n> > > +git-branch-diff                         mainporcelain           info\n> > \n> > Making it part of \"git help\" with the info keywords at this stage may\n> > be premature. \"git help\" is about _common_ commands and we don't know\n> > (yet) how popular this will be.\n> \n> Makes sense. I removed the `mainporcelain` keyword locally.\n\nOn second thought, I *think* you meant to imply that I should remove that\nline altogether. Will do that now.\n\nCiao,\nDscho\n"},{"id":"346577","messageId":"nycvar.QRO.7.76.6.1805032227520.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAGZ79kZAidPafdfu1NGwwpVo1Vy=vKOV+EREE2=-ct_sbo7Gkg@mail.gmail.com","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T20:42:25Z","receivedAt":"2018-05-03T20:42:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Thu, 3 May 2018, Stefan Beller wrote:\n\n> On Thu, May 3, 2018 at 8:30 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > This builtin does not do a whole lot so far, apart from showing a\n> > usage that is oddly similar to that of `git tbdiff`. And for a good\n> > reason: the next commits will turn `branch-diff` into a full-blown\n> > replacement for `tbdiff`.\n> \n> While I appreciate the 1:1 re-implementation, I'll comment as if this\n> was a newly invented tool, questioning design choices. They are probably\n> chosen pretty well, and fudge facotrs as below are at tweaked to a\n> reasonable factor, but I'll try to look with fresh eyes.\n\nAbsolutely. While tbdiff got some testing over time, it has definitely not\ngotten as much exposure as branch-diff hopefully will.\n\nBTW I chose a different command name on purpose, so that we are free to\nchange the design and not harm existing tbdiff users.\n\n> > At this point, we ignore tbdiff's color options, as they will all be\n> > implemented later and require some patches to the diff machinery.\n> \n> Speaking of colors, for origin/sb/blame-color Junio hinted at re-using\n> cyan for \"uninteresting\" parts to deliver a consistent color scheme for\n> Git. Eventually he dreams of having 2 layers of indirection IIUC, with\n>     \"uninteresting\" -> cyan\n>     \"repeated lines in blame\" -> uninteresting\n> \n> Maybe we can fit the coloring of this tool in this scheme, too?\n\nSure. So you mean I should use cyan for... what part of the colored\noutput? ;-)\n\n> > +       double creation_weight = 0.6;\n> \n> I wondered if we use doubles in Gits code base at all,\n> and I found\n> \n> khash.h:59:static const double __ac_HASH_UPPER = 0.77;\n> pack-bitmap-write.c:248:        static const double\n> REUSE_BITMAP_THRESHOLD = 0.2;\n> pack-bitmap.c:751:      static const double REUSE_PERCENT = 0.9;\n> \n> all other occurrences of `git grep double` are mentioning it in other\n> contexts (e.g. \"double linked list\" or comments).\n> \n> When implementing diff heuristics in 433860f3d0b (diff: improve\n> positioning of add/delete blocks in diffs, 2016-09-05), Michael broke\n> it down to fixed integers instead of floating point.\n> \n> Do we need to dynamic of a floating point, or would a rather small range\n> suffice here? (Also see rename detection settings, that take percents as\n> integers)\n\nI guess you are right, and we do not need floats. It was just very, very\nconvenient to do that instead of using integers because\n\n- I already had the Jonker-Volgenant implementation \"lying around\" from my\n  previous life as an image processing expert, using doubles (but it was\n  in Java, not in C, so I quickly converted it for branch-diff).\n\n- I was actually not paying attention whether divisions are a thing in the\n  algorithm. From a cursory glance, it would appear that we are never\n  dividing in hungarian.c, so theoretically integers should be fine.\n\n- using doubles neatly side-steps the overflow problem. If I use integers\n  instead, I always will have to worry what to do if, say, adding\n  `INT_MAX` to `INT_MAX`.\n\nI am particularly worried about that last thing: it could easily lead to\nincorrect results if we blindly, say, pretend that `INT_MAX + INT_MAX ==\nINT_MAX` for the purpose of avoiding overflows.\n\nIf, however, I misunderstood and you are only concerned about using\n*double-precision* floating point numbers, and would suggest using `float`\ntyped variables instead, that would be totally cool with me.\n\nCiao,\nDscho\n"},{"id":"346578","messageId":"nycvar.QRO.7.76.6.1805032242510.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"24af8d6e-da6f-2d0b-22a0-6a2a215ac55f@ramsayjones.plus.com","subject":"Re: [PATCH 03/18] branch-diff: first rudimentary implementation","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T20:44:59Z","receivedAt":"2018-05-03T20:45:09Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Ramsay,\n\nOn Thu, 3 May 2018, Ramsay Jones wrote:\n\n> On 03/05/18 16:30, Johannes Schindelin wrote:\n> > At this stage, `git branch-diff` can determine corresponding commits of\n> > two related commit ranges. This makes use of the recently introduced\n> > implementation of the Hungarian algorithm.\n> > \n> > The core of this patch is a straight port of the ideas of tbdiff, the\n> > seemingly dormant project at https://github.com/trast/tbdiff.\n> > \n> > The output does not at all match `tbdiff`'s output yet, as this patch\n> > really concentrates on getting the patch matching part right.\n> > \n> > Note: due to differences in the diff algorithm (`tbdiff` uses the\n> > Pythong module `difflib`, Git uses its xdiff fork), the cost matrix\n> > calculated by `branch-diff` is different (but very similar) to the one\n> > calculated by `tbdiff`. Therefore, it is possible that they find\n> > different matching commits in corner cases (e.g. when a patch was split\n> > into two patches of roughly equal length).\n> > \n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> >  builtin/branch-diff.c | 337 +++++++++++++++++++++++++++++++++++++++++-\n> >  1 file changed, 334 insertions(+), 3 deletions(-)\n> > \n> > diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n> > index 97266cd326d..02dc06a57ca 100644\n> > --- a/builtin/branch-diff.c\n> > +++ b/builtin/branch-diff.c\n> > @@ -1,13 +1,17 @@\n> >  #include \"cache.h\"\n> >  #include \"parse-options.h\"\n> > +#include \"string-list.h\"\n> > +#include \"run-command.h\"\n> > +#include \"argv-array.h\"\n> > +#include \"hashmap.h\"\n> > +#include \"xdiff-interface.h\"\n> > +#include \"hungarian.h\"\n> >  \n> >  static const char * const builtin_branch_diff_usage[] = {\n> >  \tN_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n> >  \tNULL\n> >  };\n> >  \n> > -#define COLOR_DUAL_MODE 2\n> > -\n> \n> This #define was introduced in the previous patch, without being\n> used in that patch, and is now deleted here.\n\nDarn, darn, darn. You know, this macro simply won't die. I tried to kill\nit off but it keeps showing its ugly head everywhere.\n\nI *think* I finally killed it for good.\n\nThanks!\nDscho\n"},{"id":"346580","messageId":"nycvar.QRO.7.76.6.1805032246040.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAGZ79kYzZkdZKdR4hMK0V6D6=cm4damct01MGidGA0g-dtW+gQ@mail.gmail.com","subject":"Re: [PATCH 03/18] branch-diff: first rudimentary implementation","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T21:01:33Z","receivedAt":"2018-05-03T21:01:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Thu, 3 May 2018, Stefan Beller wrote:\n\n> On Thu, May 3, 2018 at 8:30 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> \n> > Note: due to differences in the diff algorithm (`tbdiff` uses the\n> > Pythong module `difflib`, Git uses its xdiff fork), the cost matrix\n> > calculated by `branch-diff` is different (but very similar) to the one\n> > calculated by `tbdiff`. Therefore, it is possible that they find\n> > different matching commits in corner cases (e.g. when a patch was\n> > split into two patches of roughly equal length).\n> \n> Does that mean, we may want to tweak the underlying diff parameters for\n> this special use case eventually?\n\nI don't think that will be necessary. Generating diffs is an ambiguous\nbusiness, after all, and we just have to live with the consequence that it\nmight even be possible that the cost is non-symmetric, i.e. that the\nlength (i.e. line count) of the diff is different depending whether we\ncompare old patch to new patch vs new patch to old patch.\n\nIf the result changes due to these vagaries, it means that there is no\nsingle one good answer to the question which old/new commits form a pair.\nI would expect that only to happen if a commit with a lengthy diff is cut\ninto two commits whose diffs have roughly equal lengths (so that the\ndifference of the commit message won't matter that much).\n\n> > -#define COLOR_DUAL_MODE 2\n> > -\n> \n> Leave this out in the first patch?\n\nYep, as Ramsay said.\n\n> > @@ -19,6 +23,279 @@ static int parse_creation_weight(const struct option *opt, const char *arg,\n> >         return 0;\n> >  }\n> >\n> > +struct patch_util {\n> > +       /* For the search for an exact match */\n> > +       struct hashmap_entry e;\n> > +       const char *diff, *patch;\n> > +\n> > +       int i;\n> > +       int diffsize;\n> > +       size_t diff_offset;\n> > +       /* the index of the matching item in the other branch, or -1 */\n> > +       int matching;\n> > +       struct object_id oid;\n> > +};\n> > +\n> > +/*\n> > + * Reads the patches into a string list, with the `util` field being populated\n> > + * as struct object_id (will need to be free()d).\n> > + */\n> > +static int read_patches(const char *range, struct string_list *list)\n> > +{\n> > +       struct child_process cp = CHILD_PROCESS_INIT;\n> > +       FILE *in;\n> > +       struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;\n> > +       struct patch_util *util = NULL;\n> > +       int in_header = 1;\n> > +\n> > +       argv_array_pushl(&cp.args, \"log\", \"--no-color\", \"-p\", \"--no-merges\",\n> > +                       \"--reverse\", \"--date-order\", \"--decorate=no\",\n> > +                       \"--no-abbrev-commit\", range,\n> > +                       NULL);\n> > +       cp.out = -1;\n> > +       cp.no_stdin = 1;\n> > +       cp.git_cmd = 1;\n> > +\n> > +       if (start_command(&cp))\n> > +               return error_errno(_(\"could not start `log`\"));\n> > +       in = fdopen(cp.out, \"r\");\n> > +       if (!in) {\n> > +               error_errno(_(\"could not read `log` output\"));\n> > +               finish_command(&cp);\n> > +               return -1;\n> > +       }\n> \n> With the implementation of --color-moved, there is an option\n> to buffer all diff output in memory e6e045f8031 (diff.c: buffer\n> all output if asked to, 2017-06-29), so I posit that running this\n> diff in-core may be \"not too hard\". Famous last words.\n\nTrue. I *did* stumble over emitted_symbols and thought that there might be\na way to leverage it, but I did not find any existing knob, and did not\nwant to interfere with any other user case of that feature (which we may\nwant to allow combining with branch-diff one day, afer all).\n\nTo be honest, the main reason I spawn here is that I did not want to be\nbothered with resetting the commit flags after traversing the first commit\nrange. But I guess I was just too cheap and should really do it?\n\nOTOH spawning here is a lot easier than not spawning, so maybe it would be\npremature optimization?\n\n> In addition to that patch, we'd have to buffer commit messages\n> and buffer multiple commits, as that only buffers a diff of a single\n> commit.\n\n... and make sure that the moved-code logic (which is currently the only\nuser of emitted_symbols, correct?) would never be called at the same time\nas we generate the diff.\n\n> The benefit would be no invocation of new processes, letting us\n> do more in core. This would allow for tweaking revision walking\n> internally, e.g. passing of options to this command such as rename\n> detection factors, can be passed through easily without the need\n> of translating it back to the command line.\n\nOn the other hand, we can simply copy those options to the command-line\nfor `log`. Which might even be better, as e.g. `--format` changes global\nstate :-(\n\n> > +\n> > +               if (starts_with(line.buf, \"diff --git\")) {\n> \n> When using the internal buffers, you would not need to\n> string compare, but could just check for the\n> DIFF_SYMBOL_HEADER.\n\nTrue. Not sure whether the current way is *that* terrible, though, as the\n`diff` line is meant for parsing by other commands, anyway.\n\n> > +               } else if (starts_with(line.buf, \"@@ \"))\n> > +                       strbuf_addstr(&buf, \"@@\");\n> \n> So we omit line information for hunks. Makes sense,\n> though then we could also skip the \"index ...\" lines?\n\nAnd we do, in the next line:\n\n> > +               else if (line.buf[0] && !starts_with(line.buf, \"index \"))\n\nCiao,\nDscho\n"},{"id":"346581","messageId":"nycvar.QRO.7.76.6.1805032303160.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"87r2msy8yf.fsf@evledraar.gmail.com","subject":"Re: [PATCH 11/18] branch-diff: add tests","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T21:03:36Z","receivedAt":"2018-05-03T21:03:45Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Ævar,\n\nOn Thu, 3 May 2018, Ævar Arnfjörð Bjarmason wrote:\n\n> On Thu, May 03 2018, Johannes Schindelin wrote:\n> \n> > *before* the existing emtpy line. And apparently xdiff picks a different\n> \n> s/emtpy/empty/\n\nThanks for the spell check!\nDscho"},{"id":"346582","messageId":"nycvar.QRO.7.76.6.1805032303540.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAGZ79kY_kXpiXScjE+cNWRN1r71B3KkGNGZQjCHpKNe+tdMipw@mail.gmail.com","subject":"Re: [PATCH 11/18] branch-diff: add tests","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T21:05:50Z","receivedAt":"2018-05-03T21:05:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Thu, 3 May 2018, Stefan Beller wrote:\n\n> On Thu, May 3, 2018 at 8:30 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > From: Thomas Rast <tr@thomasrast.ch>\n> >\n> > These are essentially lifted from https://github.com/trast/tbdiff, with\n> > light touch-ups to account for the new command name.\n> >\n> > Apart from renaming `tbdiff` to `branch-diff`, only one test case needed\n> > to be adjusted: 11 - 'changed message'.\n> >\n> > The underlying reason it had to be adjusted is that diff generation is\n> > sometimes ambiguous. In this case, a comment line and an empty line are\n> > added, but it is ambiguous whether they were added after the existing\n> > empty line, or whether an empty line and the comment line are added\n> > *before* the existing emtpy line. And apparently xdiff picks a different\n> > option here than Python's difflib.\n> \n> I think that is the fallout of the diff heuristics. If you are keen on\n> a 1:1 port, you can disable the diff sliding heuristics and it should\n> produce the same diff with trailing new lines.\n\nI am not keen on a 1:1 port. I am fine with having xdiff generate\ndifferent diffs than Python's difflib. That's par for the course when\nrelying on a not-quite-well-defined metric.\n\nCiao,\nDscho\n"},{"id":"346583","messageId":"nycvar.QRO.7.76.6.1805032307160.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"87po2cy5qd.fsf@evledraar.gmail.com","subject":"Re: [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T21:07:53Z","receivedAt":"2018-05-03T21:08:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Ævar,\n\nOn Thu, 3 May 2018, Ævar Arnfjörð Bjarmason wrote:\n\n> On Thu, May 03 2018, Johannes Schindelin wrote:\n> \n> > The incredibly useful `git-tbdiff` tool to compare patch series (say,\n> > to see what changed between two iterations sent to the Git mailing\n> > list) is slightly less useful for this developer due to the fact that\n> > it requires the `hungarian` and `numpy` Python packages which are for\n> > some reason really hard to build in MSYS2. So hard that I even had to\n> > give up, because it was simply easier to reimplement the whole shebang\n> > as a builtin command.\n> >\n> > The project at https://github.com/trast/tbdiff seems to be dormant,\n> > anyway.  Funny (and true) story: I looked at the open Pull Requests to\n> > see how active that project is, only to find to my surprise that I had\n> > submitted one in August 2015, and that it was still unanswered let\n> > alone merged.\n> \n> I've been using branch-diff and haven't found issues with it yet, it\n> works like tbdiff but better. Faster, uses the same diff as git\n> (better), and spews to the pager by default.\n\nThanks for your enthusiasm!\nDscho"},{"id":"346584","messageId":"CAGZ79ka7RJq=tNnBsLs6wwL7Cf3hSJODM2iLW5tet-csAM20eg@mail.gmail.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805032227520.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-05-03T21:12:55Z","receivedAt":"2018-05-03T21:13:02Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Thu, May 3, 2018 at 1:42 PM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n\n>> Speaking of colors, for origin/sb/blame-color Junio hinted at re-using\n>> cyan for \"uninteresting\" parts to deliver a consistent color scheme for\n>> Git. Eventually he dreams of having 2 layers of indirection IIUC, with\n>>     \"uninteresting\" -> cyan\n>>     \"repeated lines in blame\" -> uninteresting\n>>\n>> Maybe we can fit the coloring of this tool in this scheme, too?\n>\n> Sure. So you mean I should use cyan for... what part of the colored\n> output? ;-)\n>\n\nIt is just a FYI heads up, not an actionable bikeshed painting plan. ;)\n\n>> Do we need to dynamic of a floating point, or would a rather small range\n>> suffice here? (Also see rename detection settings, that take percents as\n>> integers)\n>\n> I guess you are right, and we do not need floats. It was just very, very\n> convenient to do that instead of using integers because\n>\n> - I already had the Jonker-Volgenant implementation \"lying around\" from my\n>   previous life as an image processing expert, using doubles (but it was\n>   in Java, not in C, so I quickly converted it for branch-diff).\n>\n> - I was actually not paying attention whether divisions are a thing in the\n>   algorithm. From a cursory glance, it would appear that we are never\n>   dividing in hungarian.c, so theoretically integers should be fine.\n>\n> - using doubles neatly side-steps the overflow problem. If I use integers\n>   instead, I always will have to worry what to do if, say, adding\n>   `INT_MAX` to `INT_MAX`.\n>\n> I am particularly worried about that last thing: it could easily lead to\n> incorrect results if we blindly, say, pretend that `INT_MAX + INT_MAX ==\n> INT_MAX` for the purpose of avoiding overflows.\n>\n> If, however, I misunderstood and you are only concerned about using\n> *double-precision* floating point numbers, and would suggest using `float`\n> typed variables instead, that would be totally cool with me.\n\nSo by being worried about INT_MAX occurring, you are implying that\nwe have to worry about a large range of values, so maybe floating points\nare the best choice here.\n\nLooking through that algorithm the costs seem to be integers only\nmeasuring number of lines, so I would not be too worried about running\ninto INT_MAX problems except for the costs that are assigned INT_MAX\nexplicitly.\n\nI was more asking, if floating point is the right tool for the job.\n\nStefan\n"},{"id":"346586","messageId":"CAGZ79kZQ+mq1O_sL11jC4_Lt18nO6b_6pSPBahOqyZ+izrRm7w@mail.gmail.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805032246040.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 03/18] branch-diff: first rudimentary implementation","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-05-03T21:19:40Z","receivedAt":"2018-05-03T21:19:45Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"> To be honest, the main reason I spawn here is that I did not want to be\n> bothered with resetting the commit flags after traversing the first commit\n> range. But I guess I was just too cheap and should really do it?\n\nOh right, you'd have to do multiple revision walks.\n\n> OTOH spawning here is a lot easier than not spawning, so maybe it would be\n> premature optimization?\n\nMost likely.\n\n>\n>> In addition to that patch, we'd have to buffer commit messages\n>> and buffer multiple commits, as that only buffers a diff of a single\n>> commit.\n>\n> ... and make sure that the moved-code logic (which is currently the only\n> user of emitted_symbols, correct?) would never be called at the same time\n> as we generate the diff.\n\nThe moved detection is all part of the flags of an emitted symbol.\n\nBy design the emitted symbol has easy access to the raw line of the output,\nwhich made it easy for the move detection to work on the lines. AFAICT this\nis also desired here as lines are put into a hashmap for comparisons.\n(and having it colored differently would make finding the same line\ncomplex using hashmaps)\n\nI just entertain the thought of having move detection active in a\nbranch-diff. That would be really cool actually.\n\n>\n>> The benefit would be no invocation of new processes, letting us\n>> do more in core. This would allow for tweaking revision walking\n>> internally, e.g. passing of options to this command such as rename\n>> detection factors, can be passed through easily without the need\n>> of translating it back to the command line.\n>\n> On the other hand, we can simply copy those options to the command-line\n> for `log`. Which might even be better, as e.g. `--format` changes global\n> state :-(\n\nok.\n\nThanks for your patience,\nStefan\n"},{"id":"346588","messageId":"nycvar.QRO.7.76.6.1805032334180.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAGZ79ka7RJq=tNnBsLs6wwL7Cf3hSJODM2iLW5tet-csAM20eg@mail.gmail.com","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T21:49:31Z","receivedAt":"2018-05-03T21:49:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Thu, 3 May 2018, Stefan Beller wrote:\n\n> On Thu, May 3, 2018 at 1:42 PM, Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> \n> >> Speaking of colors, for origin/sb/blame-color Junio hinted at re-using\n> >> cyan for \"uninteresting\" parts to deliver a consistent color scheme for\n> >> Git. Eventually he dreams of having 2 layers of indirection IIUC, with\n> >>     \"uninteresting\" -> cyan\n> >>     \"repeated lines in blame\" -> uninteresting\n> >>\n> >> Maybe we can fit the coloring of this tool in this scheme, too?\n> >\n> > Sure. So you mean I should use cyan for... what part of the colored\n> > output? ;-)\n> \n> It is just a FYI heads up, not an actionable bikeshed painting plan. ;)\n\nOh, I did not understand it as bike-shedding at all. I had hoped that you\nran `git branch-diff --dual-color` on something interesting and found e.g.\nthe yellow color inherited from DIFF_COMMIT to be the wrong color for\nunchanged commit pairs.\n\nSo please: as soon as you have a concrete suggestion where to use cyan\n(and preferably even a DIFF_* constant to feed to diff_get_color_opt()), I\nwill be more than interested.\n\n> >> Do we need to dynamic of a floating point, or would a rather small range\n> >> suffice here? (Also see rename detection settings, that take percents as\n> >> integers)\n> >\n> > I guess you are right, and we do not need floats. It was just very, very\n> > convenient to do that instead of using integers because\n> >\n> > - I already had the Jonker-Volgenant implementation \"lying around\" from my\n> >   previous life as an image processing expert, using doubles (but it was\n> >   in Java, not in C, so I quickly converted it for branch-diff).\n> >\n> > - I was actually not paying attention whether divisions are a thing in the\n> >   algorithm. From a cursory glance, it would appear that we are never\n> >   dividing in hungarian.c, so theoretically integers should be fine.\n> >\n> > - using doubles neatly side-steps the overflow problem. If I use integers\n> >   instead, I always will have to worry what to do if, say, adding\n> >   `INT_MAX` to `INT_MAX`.\n> >\n> > I am particularly worried about that last thing: it could easily lead to\n> > incorrect results if we blindly, say, pretend that `INT_MAX + INT_MAX ==\n> > INT_MAX` for the purpose of avoiding overflows.\n> >\n> > If, however, I misunderstood and you are only concerned about using\n> > *double-precision* floating point numbers, and would suggest using `float`\n> > typed variables instead, that would be totally cool with me.\n> \n> So by being worried about INT_MAX occurring, you are implying that\n> we have to worry about a large range of values, so maybe floating points\n> are the best choice here.\n\nI am not really worried about a large range of values, I am worried about\na use case where we use the maximal value as an \"impossible, must avoid at\nall cost\" value. See this line in hungarian.c:\n\n                        u2 = DBL_MAX;\n\nIt does not seem as if any arithmetic is done on u2 after that\n(theoretically, it should not survive the loop that comes after it and\ntries to replace u2 with any smaller value it finds, but what if that loop\ndoes not even run because column_count == 1? Sure, it is a pathological\ncase, but even those should have correct results).\n\nBut actually, yes, there *is* arithmetic performed on u2:\n\n                        if (u1 < u2)\n                                v[j1] -= u2 - u1;\n\nSo in the pathological case where we try to find the best assignment of a\nsingle column to an arbitrary number of rows (where simply the row with\na smallest cost should be picked), we try to subtract from v[j1] an\ninsanely large value. As a consequence, v[j1] will be close to INT_MIN if\nwe were to switch to integers, and who is to say that the next time we get\nto this part, j1 will be different? If it is the same, and we hit the same\nu2, then we might end up subtracting something close to INT_MAX from\nINT_MIN, which will definitely overflow and the computation will be\nincorrect.\n\n*That* is what I am worried about: overflowing integer arithmetic. IIRC if\nyou subtract DBL_MAX from -DBL_MAX, you still end up with -DBL_MAX. So in\nthat respect, using floating point numbers here is safer.\n\n> Looking through that algorithm the costs seem to be integers only\n> measuring number of lines, so I would not be too worried about running\n> into INT_MAX problems except for the costs that are assigned INT_MAX\n> explicitly.\n> \n> I was more asking, if floating point is the right tool for the job.\n\nI think I would have to spend some real quality time with the code in\nhungarian.c to turn it into using integer costs instead of floating point\nnumbers, to ensure that the arithmetic is done in a way that is consistent\nwith the algorithm, even if we cannot represent the arithmetic faithfully\nwith limited-range integers.\n\nI'll think about the best way forward.\n\nCiao,\nDscho\n"},{"id":"346589","messageId":"CA+P7+xrHeAtYjOCNd28t3Kv7G0hsvdVm+dwyVWuGx=XjS3nskQ@mail.gmail.com","threadId":"48405","inReplyTo":"87po2cy5qd.fsf@evledraar.gmail.com","subject":"Re: [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-05-03T21:50:54Z","receivedAt":"2018-05-03T21:51:18Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Thu, May 3, 2018 at 11:05 AM, Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n> On Thu, May 03 2018, Johannes Schindelin wrote:\n>\n>> The incredibly useful `git-tbdiff` tool to compare patch series (say, to see\n>> what changed between two iterations sent to the Git mailing list) is slightly\n>> less useful for this developer due to the fact that it requires the `hungarian`\n>> and `numpy` Python packages which are for some reason really hard to build in\n>> MSYS2. So hard that I even had to give up, because it was simply easier to\n>> reimplement the whole shebang as a builtin command.\n>>\n>> The project at https://github.com/trast/tbdiff seems to be dormant, anyway.\n>> Funny (and true) story: I looked at the open Pull Requests to see how active\n>> that project is, only to find to my surprise that I had submitted one in August\n>> 2015, and that it was still unanswered let alone merged.\n>\n> I've been using branch-diff and haven't found issues with it yet, it\n> works like tbdiff but better. Faster, uses the same diff as git\n> (better), and spews to the pager by default.\n\nI'm hoping to take a look at this as well, I remember looking into\ntbdiff in the past, but also had trouble getting it to work. I've\ntried a variety of similar things, including 4-way parent diffs, but\nnothing quite gave the results I expected.\n\nThanks!\n\nRegards,\nJake\n"},{"id":"346590","messageId":"nycvar.QRO.7.76.6.1805032352380.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAGZ79kZQ+mq1O_sL11jC4_Lt18nO6b_6pSPBahOqyZ+izrRm7w@mail.gmail.com","subject":"Re: [PATCH 03/18] branch-diff: first rudimentary implementation","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-03T22:00:29Z","receivedAt":"2018-05-03T22:00:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Thu, 3 May 2018, Stefan Beller wrote:\n\n> >> In addition to that patch, we'd have to buffer commit messages and\n> >> buffer multiple commits, as that only buffers a diff of a single\n> >> commit.\n> >\n> > ... and make sure that the moved-code logic (which is currently the\n> > only user of emitted_symbols, correct?) would never be called at the\n> > same time as we generate the diff.\n> \n> The moved detection is all part of the flags of an emitted symbol.\n> \n> By design the emitted symbol has easy access to the raw line of the output,\n> which made it easy for the move detection to work on the lines. AFAICT this\n> is also desired here as lines are put into a hashmap for comparisons.\n> (and having it colored differently would make finding the same line\n> complex using hashmaps)\n> \n> I just entertain the thought of having move detection active in a\n> branch-diff. That would be really cool actually.\n\nThere are two separate times when we generate a diff in branch-diff: the\nfirst time in that `git log -p <range>` call for both ranges, and later,\nwhen displaying the changes of old/new commits.\n\n(There is actually a third time, when the cost is calculated, but those\ndiffs are not shown, only their line count is used.)\n\nIt would be relatively easy to use move detection in the diff between\nold/new commits. But it would be harder to do that with the `git log -p`\ndiffs, as we only color-code them later, not at the time they are\ngenerated.\n\nIn fact, Ævar mentioned that he was pretty happy about the fact that `git\nbranch-diff` accepts all kinds of diff options, when tbdiff emulated only\ntwo of them. Ævar mentioned specifically the use of `--color-words`...\n\n> >> The benefit would be no invocation of new processes, letting us do\n> >> more in core. This would allow for tweaking revision walking\n> >> internally, e.g. passing of options to this command such as rename\n> >> detection factors, can be passed through easily without the need of\n> >> translating it back to the command line.\n> >\n> > On the other hand, we can simply copy those options to the\n> > command-line for `log`. Which might even be better, as e.g. `--format`\n> > changes global state :-(\n> \n> ok.\n\nI really appreciate the sanity check. It would benefit me on Windows if I\ncould avoid spawning... But I think in this case, it would save me 2*50ms,\nwhich is not really worth doing the work for, so far.\n\n> Thanks for your patience,\n\nAnd thank you for yours! It *is* important to challenge beliefs during\ncode review, so that the choices are made for the right reasons.\n\nThanks,\nDscho"},{"id":"346601","messageId":"850f1ad6-752d-85ae-ebad-feae09a76c54@ramsayjones.plus.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805032224150.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Ramsay Jones","fromEmail":"ramsay@ramsayjones.plus.com","sentAt":"2018-05-03T23:20:33Z","receivedAt":"2018-05-03T23:20:38Z","isPatch":true,"sender":{"key":"ramsay@ramsayjones.plus.com","avatar":"https://avatars.githubusercontent.com/u/33702710?v=4"},"body":"\n\nOn 03/05/18 21:25, Johannes Schindelin wrote:\n\n> On Thu, 3 May 2018, Ramsay Jones wrote:\n\n>> On 03/05/18 16:30, Johannes Schindelin wrote:\n[snip]\n\n>>> diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n>>> new file mode 100644\n>>> index 00000000000..97266cd326d\n>>> --- /dev/null\n>>> +++ b/builtin/branch-diff.c\n>>> @@ -0,0 +1,40 @@\n>>> +#include \"cache.h\"\n>>> +#include \"parse-options.h\"\n>>> +\n>>> +static const char * const builtin_branch_diff_usage[] = {\n>>> +\tN_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n>>\n>> s/rebase--helper/branch-diff/\n> \n> Whoops!\n> \n> BTW funny side note: when I saw that you replied, I instinctively thought\n> \"oh no, I forgot to mark a function as `static`!\" ;-)\n\nHeh, but I hadn't got around to applying the patches and building\ngit yet! ;-)\n\nSparse has two complaints:\n\n  >     SP builtin/branch-diff.c\n  > builtin/branch-diff.c:433:41: warning: Using plain integer as NULL pointer\n  > builtin/branch-diff.c:431:5: warning: symbol 'cmd_branch_diff' was not declared. Should it be static?\n\nI suppressed those warnings with the following patch (on top\nof these patches):\n\n  $ git diff\n  diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n  index edf80ecb7..1373c22f4 100644\n  --- a/builtin/branch-diff.c\n  +++ b/builtin/branch-diff.c\n  @@ -1,4 +1,5 @@\n   #include \"cache.h\"\n  +#include \"builtin.h\"\n   #include \"parse-options.h\"\n   #include \"string-list.h\"\n   #include \"run-command.h\"\n  @@ -430,7 +431,7 @@ static void output(struct string_list *a, struct string_list *b,\n \n   int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n   {\n  -       struct diff_options diffopt = { 0 };\n  +       struct diff_options diffopt = { NULL };\n          struct strbuf four_spaces = STRBUF_INIT;\n          int dual_color = 0;\n          double creation_weight = 0.6;\n  $ \n\nThe first hunk applies to patch 02/18 (ie this very patch) and\nthe second hunk should be applied to patch 05/18 (ie, \"branch-diff:\nalso show the diff between patches\").\n\nATB,\nRamsay Jones\n\n\n"},{"id":"346602","messageId":"8DBB15A6A2AB48C7A9914B5B419E001F@PhilipOakley","threadId":"48405","inReplyTo":"fe12b99a0b4f78ab75fcfbcf51c5edffb190c4e8.1525361419.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 11/18] branch-diff: add tests","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":"2018-05-03T23:27:04Z","receivedAt":"2018-05-03T23:27:15Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n> From: Thomas Rast <tr@thomasrast.ch>\n> \n> These are essentially lifted from https://github.com/trast/tbdiff, with\n> light touch-ups to account for the new command name.\n> \n> Apart from renaming `tbdiff` to `branch-diff`, only one test case needed\n> to be adjusted: 11 - 'changed message'.\n> \n> The underlying reason it had to be adjusted is that diff generation is\n> sometimes ambiguous. In this case, a comment line and an empty line are\n> added, but it is ambiguous whether they were added after the existing\n> empty line, or whether an empty line and the comment line are added\n> *before* the existing emtpy line. And apparently xdiff picks a different\n\ns/emtpy/empty/\n\n> option here than Python's difflib.\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n[...]\nPhilip\n"},{"id":"346614","messageId":"CAPig+cQv7tNCNhDdThhhDYEE=XmB0xO35Qjvpw+-MgCg0W3ovQ@mail.gmail.com","threadId":"48405","inReplyTo":"8bc517e35d4842f8d9d98f3b99adb9475d6db2d2.1525361419.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-05-04T02:35:38Z","receivedAt":"2018-05-04T02:35:42Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, May 3, 2018 at 11:30 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> This builtin does not do a whole lot so far, apart from showing a usage\n> that is oddly similar to that of `git tbdiff`. And for a good reason:\n> the next commits will turn `branch-diff` into a full-blown replacement\n> for `tbdiff`.\n>\n> At this point, we ignore tbdiff's color options, as they will all be\n> implemented later and require some patches to the diff machinery.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n> @@ -0,0 +1,40 @@\n> +static const char * const builtin_branch_diff_usage[] = {\n> +       N_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n> +       NULL\n> +};\n\nThe formatting of \"<options>\" vs. \"base\" confused me into thinking\nthat the latter was a literal keyword, but I see from reading patch\n3/18 that it is not a literal at all, thus probably ought to be\nspecified as \"<base>\".\n"},{"id":"346615","messageId":"CAPig+cSvHWvb0dsGkjL69yzbBvgaT7oJm6nFuGWeA6Jw0NpYUw@mail.gmail.com","threadId":"48405","inReplyTo":"ec51c71779a325263c1b705a6b1bfb003fcd528a.1525361419.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 03/18] branch-diff: first rudimentary implementation","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-05-04T02:35:49Z","receivedAt":"2018-05-04T02:35:53Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, May 3, 2018 at 11:30 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> At this stage, `git branch-diff` can determine corresponding commits of\n> two related commit ranges. This makes use of the recently introduced\n> implementation of the Hungarian algorithm.\n>\n> The core of this patch is a straight port of the ideas of tbdiff, the\n> seemingly dormant project at https://github.com/trast/tbdiff.\n>\n> The output does not at all match `tbdiff`'s output yet, as this patch\n> really concentrates on getting the patch matching part right.\n>\n> Note: due to differences in the diff algorithm (`tbdiff` uses the\n> Pythong module `difflib`, Git uses its xdiff fork), the cost matrix\n\ns/Pythong/Python/\n\n> calculated by `branch-diff` is different (but very similar) to the one\n> calculated by `tbdiff`. Therefore, it is possible that they find\n> different matching commits in corner cases (e.g. when a patch was split\n> into two patches of roughly equal length).\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n> @@ -19,6 +23,279 @@ static int parse_creation_weight(const struct option *opt, const char *arg,\n> +static int read_patches(const char *range, struct string_list *list)\n> +{\n> +       [...]\n> +       struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;\n> +       [...]\n> +                       } else if (starts_with(line.buf, \"    \")) {\n> +                               strbuf_addbuf(&buf, &line);\n> +                               strbuf_addch(&buf, '\\n');\n> +                       }\n> +\n> +                       continue;\n\nUnnecessary blank line above 'continue'?\n\n> +               } else if (starts_with(line.buf, \"@@ \"))\n> +                       strbuf_addstr(&buf, \"@@\");\n> +               [...]\n> +       }\n> +       fclose(in);\n> +\n> +       if (util)\n> +               string_list_append(list, buf.buf)->util = util;\n> +       strbuf_release(&buf);\n\nstrbuf_release(&line);\n\n> +       if (finish_command(&cp))\n> +               return -1;\n> +\n> +       return 0;\n> +}\n> @@ -32,9 +309,63 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n> +       if (argc == 2) {\n> +               if (!strstr(argv[0], \"..\"))\n> +                       warning(_(\"no .. in range: '%s'\"), argv[0]);\n> +               strbuf_addstr(&range1, argv[0]);\n> +\n> +               if (!strstr(argv[1], \"..\"))\n> +                       warning(_(\"no .. in range: '%s'\"), argv[1]);\n> +               strbuf_addstr(&range2, argv[1]);\n> +       } else if (argc == 1) {\n> +               if (!b)\n> +                       die(_(\"single arg format requires a symmetric range\"));\n> +       } else {\n> +               error(\"Need two commit ranges\");\n\nOther warning/error messages emitted by this function are not\ncapitalized: s/Need/need/\n\n> +               usage_with_options(builtin_branch_diff_usage, options);\n> +       }\n> +\n> +       if (read_patches(range1.buf, &branch1))\n> +               res = error(_(\"could not parse log for '%s'\"), range1.buf);\n> +       if (!res && read_patches(range2.buf, &branch2))\n> +               res = error(_(\"could not parse log for '%s'\"), range2.buf);\n"},{"id":"346617","messageId":"CAPig+cQc-FXyZv=61GO7-6apu_avA-DhPkqJLC_1a5hKmq=bZg@mail.gmail.com","threadId":"48405","inReplyTo":"141e5b63e4511c13380216fad9b8601d2bc6051e.1525361419.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 05/18] branch-diff: also show the diff between patches","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-05-04T02:51:07Z","receivedAt":"2018-05-04T02:51:12Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, May 3, 2018 at 11:30 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> Just like tbdiff, we now show the diff between matching patches. This is\n> a \"diff of two diffs\", so it can be a bit daunting to read for the\n> beginnger.\n\ns/beginnger/beginner/\n\n> This brings branch-diff closer to be feature-complete with regard to\n\ns/be feature-complete/feature parity/\n\n> tbdiff.\n>\n> An alternative would be to display an interdiff, i.e. the hypothetical\n> diff which is the result of first reverting the old diff and then\n> applying the new diff.\n>\n> Especially when rebasing often, an interdiff is often not feasible,\n> though: if the old diff cannot be applied in reverse (due to a moving\n> upstream), an interdiff can simply not be inferred.\n>\n> Note: while we now parse diff options such as --color, the effect is not\n> yet the same as in tbdiff, where also the commit pairs would be colored.\n\n\"... tbdiff, in which the commit pairs would also be colored.\"\n\nHowever, I don't see the --color option being parsed by this patch, so\nperhaps this \"Note\" can be dropped?\n\n> This is left for a later commit.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n> @@ -319,24 +348,37 @@ static void output(struct string_list *a, struct string_list *b)\n>  int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n>  {\n> -       int no_patches = 0;\n> +       struct diff_options diffopt = { 0 };\n>         double creation_weight = 0.6;\n>         struct option options[] = {\n> -               OPT_BOOL(0, \"no-patches\", &no_patches,\n> -                        N_(\"short format (no diffs)\")),\n\nThis was added in 2/18 but never used...\n\n> +               OPT_SET_INT(0, \"no-patches\", &diffopt.output_format,\n> +                           N_(\"short format (no diffs)\"),\n> +                           DIFF_FORMAT_NO_OUTPUT),\n\n... and is then replaced in its entirety by this. Perhaps just drop\nthe original --no-patches from 2/18 and let it be introduced for the\nfirst time here?\n\n>                 { OPTION_CALLBACK,\n>                         0, \"creation-weight\", &creation_weight, N_(\"factor\"),\n>                         N_(\"Fudge factor by which creation is weighted [0.6]\"),\n>                         0, parse_creation_weight },\n>                 OPT_END()\n>         };\n"},{"id":"346619","messageId":"CAPig+cSzBY4gi_9b8NREeeOb0EA-wyKQusiDw6c3=FMaBoaUNg@mail.gmail.com","threadId":"48405","inReplyTo":"CAPig+cQc-FXyZv=61GO7-6apu_avA-DhPkqJLC_1a5hKmq=bZg@mail.gmail.com","subject":"Re: [PATCH 05/18] branch-diff: also show the diff between patches","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-05-04T03:15:43Z","receivedAt":"2018-05-04T03:15:48Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, May 3, 2018 at 10:51 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Thu, May 3, 2018 at 11:30 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n>> Note: while we now parse diff options such as --color, the effect is not\n>> yet the same as in tbdiff, where also the commit pairs would be colored.\n>\n> \"... tbdiff, in which the commit pairs would also be colored.\"\n>\n> However, I don't see the --color option being parsed by this patch, so\n> perhaps this \"Note\" can be dropped?\n\nIgnore this latter comment; I missed the newly added call to diff_opt_parse().\n"},{"id":"346620","messageId":"xmqqa7tgt87y.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805032334180.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-05-04T03:23:13Z","receivedAt":"2018-05-04T03:23:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> So please: as soon as you have a concrete suggestion where to use cyan\n> (and preferably even a DIFF_* constant to feed to diff_get_color_opt()), I\n> will be more than interested.\n\nI do not think Stefan's comment was that he was keen to use 'cyan'.\nIt was a color I suggested in a review of his change where he added\nnew colors to the color.[ch] palette, and I found that reusing an\nexisting color would have achieved the same distinction between\nlines of output from his code, and it would be beneficial to make\nthe outcome consistent to consider why these existing colors are\nused in existing places and trying to align the rationale for new\nuses. \"cyan\" was cited as an example to illustrate that last point,\ni.e. we use it to dim out relatively uninteresting part.\n"},{"id":"346621","messageId":"CAPig+cR0V-UgKg0iDfAXLPiSANLv2b3CbwGNKk=VBvrZjX5FdA@mail.gmail.com","threadId":"48405","inReplyTo":"edb34bd4f8da7437efb20e442780f17e9f84fc73.1525361419.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 17/18] branch-diff: add a man page","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-05-04T03:27:58Z","receivedAt":"2018-05-04T03:28:02Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, May 3, 2018 at 11:31 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> This is a heavily butchered version of the README written by Thomas\n> Rast and Thomas Gummerer, lifted from https://github.com/trast/tbdiff.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> diff --git a/Documentation/git-branch-diff.txt b/Documentation/git-branch-diff.txt\n> @@ -0,0 +1,239 @@\n> +Algorithm\n> +---------\n> +\n> +The general idea is this: we generate a cost matrix between the commits\n> +in both commit ranges, then solve the least-cost assignment.\n> +\n> +To avoid false positives (e.g. when a patch has been removed, and an\n> +unrelated patch has been added between two iterations of the same patch\n> +series), the cost matrix is extended to allow for that, by adding\n> +fixed-cost entries for wholesale deletes/adds.\n> +\n> +Example: let commits `1--2` be the first iteration of a patch series and\n\ns/let/Let/\n\n> +`A--C` the second iteration. Let's assume that `A` is a cherry-pick of\n> +`2,` and `C` is a cherry-pick of `1` but with a small modification (say,\n> +a fixed typo). Visualize the commits as a bipartite graph:\n"},{"id":"346622","messageId":"xmqq6044t3x8.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"ec51c71779a325263c1b705a6b1bfb003fcd528a.1525361419.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 03/18] branch-diff: first rudimentary implementation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-05-04T04:56:03Z","receivedAt":"2018-05-04T04:56:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> Note: due to differences in the diff algorithm (`tbdiff` uses the\n> Pythong module `difflib`, Git uses its xdiff fork), the cost matrix\n\nPythong???\n\n> calculated by `branch-diff` is different (but very similar) to the one\n> calculated by `tbdiff`. Therefore, it is possible that they find\n> different matching commits in corner cases (e.g. when a patch was split\n> into two patches of roughly equal length).\n"},{"id":"346623","messageId":"CACsJy8CRCb2go5qUBOdiSNvvAShotD=e4Cm3Jo1OxNk212YtCA@mail.gmail.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805032232080.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2018-05-04T05:15:45Z","receivedAt":"2018-05-04T05:16:22Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, May 3, 2018 at 10:32 PM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> Hi Duy,\n>\n> On Thu, 3 May 2018, Johannes Schindelin wrote:\n>\n>> On Thu, 3 May 2018, Duy Nguyen wrote:\n>>\n>> > On Thu, May 3, 2018 at 5:30 PM, Johannes Schindelin\n>> > <johannes.schindelin@gmx.de> wrote:\n>> > > diff --git a/command-list.txt b/command-list.txt\n>> > > index a1fad28fd82..c89ac8f417f 100644\n>> > > --- a/command-list.txt\n>> > > +++ b/command-list.txt\n>> > > @@ -19,6 +19,7 @@ git-archive                             mainporcelain\n>> > >  git-bisect                              mainporcelain           info\n>> > >  git-blame                               ancillaryinterrogators\n>> > >  git-branch                              mainporcelain           history\n>> > > +git-branch-diff                         mainporcelain           info\n>> >\n>> > Making it part of \"git help\" with the info keywords at this stage may\n>> > be premature. \"git help\" is about _common_ commands and we don't know\n>> > (yet) how popular this will be.\n>>\n>> Makes sense. I removed the `mainporcelain` keyword locally.\n>\n> On second thought, I *think* you meant to imply that I should remove that\n> line altogether. Will do that now.\n\nActually I only suggested to remove the last word \"info\". That was\nwhat made this command \"common\". Classifying all commands in this file\nis definitely a good thing, and I think mainporcelain is the right\nchoice.\n-- \nDuy\n"},{"id":"346624","messageId":"xmqq1sest2m1.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-05-04T05:24:22Z","receivedAt":"2018-05-04T05:24:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> Johannes Schindelin (17):\n>   Add a function to solve least-cost assignment problems\n>   Add a new builtin: branch-diff\n>   branch-diff: first rudimentary implementation\n>   branch-diff: improve the order of the shown commits\n>   branch-diff: also show the diff between patches\n>   branch-diff: right-trim commit messages\n>   branch-diff: indent the diffs just like tbdiff\n>   branch-diff: suppress the diff headers\n>   branch-diff: adjust the output of the commit pairs\n>   branch-diff: do not show \"function names\" in hunk headers\n>   branch-diff: use color for the commit pairs\n>   color: provide inverted colors, too\n>   diff: add an internal option to dual-color diffs of diffs\n>   branch-diff: offer to dual-color the diffs\n>   branch-diff --dual-color: work around bogus white-space warning\n>   branch-diff: add a man page\n>   completion: support branch-diff\n>\n> Thomas Rast (1):\n>   branch-diff: add tests\n\nLovely.  \n\nI often have to criticize a series whose later half consists of many\nfollow-up patches with \"don't do 'oops, the previous was wrong'\",\nbut the follow-up patches in this series are not such corrections.\nThe organization of the series to outline the basic and core idea\nfirst in the minimum form and then to build on it to improve an\naspect of the command one step at a time is very helpful to guide\nthe readers where the author of the series wants them to go.\n"},{"id":"346626","messageId":"nycvar.QRO.7.76.6.1805040829390.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"850f1ad6-752d-85ae-ebad-feae09a76c54@ramsayjones.plus.com","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T06:40:53Z","receivedAt":"2018-05-04T06:41:05Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Ramsay,\n\nOn Fri, 4 May 2018, Ramsay Jones wrote:\n\n> On 03/05/18 21:25, Johannes Schindelin wrote:\n> \n> > On Thu, 3 May 2018, Ramsay Jones wrote:\n> \n> >> On 03/05/18 16:30, Johannes Schindelin wrote:\n> [snip]\n> \n> >>> diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n> >>> new file mode 100644\n> >>> index 00000000000..97266cd326d\n> >>> --- /dev/null\n> >>> +++ b/builtin/branch-diff.c\n> >>> @@ -0,0 +1,40 @@\n> >>> +#include \"cache.h\"\n> >>> +#include \"parse-options.h\"\n> >>> +\n> >>> +static const char * const builtin_branch_diff_usage[] = {\n> >>> +\tN_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n> >>\n> >> s/rebase--helper/branch-diff/\n> > \n> > Whoops!\n> > \n> > BTW funny side note: when I saw that you replied, I instinctively thought\n> > \"oh no, I forgot to mark a function as `static`!\" ;-)\n> \n> Heh, but I hadn't got around to applying the patches and building\n> git yet! ;-)\n\n;-)\n\n> Sparse has two complaints:\n> \n>   >     SP builtin/branch-diff.c\n>   > builtin/branch-diff.c:433:41: warning: Using plain integer as NULL pointer\n>   > builtin/branch-diff.c:431:5: warning: symbol 'cmd_branch_diff' was not declared. Should it be static?\n> \n> I suppressed those warnings with the following patch (on top\n> of these patches):\n> \n>   $ git diff\n>   diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n>   index edf80ecb7..1373c22f4 100644\n>   --- a/builtin/branch-diff.c\n>   +++ b/builtin/branch-diff.c\n>   @@ -1,4 +1,5 @@\n>    #include \"cache.h\"\n>   +#include \"builtin.h\"\n>    #include \"parse-options.h\"\n>    #include \"string-list.h\"\n>    #include \"run-command.h\"\n>   @@ -430,7 +431,7 @@ static void output(struct string_list *a, struct string_list *b,\n>  \n>    int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n>    {\n>   -       struct diff_options diffopt = { 0 };\n>   +       struct diff_options diffopt = { NULL };\n>           struct strbuf four_spaces = STRBUF_INIT;\n>           int dual_color = 0;\n>           double creation_weight = 0.6;\n>   $ \n\nThanks!\n\n> The first hunk applies to patch 02/18 (ie this very patch) and\n> the second hunk should be applied to patch 05/18 (ie, \"branch-diff:\n> also show the diff between patches\").\n\nI actually have a hacky script to fixup commits in a patch series. It lets\nme stage part of the current changes, then figures out which of the\ncommits' changes overlap with the staged changed. If there is only one\ncommit, it automatically commits with --fixup, otherwise it lets me choose\nwhich one I want to fixup (giving me the list of candidates).\n\nBTW I ran `make sparse` for the first time, and it spits out tons of\nstuff. And I notice that they are all non-fatal warnings, but so were the\nones you pointed out above. This is a bit sad, as I would *love* to\ninstall a VSTS build job to run `make sparse` automatically. Examples of\nwarnings *after* applying your patch:\n\nconnect.c:481:40: warning: incorrect type in argument 2 (invalid types)\nconnect.c:481:40:    expected union __CONST_SOCKADDR_ARG [usertype] __addr\nconnect.c:481:40:    got struct sockaddr *ai_addr\n\nor\n\npack-revindex.c:65:23: warning: memset with byte count of 262144\n\nWhat gives?\n\nCiao,\nDscho\n"},{"id":"346627","messageId":"nycvar.QRO.7.76.6.1805040841230.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"8DBB15A6A2AB48C7A9914B5B419E001F@PhilipOakley","subject":"Re: [PATCH 11/18] branch-diff: add tests","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T06:42:05Z","receivedAt":"2018-05-04T06:42:18Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Philip,\n\nOn Fri, 4 May 2018, Philip Oakley wrote:\n\n> From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n> > From: Thomas Rast <tr@thomasrast.ch>\n> > \n> > These are essentially lifted from https://github.com/trast/tbdiff, with\n> > light touch-ups to account for the new command name.\n> > \n> > Apart from renaming `tbdiff` to `branch-diff`, only one test case needed\n> > to be adjusted: 11 - 'changed message'.\n> > \n> > The underlying reason it had to be adjusted is that diff generation is\n> > sometimes ambiguous. In this case, a comment line and an empty line are\n> > added, but it is ambiguous whether they were added after the existing\n> > empty line, or whether an empty line and the comment line are added\n> > *before* the existing emtpy line. And apparently xdiff picks a different\n> \n> s/emtpy/empty/\n\nYep, that has been pointed out by others, too... ;-)\n\nIt is good that this thing gets a really good review *grins*\n\nCiao,\nDscho\n"},{"id":"346628","messageId":"nycvar.QRO.7.76.6.1805040843050.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAPig+cQv7tNCNhDdThhhDYEE=XmB0xO35Qjvpw+-MgCg0W3ovQ@mail.gmail.com","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T06:52:37Z","receivedAt":"2018-05-04T06:52:45Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Thu, 3 May 2018, Eric Sunshine wrote:\n\n> On Thu, May 3, 2018 at 11:30 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > This builtin does not do a whole lot so far, apart from showing a usage\n> > that is oddly similar to that of `git tbdiff`. And for a good reason:\n> > the next commits will turn `branch-diff` into a full-blown replacement\n> > for `tbdiff`.\n> >\n> > At this point, we ignore tbdiff's color options, as they will all be\n> > implemented later and require some patches to the diff machinery.\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> > diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n> > @@ -0,0 +1,40 @@\n> > +static const char * const builtin_branch_diff_usage[] = {\n> > +       N_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n> > +       NULL\n> > +};\n> \n> The formatting of \"<options>\" vs. \"base\" confused me into thinking\n> that the latter was a literal keyword, but I see from reading patch\n> 3/18 that it is not a literal at all, thus probably ought to be\n> specified as \"<base>\".\n\nGood point. Or maybe BASE?\n\nOr I should just use the same convention as in the man page. Or not, as\nthe usage should be conciser.\n\nThis is what I have currently:\n\nstatic const char * const builtin_branch_diff_usage[] = {\nN_(\"git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\nN_(\"git branch-diff [<options>] <old-tip>...<new-tip>\"),\nN_(\"git branch-diff [<options>] <base> <old-tip> <new-tip>\"),\nNULL\n};\n\nThanks,\nDscho\n"},{"id":"346629","messageId":"nycvar.QRO.7.76.6.1805040900570.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAPig+cSvHWvb0dsGkjL69yzbBvgaT7oJm6nFuGWeA6Jw0NpYUw@mail.gmail.com","subject":"Re: [PATCH 03/18] branch-diff: first rudimentary implementation","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T07:03:04Z","receivedAt":"2018-05-04T07:03:12Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Thu, 3 May 2018, Eric Sunshine wrote:\n\n> On Thu, May 3, 2018 at 11:30 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > At this stage, `git branch-diff` can determine corresponding commits of\n> > two related commit ranges. This makes use of the recently introduced\n> > implementation of the Hungarian algorithm.\n> >\n> > The core of this patch is a straight port of the ideas of tbdiff, the\n> > seemingly dormant project at https://github.com/trast/tbdiff.\n> >\n> > The output does not at all match `tbdiff`'s output yet, as this patch\n> > really concentrates on getting the patch matching part right.\n> >\n> > Note: due to differences in the diff algorithm (`tbdiff` uses the\n> > Pythong module `difflib`, Git uses its xdiff fork), the cost matrix\n> \n> s/Pythong/Python/\n\nYep!\n\n> > calculated by `branch-diff` is different (but very similar) to the one\n> > calculated by `tbdiff`. Therefore, it is possible that they find\n> > different matching commits in corner cases (e.g. when a patch was split\n> > into two patches of roughly equal length).\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> > diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n> > @@ -19,6 +23,279 @@ static int parse_creation_weight(const struct option *opt, const char *arg,\n> > +static int read_patches(const char *range, struct string_list *list)\n> > +{\n> > +       [...]\n> > +       struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;\n> > +       [...]\n> > +                       } else if (starts_with(line.buf, \"    \")) {\n> > +                               strbuf_addbuf(&buf, &line);\n> > +                               strbuf_addch(&buf, '\\n');\n> > +                       }\n> > +\n> > +                       continue;\n> \n> Unnecessary blank line above 'continue'?\n\nSure.\n\n> > +               } else if (starts_with(line.buf, \"@@ \"))\n> > +                       strbuf_addstr(&buf, \"@@\");\n> > +               [...]\n> > +       }\n> > +       fclose(in);\n> > +\n> > +       if (util)\n> > +               string_list_append(list, buf.buf)->util = util;\n> > +       strbuf_release(&buf);\n> \n> strbuf_release(&line);\n\nYes!\n\n> > +       if (finish_command(&cp))\n> > +               return -1;\n> > +\n> > +       return 0;\n> > +}\n> > @@ -32,9 +309,63 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n> > +       if (argc == 2) {\n> > +               if (!strstr(argv[0], \"..\"))\n> > +                       warning(_(\"no .. in range: '%s'\"), argv[0]);\n> > +               strbuf_addstr(&range1, argv[0]);\n> > +\n> > +               if (!strstr(argv[1], \"..\"))\n> > +                       warning(_(\"no .. in range: '%s'\"), argv[1]);\n> > +               strbuf_addstr(&range2, argv[1]);\n> > +       } else if (argc == 1) {\n> > +               if (!b)\n> > +                       die(_(\"single arg format requires a symmetric range\"));\n> > +       } else {\n> > +               error(\"Need two commit ranges\");\n> \n> Other warning/error messages emitted by this function are not\n> capitalized: s/Need/need/\n\nRight. And it is also not translated. Fixed both.\n\nThank you for helping me make this patch series better!\n\nCiao,\nDscho\n"},{"id":"346631","messageId":"nycvar.QRO.7.76.6.1805040904430.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAPig+cQc-FXyZv=61GO7-6apu_avA-DhPkqJLC_1a5hKmq=bZg@mail.gmail.com","subject":"Re: [PATCH 05/18] branch-diff: also show the diff between patches","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T07:15:38Z","receivedAt":"2018-05-04T07:15:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Thu, 3 May 2018, Eric Sunshine wrote:\n\n> On Thu, May 3, 2018 at 11:30 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > Just like tbdiff, we now show the diff between matching patches. This is\n> > a \"diff of two diffs\", so it can be a bit daunting to read for the\n> > beginnger.\n> \n> s/beginnger/beginner/\n> \n> > This brings branch-diff closer to be feature-complete with regard to\n> \n> s/be feature-complete/feature parity/\n\nYes.\n\n> > diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n> > @@ -319,24 +348,37 @@ static void output(struct string_list *a, struct string_list *b)\n> >  int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n> >  {\n> > -       int no_patches = 0;\n> > +       struct diff_options diffopt = { 0 };\n> >         double creation_weight = 0.6;\n> >         struct option options[] = {\n> > -               OPT_BOOL(0, \"no-patches\", &no_patches,\n> > -                        N_(\"short format (no diffs)\")),\n> \n> This was added in 2/18 but never used...\n> \n> > +               OPT_SET_INT(0, \"no-patches\", &diffopt.output_format,\n> > +                           N_(\"short format (no diffs)\"),\n> > +                           DIFF_FORMAT_NO_OUTPUT),\n> \n> ... and is then replaced in its entirety by this. Perhaps just drop\n> the original --no-patches from 2/18 and let it be introduced for the\n> first time here?\n\nSure. I actually started out by parsing even the --color option, but had\nstripped that out in a rebase -i run, in favor of using diff_parse_opt()\nlater. I should really do the same here, too.\n\nThanks,\nDscho\n"},{"id":"346632","messageId":"nycvar.QRO.7.76.6.1805040916550.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAPig+cR0V-UgKg0iDfAXLPiSANLv2b3CbwGNKk=VBvrZjX5FdA@mail.gmail.com","subject":"Re: [PATCH 17/18] branch-diff: add a man page","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T07:17:32Z","receivedAt":"2018-05-04T07:17:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Thu, 3 May 2018, Eric Sunshine wrote:\n\n> On Thu, May 3, 2018 at 11:31 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > This is a heavily butchered version of the README written by Thomas\n> > Rast and Thomas Gummerer, lifted from https://github.com/trast/tbdiff.\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> > diff --git a/Documentation/git-branch-diff.txt b/Documentation/git-branch-diff.txt\n> > @@ -0,0 +1,239 @@\n> > +Algorithm\n> > +---------\n> > +\n> > +The general idea is this: we generate a cost matrix between the commits\n> > +in both commit ranges, then solve the least-cost assignment.\n> > +\n> > +To avoid false positives (e.g. when a patch has been removed, and an\n> > +unrelated patch has been added between two iterations of the same patch\n> > +series), the cost matrix is extended to allow for that, by adding\n> > +fixed-cost entries for wholesale deletes/adds.\n> > +\n> > +Example: let commits `1--2` be the first iteration of a patch series and\n> \n> s/let/Let/\n\nOkay. I am always a little bit fuzzy on the question whether to continue\nlower-case or upper-case after a colon or semicolon.\n\nCiao,\nDscho\n"},{"id":"346633","messageId":"nycvar.QRO.7.76.6.1805040918250.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"xmqq6044t3x8.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 03/18] branch-diff: first rudimentary implementation","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T07:18:40Z","receivedAt":"2018-05-04T07:18:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Fri, 4 May 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> > Note: due to differences in the diff algorithm (`tbdiff` uses the\n> > Pythong module `difflib`, Git uses its xdiff fork), the cost matrix\n> \n> Pythong???\n\nYes, that's the French pronunciation.\n\nCiao,\nDscho\n"},{"id":"346634","messageId":"nycvar.QRO.7.76.6.1805040919400.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CACsJy8CRCb2go5qUBOdiSNvvAShotD=e4Cm3Jo1OxNk212YtCA@mail.gmail.com","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T07:23:10Z","receivedAt":"2018-05-04T07:23:20Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Duy,\n\nOn Fri, 4 May 2018, Duy Nguyen wrote:\n\n> On Thu, May 3, 2018 at 10:32 PM, Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> >\n> > On Thu, 3 May 2018, Johannes Schindelin wrote:\n> >\n> >> On Thu, 3 May 2018, Duy Nguyen wrote:\n> >>\n> >> > On Thu, May 3, 2018 at 5:30 PM, Johannes Schindelin\n> >> > <johannes.schindelin@gmx.de> wrote:\n> >> > > diff --git a/command-list.txt b/command-list.txt\n> >> > > index a1fad28fd82..c89ac8f417f 100644\n> >> > > --- a/command-list.txt\n> >> > > +++ b/command-list.txt\n> >> > > @@ -19,6 +19,7 @@ git-archive                             mainporcelain\n> >> > >  git-bisect                              mainporcelain           info\n> >> > >  git-blame                               ancillaryinterrogators\n> >> > >  git-branch                              mainporcelain           history\n> >> > > +git-branch-diff                         mainporcelain           info\n> >> >\n> >> > Making it part of \"git help\" with the info keywords at this stage may\n> >> > be premature. \"git help\" is about _common_ commands and we don't know\n> >> > (yet) how popular this will be.\n> >>\n> >> Makes sense. I removed the `mainporcelain` keyword locally.\n> >\n> > On second thought, I *think* you meant to imply that I should remove that\n> > line altogether. Will do that now.\n> \n> Actually I only suggested to remove the last word \"info\". That was\n> what made this command \"common\". Classifying all commands in this file\n> is definitely a good thing, and I think mainporcelain is the right\n> choice.\n\nOh, okay. It was not at all clear to me what the exact format and role of\nthese lines are... So that's what `info` does: it influences whether/where\nthe command is listed in `git help`'s output... Interesting. I thought the\nlines here were trying to automate parts of the tab completion or\nsomething.\n\nI re-added the line, this time without `info` and verified that\n`branch-diff` does not show up in `git help`'s output.\n\nCiao,\nDscho\n"},{"id":"346636","messageId":"nycvar.QRO.7.76.6.1805040923570.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"xmqq1sest2m1.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T07:24:11Z","receivedAt":"2018-05-04T07:24:20Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Fri, 4 May 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> > Johannes Schindelin (17):\n> >   Add a function to solve least-cost assignment problems\n> >   Add a new builtin: branch-diff\n> >   branch-diff: first rudimentary implementation\n> >   branch-diff: improve the order of the shown commits\n> >   branch-diff: also show the diff between patches\n> >   branch-diff: right-trim commit messages\n> >   branch-diff: indent the diffs just like tbdiff\n> >   branch-diff: suppress the diff headers\n> >   branch-diff: adjust the output of the commit pairs\n> >   branch-diff: do not show \"function names\" in hunk headers\n> >   branch-diff: use color for the commit pairs\n> >   color: provide inverted colors, too\n> >   diff: add an internal option to dual-color diffs of diffs\n> >   branch-diff: offer to dual-color the diffs\n> >   branch-diff --dual-color: work around bogus white-space warning\n> >   branch-diff: add a man page\n> >   completion: support branch-diff\n> >\n> > Thomas Rast (1):\n> >   branch-diff: add tests\n> \n> Lovely.  \n> \n> I often have to criticize a series whose later half consists of many\n> follow-up patches with \"don't do 'oops, the previous was wrong'\",\n> but the follow-up patches in this series are not such corrections.\n> The organization of the series to outline the basic and core idea\n> first in the minimum form and then to build on it to improve an\n> aspect of the command one step at a time is very helpful to guide\n> the readers where the author of the series wants them to go.\n\nThanks! *beams*\n\nCiao,\nDscho\n"},{"id":"346637","messageId":"CAPig+cSVve5vK6F6CgOoTahKZtzU5Pvv8c6DJzFSMeeqcB1fug@mail.gmail.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805040843050.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-05-04T07:27:31Z","receivedAt":"2018-05-04T07:27:35Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, May 4, 2018 at 2:52 AM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> On Thu, 3 May 2018, Eric Sunshine wrote:\n>> On Thu, May 3, 2018 at 11:30 AM, Johannes Schindelin\n>> <johannes.schindelin@gmx.de> wrote:\n>> > +static const char * const builtin_branch_diff_usage[] = {\n>> > +       N_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n>>\n>> The formatting of \"<options>\" vs. \"base\" confused me into thinking\n>> that the latter was a literal keyword, but I see from reading patch\n>> 3/18 that it is not a literal at all, thus probably ought to be\n>> specified as \"<base>\".\n>\n> Good point. Or maybe BASE?\n\nIndeed, that's probably more consistent with 'A', 'B', etc. than <base>.\n\n> Or I should just use the same convention as in the man page. Or not, as\n> the usage should be conciser.\n>\n> This is what I have currently:\n>\n> static const char * const builtin_branch_diff_usage[] = {\n> N_(\"git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n> N_(\"git branch-diff [<options>] <old-tip>...<new-tip>\"),\n> N_(\"git branch-diff [<options>] <base> <old-tip> <new-tip>\"),\n> NULL\n> };\n\nI can live with this. It's more verbose but more self-explanatory,\nthus likely a good choice.\n"},{"id":"346651","messageId":"CACsJy8AnWC8bLfhj25quzHokber-wrWdSsJiCDra=Ymr++0-nQ@mail.gmail.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805040919400.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2018-05-04T14:44:49Z","receivedAt":"2018-05-04T14:45:23Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Fri, May 4, 2018 at 9:23 AM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> Oh, okay. It was not at all clear to me what the exact format and role of\n> these lines are...\n\nNoted. I'm making more updates in this file in another topic and will\nadd some explanation so the next guy will be less confused.\n\n> So that's what `info` does: it influences whether/where\n> the command is listed in `git help`'s output... Interesting. I thought the\n> lines here were trying to automate parts of the tab completion or\n> something.\n\nOh it does many things. The completion part is coming (so yeah you\ndon't need to update git-completion.bash at all, as long as you have a\nline here and use parse_options() ;-), but I think it's mainly for\n\"git help\" and command listing in \"git help git\" (this command for\nexample should show up under the \"Main porcelain commands\" in that man\npage when you put a line here)\n-- \nDuy\n"},{"id":"346654","messageId":"20180504151746.GA4921@duynguyen.home","threadId":"48405","inReplyTo":"CACsJy8AnWC8bLfhj25quzHokber-wrWdSsJiCDra=Ymr++0-nQ@mail.gmail.com","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2018-05-04T15:17:46Z","receivedAt":"2018-05-04T15:17:55Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Fri, May 04, 2018 at 04:44:49PM +0200, Duy Nguyen wrote:\n> On Fri, May 4, 2018 at 9:23 AM, Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> > Oh, okay. It was not at all clear to me what the exact format and role of\n> > these lines are...\n> \n> Noted. I'm making more updates in this file in another topic and will\n> add some explanation so the next guy will be less confused.\n\nThis is what I will include (but in a separate topic). I will not CC\nyou there to keep your inbox slightly less full. I hope this helps\nunderstand what command-list.txt is for.\n\ndiff --git a/command-list.txt b/command-list.txt\nindex 40776b9587..929d8f0ea0 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -1,3 +1,47 @@\n+# Command classification list\n+# ---------------------------\n+# All supported commands, builtin or external, must be described in\n+# here. This info is used to list commands in various places. Each\n+# command is on one line followed by one or more attributes.\n+#\n+# The first attribute group is mandatory and indicates the command\n+# type. This group includes:\n+#\n+#   mainporcelain\n+#   ancillarymanipulators\n+#   ancillaryinterrogators\n+#   foreignscminterface\n+#   plumbingmanipulators\n+#   plumbinginterrogators\n+#   synchingrepositories\n+#   synchelpers\n+#   purehelpers\n+#\n+# the type names are self explanatory. But if you want to see what\n+# command belongs to what group to get a better idea, have a look at\n+# \"git\" man page, \"GIT COMMANDS\" section.\n+#\n+# Commands of type mainporcelain can also optionally have one of these\n+# attributes:\n+#\n+#   init\n+#   worktree\n+#   info\n+#   history\n+#   remote\n+#\n+# These commands are considered \"common\" and will show up in \"git\n+# help\" output in groups. Uncommon porcelain commands must not\n+# specify any of these attributes.\n+#\n+# \"complete\" attribute is used to mark that the command should be\n+# completable by git-completion.bash. Note that by default,\n+# mainporcelain commands are completable and you don't need this\n+# attribute.\n+#\n+# While not true commands, guides are also specified here, which can\n+# only have \"guide\" attribute and nothing else.\n+#\n ### command list (do not change this line, also do not change alignment)\n # command name                          category [category] [category]\n git-add                                 mainporcelain           worktree\n"},{"id":"346655","messageId":"nycvar.QRO.7.76.6.1805041723050.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CACsJy8AnWC8bLfhj25quzHokber-wrWdSsJiCDra=Ymr++0-nQ@mail.gmail.com","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:23:51Z","receivedAt":"2018-05-04T15:24:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Duy,\n\nOn Fri, 4 May 2018, Duy Nguyen wrote:\n\n> On Fri, May 4, 2018 at 9:23 AM, Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> > Oh, okay. It was not at all clear to me what the exact format and role of\n> > these lines are...\n> \n> Noted. I'm making more updates in this file in another topic and will\n> add some explanation so the next guy will be less confused.\n\nThank you!\n\n> > So that's what `info` does: it influences whether/where\n> > the command is listed in `git help`'s output... Interesting. I thought the\n> > lines here were trying to automate parts of the tab completion or\n> > something.\n> \n> Oh it does many things. The completion part is coming (so yeah you\n> don't need to update git-completion.bash at all, as long as you have a\n> line here and use parse_options() ;-), but I think it's mainly for\n> \"git help\" and command listing in \"git help git\" (this command for\n> example should show up under the \"Main porcelain commands\" in that man\n> page when you put a line here)\n\nI have a hard time believing that anything automated can infer from the\nsource code that branch-diff can accept the non-options in three different\nformats, and what those formats look like...\n\nCiao,\nDscho\n"},{"id":"346656","messageId":"CACsJy8Cr8f1oXe18_YdJn-5=mKuCJZgM22dBd=Z6+GQh8EUF6g@mail.gmail.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805041723050.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2018-05-04T15:29:54Z","receivedAt":"2018-05-04T15:30:28Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Fri, May 4, 2018 at 5:23 PM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>> > So that's what `info` does: it influences whether/where\n>> > the command is listed in `git help`'s output... Interesting. I thought the\n>> > lines here were trying to automate parts of the tab completion or\n>> > something.\n>>\n>> Oh it does many things. The completion part is coming (so yeah you\n>> don't need to update git-completion.bash at all, as long as you have a\n>> line here and use parse_options() ;-), but I think it's mainly for\n>> \"git help\" and command listing in \"git help git\" (this command for\n>> example should show up under the \"Main porcelain commands\" in that man\n>> page when you put a line here)\n>\n> I have a hard time believing that anything automated can infer from the\n> source code that branch-diff can accept the non-options in three different\n> formats, and what those formats look like...\n\nYeah. For now it can only do options which is machine readable. But I\nhave a big plan, so who knows :D\n-- \nDuy\n"},{"id":"346657","messageId":"3f51970cbc44bfe34133c48c0844ed3723e83808.1525448066.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 01/18] Add a function to solve least-cost assignment problems","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:34:29Z","receivedAt":"2018-05-04T15:34:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The Jonker-Volgenant algorithm was implemented to answer questions such\nas: given two different versions of a topic branch (or iterations of a\npatch series), what is the best pairing of commits/patches between the\ndifferent versions?\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Makefile    |   1 +\n hungarian.c | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n hungarian.h |  19 +++++\n 3 files changed, 225 insertions(+)\n create mode 100644 hungarian.c\n create mode 100644 hungarian.h\n\ndiff --git a/Makefile b/Makefile\nindex 50da82b0169..96f2e76a904 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -829,6 +829,7 @@ LIB_OBJS += gpg-interface.o\n LIB_OBJS += graph.o\n LIB_OBJS += grep.o\n LIB_OBJS += hashmap.o\n+LIB_OBJS += hungarian.o\n LIB_OBJS += help.o\n LIB_OBJS += hex.o\n LIB_OBJS += ident.o\ndiff --git a/hungarian.c b/hungarian.c\nnew file mode 100644\nindex 00000000000..346299a97d9\n--- /dev/null\n+++ b/hungarian.c\n@@ -0,0 +1,205 @@\n+/*\n+ * Based on: Jonker, R., & Volgenant, A. (1987). <i>A shortest augmenting path\n+ * algorithm for dense and sparse linear assignment problems</i>. Computing,\n+ * 38(4), 325-340.\n+ */\n+#include \"cache.h\"\n+#include \"hungarian.h\"\n+#include <float.h>\n+\n+#define COST(column, row) cost[(column) + column_count * (row)]\n+\n+/*\n+ * The parameter `cost` is the cost matrix: the cost to assign column j to row\n+ * i is `cost[j + column_count * i].\n+ */\n+int compute_assignment(int column_count, int row_count, double *cost,\n+\t\t       int *column2row, int *row2column)\n+{\n+\tdouble *v = xmalloc(sizeof(double) * column_count), *d;\n+\tint *free_row, free_count = 0, saved_free_count, *pred, *col;\n+\tint i, j, phase;\n+\n+\tmemset(column2row, -1, sizeof(int) * column_count);\n+\tmemset(row2column, -1, sizeof(int) * row_count);\n+\n+\t/* column reduction */\n+\tfor (j = column_count - 1; j >= 0; j--) {\n+\t\tint i1 = 0;\n+\n+\t\tfor (i = 1; i < row_count; i++)\n+\t\t\tif (COST(j, i1) > COST(j, i))\n+\t\t\t\ti1 = i;\n+\t\tv[j] = COST(j, i1);\n+\t\tif (row2column[i1] == -1) {\n+\t\t\t/* row i1 unassigned */\n+\t\t\trow2column[i1] = j;\n+\t\t\tcolumn2row[j] = i1;\n+\t\t} else {\n+\t\t\tif (row2column[i1] >= 0)\n+\t\t\t\trow2column[i1] = -2 - row2column[i1];\n+\t\t\tcolumn2row[j] = -1;\n+\t\t}\n+\t}\n+\n+\t/* reduction transfer */\n+\tfree_row = xmalloc(sizeof(int) * row_count);\n+\tfor (int i = 0; i < row_count; i++) {\n+\t\tint j1 = row2column[i];\n+\t\tif (j1 == -1)\n+\t\t\tfree_row[free_count++] = i;\n+\t\telse if (j1 < -1)\n+\t\t\trow2column[i] = -2 - j1;\n+\t\telse {\n+\t\t\tdouble min = COST(!j1, i) - v[!j1];\n+\t\t\tfor (j = 1; j < column_count; j++)\n+\t\t\t\tif (j != j1 && min > COST(j, i) - v[j])\n+\t\t\t\t\tmin = COST(j, i) - v[j];\n+\t\t\tv[j1] -= min;\n+\t\t}\n+\t}\n+\n+\tif (free_count ==\n+\t    (column_count < row_count ? row_count - column_count : 0)) {\n+\t\tfree(v);\n+\t\tfree(free_row);\n+\t\treturn 0;\n+\t}\n+\n+\t/* augmenting row reduction */\n+\tfor (phase = 0; phase < 2; phase++) {\n+\t\tint k = 0;\n+\n+\t\tsaved_free_count = free_count;\n+\t\tfree_count = 0;\n+\t\twhile (k < saved_free_count) {\n+\t\t\tdouble u1, u2;\n+\t\t\tint j1 = 0, j2, i0;\n+\n+\t\t\ti = free_row[k++];\n+\t\t\tu1 = COST(j1, i) - v[j1];\n+\t\t\tj2 = -1;\n+\t\t\tu2 = DBL_MAX;\n+\t\t\tfor (j = 1; j < column_count; j++) {\n+\t\t\t\tdouble c = COST(j, i) - v[j];\n+\t\t\t\tif (u2 > c) {\n+\t\t\t\t\tif (u1 < c) {\n+\t\t\t\t\t\tu2 = c;\n+\t\t\t\t\t\tj2 = j;\n+\t\t\t\t\t} else {\n+\t\t\t\t\t\tu2 = u1;\n+\t\t\t\t\t\tu1 = c;\n+\t\t\t\t\t\tj2 = j1;\n+\t\t\t\t\t\tj1 = j;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tif (j2 < 0) {\n+\t\t\t\tj2 = j1;\n+\t\t\t\tu2 = u1;\n+\t\t\t}\n+\n+\t\t\ti0 = column2row[j1];\n+\t\t\tif (u1 < u2)\n+\t\t\t\tv[j1] -= u2 - u1;\n+\t\t\telse if (i0 >= 0) {\n+\t\t\t\tj1 = j2;\n+\t\t\t\ti0 = column2row[j1];\n+\t\t\t}\n+\n+\t\t\tif (i0 >= 0) {\n+\t\t\t\tif (u1 < u2)\n+\t\t\t\t\tfree_row[--k] = i0;\n+\t\t\t\telse\n+\t\t\t\t\tfree_row[free_count++] = i0;\n+\t\t\t}\n+\t\t\trow2column[i] = j1;\n+\t\t\tcolumn2row[j1] = i;\n+\t\t}\n+\t}\n+\n+\t/* augmentation */\n+\tsaved_free_count = free_count;\n+\td = xmalloc(sizeof(double) * column_count);\n+\tpred = xmalloc(sizeof(int) * column_count);\n+\tcol = xmalloc(sizeof(int) * column_count);\n+\tfor (free_count = 0; free_count < saved_free_count; free_count++) {\n+\t\tint i1 = free_row[free_count], low = 0, up = 0, last, k;\n+\t\tdouble min, c, u1;\n+\n+\t\tfor (j = 0; j < column_count; j++) {\n+\t\t\td[j] = COST(j, i1) - v[j];\n+\t\t\tpred[j] = i1;\n+\t\t\tcol[j] = j;\n+\t\t}\n+\n+\t\tj = -1;\n+\t\tdo {\n+\t\t\tlast = low;\n+\t\t\tmin = d[col[up++]];\n+\t\t\tfor (k = up; k < column_count; k++) {\n+\t\t\t\tj = col[k];\n+\t\t\t\tc = d[j];\n+\t\t\t\tif (c <= min) {\n+\t\t\t\t\tif (c < min) {\n+\t\t\t\t\t\tup = low;\n+\t\t\t\t\t\tmin = c;\n+\t\t\t\t\t}\n+\t\t\t\t\tcol[k] = col[up];\n+\t\t\t\t\tcol[up++] = j;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tfor (k = low; k < up; k++)\n+\t\t\t\tif (column2row[col[k]] == -1)\n+\t\t\t\t\tgoto update;\n+\n+\t\t\t/* scan a row */\n+\t\t\tdo {\n+\t\t\t\tint j1 = col[low++];\n+\n+\t\t\t\ti = column2row[j1];\n+\t\t\t\tu1 = COST(j1, i) - v[j1] - min;\n+\t\t\t\tfor (k = up; k < column_count; k++) {\n+\t\t\t\t\tj = col[k];\n+\t\t\t\t\tc = COST(j, i) - v[j] - u1;\n+\t\t\t\t\tif (c < d[j]) {\n+\t\t\t\t\t\td[j] = c;\n+\t\t\t\t\t\tpred[j] = i;\n+\t\t\t\t\t\tif (c == min) {\n+\t\t\t\t\t\t\tif (column2row[j] == -1)\n+\t\t\t\t\t\t\t\tgoto update;\n+\t\t\t\t\t\t\tcol[k] = col[up];\n+\t\t\t\t\t\t\tcol[up++] = j;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t} while (low != up);\n+\t\t} while (low == up);\n+\n+update:\n+\t\t/* updating of the column pieces */\n+\t\tfor (k = 0; k < last; k++) {\n+\t\t\tint j1 = col[k];\n+\t\t\tv[j1] += d[j1] - min;\n+\t\t}\n+\n+\t\t/* augmentation */\n+\t\tdo {\n+\t\t\tif (j < 0)\n+\t\t\t\tBUG(\"negative j: %d\", j);\n+\t\t\ti = pred[j];\n+\t\t\tcolumn2row[j] = i;\n+\t\t\tk = j;\n+\t\t\tj = row2column[i];\n+\t\t\trow2column[i] = k;\n+\t\t} while (i1 != i);\n+\t}\n+\n+\tfree(col);\n+\tfree(pred);\n+\tfree(d);\n+\tfree(v);\n+\tfree(free_row);\n+\n+\treturn 0;\n+}\ndiff --git a/hungarian.h b/hungarian.h\nnew file mode 100644\nindex 00000000000..e7cee4bb039\n--- /dev/null\n+++ b/hungarian.h\n@@ -0,0 +1,19 @@\n+#ifndef HUNGARIAN_H\n+#define HUNGARIAN_H\n+\n+/*\n+ * Compute an assignment of columns -> rows (and vice versa) such that every\n+ * column is assigned to at most one row (and vice versa) minimizing the\n+ * overall cost.\n+ *\n+ * The parameter `cost` is the cost matrix: the cost to assign column j to row\n+ * i is `cost[j + column_count * i].\n+ *\n+ * The arrays column2row and row2column will be populated with the respective\n+ * assignments (-1 for unassigned, which can happen only if column_count !=\n+ * row_count).\n+ */\n+int compute_assignment(int column_count, int row_count, double *cost,\n+\t\t       int *column2row, int *row2column);\n+\n+#endif\n-- \n2.17.0.409.g71698f11835\n\n\n"},{"id":"346658","messageId":"cover.1525448066.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:34:27Z","receivedAt":"2018-05-04T15:34:44Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The incredibly useful `git-tbdiff` tool to compare patch series (say, to see\nwhat changed between two iterations sent to the Git mailing list) is slightly\nless useful for this developer due to the fact that it requires the `hungarian`\nand `numpy` Python packages which are for some reason really hard to build in\nMSYS2. So hard that I even had to give up, because it was simply easier to\nreimplement the whole shebang as a builtin command.\n\nThe project at https://github.com/trast/tbdiff seems to be dormant, anyway.\nFunny (and true) story: I looked at the open Pull Requests to see how active\nthat project is, only to find to my surprise that I had submitted one in August\n2015, and that it was still unanswered let alone merged.\n\nWhile at it, I forward-ported AEvar's patch to force `--decorate=no` because\n`git -p tbdiff` would fail otherwise.\n\nSide note: I work on implementing branch-diff not only to make life easier for\nreviewers who have to suffer through v2, v3, ... of my patch series, but also\nto verify my changes before submitting a new iteraion. And also, maybe even\nmore importantly, I plan to use it to verify my merging-rebases of Git for\nWindows (for which I previously used to redirect the pre-rebase/post-rebase\ndiffs vs upstream and then compare them using `git diff --no-index`). And of\ncourse any interested person can see what changes were necessary e.g. in the\nmerging-rebase of Git for Windows onto v2.17.0 by running a command like:\n\n\tbase=^{/Start.the.merging-rebase}\n\ttag=v2.17.0.windows.1\n\tpre=$tag$base^2\n\tgit branch-diff --dual-color $pre$base..$pre $tag$base..$tag\n\nThe --dual-color mode will identify the many changes that are solely due to\ndifferent diff context lines (where otherwise uncolored lines start with a\nbackground-colored -/+ marker), i.e. merge conflicts I had to resolve.\n\nChanges since v1:\n\n- Fixed the usage to *not* say \"rebase--helper\" (oops, now everybody knows that\n  I copy-edited that code... ;-))\n\n- Removed `branch-diff` from the `git help` output for now, by removing the\n  `info` keyword from the respective line in command-list.txt.\n\n- Removed the bogus `COLOR_DUAL_MODE` constant that was introduced in one\n  patch, only to be removed in the next one.\n\n- Fixed typo \"emtpy\".\n\n- Fixed `make sparse` warnings.\n\n- Changed the usage string to avoid the confusing lower-case bare `base`.\n\n- Fixed tyop in commit message: \"Pythong\".\n\n- Removed an awkward empty line before a `continue;` statement.\n\n- Released the `line` strbuf after use.\n\n- Fixed a typo and some \"foreigner's English\" in the commit message of\n  \"branch-diff: also show the diff between patches\".\n\n- Avoided introducing --no-patches too early and then replacing it by the\n  version that uses the diff_options.\n\n- Fixed the man page to continue with upper-case after a colon.\n\nThe branch-diff of v1 vs 2 is best viewed with --dual-color; This helps e.g.\nwith identifying changed *context* lines in the diff:\n\ngit branch-diff --dual-color origin/master branch-diff-v1 branch-diff-v2\n\n(assuming that your `origin` points to git.git and that you fetched the tags\nfrom https://github.com/dscho/git). For example, you will see that the only\nchange in patch #10 is a change in the diff context (due to the change of the\nusage string in patch #2):\n\n10:  9810869ced9 ! 10:  2695a6abc46 branch-diff: do not show \"function names\" in hunk headers\n    @@ -17,7 +17,7 @@\n     +#include \"userdiff.h\"\n\n      static const char * const builtin_branch_diff_usage[] = {\n    -   N_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n    + N_(\"git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n     @@\n        return data;\n      }\n\n\nJohannes Schindelin (17):\n  Add a function to solve least-cost assignment problems\n  Add a new builtin: branch-diff\n  branch-diff: first rudimentary implementation\n  branch-diff: improve the order of the shown commits\n  branch-diff: also show the diff between patches\n  branch-diff: right-trim commit messages\n  branch-diff: indent the diffs just like tbdiff\n  branch-diff: suppress the diff headers\n  branch-diff: adjust the output of the commit pairs\n  branch-diff: do not show \"function names\" in hunk headers\n  branch-diff: use color for the commit pairs\n  color: provide inverted colors, too\n  diff: add an internal option to dual-color diffs of diffs\n  branch-diff: offer to dual-color the diffs\n  branch-diff --dual-color: work around bogus white-space warning\n  branch-diff: add a man page\n  completion: support branch-diff\n\nThomas Rast (1):\n  branch-diff: add tests\n\n .gitignore                             |   1 +\n Documentation/git-branch-diff.txt      | 239 ++++++++++\n Makefile                               |   2 +\n builtin.h                              |   1 +\n builtin/branch-diff.c                  | 534 ++++++++++++++++++++++\n color.h                                |   6 +\n command-list.txt                       |   1 +\n contrib/completion/git-completion.bash |  18 +\n diff.c                                 |  76 +++-\n diff.h                                 |   6 +-\n git.c                                  |   1 +\n hungarian.c                            | 205 +++++++++\n hungarian.h                            |  19 +\n t/.gitattributes                       |   1 +\n t/t7910-branch-diff.sh                 | 144 ++++++\n t/t7910/history.export                 | 604 +++++++++++++++++++++++++\n 16 files changed, 1846 insertions(+), 12 deletions(-)\n create mode 100644 Documentation/git-branch-diff.txt\n create mode 100644 builtin/branch-diff.c\n create mode 100644 hungarian.c\n create mode 100644 hungarian.h\n create mode 100755 t/t7910-branch-diff.sh\n create mode 100644 t/t7910/history.export\n\n\nbase-commit: 1f1cddd558b54bb0ce19c8ace353fd07b758510d\nPublished-As: https://github.com/dscho/git/releases/tag/branch-diff-v2\nFetch-It-Via: git fetch https://github.com/dscho/git branch-diff-v2\n\nBranch-diff vs v1:\n  1: 8bc517e35d4 !  1: a1ea0320b64 Add a new builtin: branch-diff\n     @@ -7,8 +7,9 @@\n          the next commits will turn `branch-diff` into a full-blown replacement\n          for `tbdiff`.\n          \n     -    At this point, we ignore tbdiff's color options, as they will all be\n     -    implemented later and require some patches to the diff machinery.\n     +    At this point, we ignore tbdiff's color options as well as the\n     +    --no-patches option, as they will all be implemented later using\n     +    diff_options.\n          \n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     @@ -54,14 +55,15 @@\n      +++ b/builtin/branch-diff.c\n      @@\n      +#include \"cache.h\"\n     ++#include \"builtin.h\"\n      +#include \"parse-options.h\"\n      +\n      +static const char * const builtin_branch_diff_usage[] = {\n     -+\tN_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n     -+\tNULL\n     ++N_(\"git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n     ++N_(\"git branch-diff [<options>] <old-tip>...<new-tip>\"),\n     ++N_(\"git branch-diff [<options>] <base> <old-tip> <new-tip>\"),\n     ++NULL\n      +};\n     -+\n     -+#define COLOR_DUAL_MODE 2\n      +\n      +static int parse_creation_weight(const struct option *opt, const char *arg,\n      +\t\t\t\t int unset)\n     @@ -76,11 +78,8 @@\n      +\n      +int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n      +{\n     -+\tint no_patches = 0;\n      +\tdouble creation_weight = 0.6;\n      +\tstruct option options[] = {\n     -+\t\tOPT_BOOL(0, \"no-patches\", &no_patches,\n     -+\t\t\t N_(\"short format (no diffs)\")),\n      +\t\t{ OPTION_CALLBACK,\n      +\t\t\t0, \"creation-weight\", &creation_weight, N_(\"factor\"),\n      +\t\t\tN_(\"Fudge factor by which creation is weighted [0.6]\"),\n     @@ -101,7 +100,7 @@\n       git-bisect                              mainporcelain           info\n       git-blame                               ancillaryinterrogators\n       git-branch                              mainporcelain           history\n     -+git-branch-diff                         mainporcelain           info\n     ++git-branch-diff                         mainporcelain\n       git-bundle                              mainporcelain\n       git-cat-file                            plumbinginterrogators\n       git-check-attr                          purehelpers\n  2: ec51c71779a !  2: e530e450ebd branch-diff: first rudimentary implementation\n     @@ -13,7 +13,7 @@\n          really concentrates on getting the patch matching part right.\n          \n          Note: due to differences in the diff algorithm (`tbdiff` uses the\n     -    Pythong module `difflib`, Git uses its xdiff fork), the cost matrix\n     +    Python module `difflib`, Git uses its xdiff fork), the cost matrix\n          calculated by `branch-diff` is different (but very similar) to the one\n          calculated by `tbdiff`. Therefore, it is possible that they find\n          different matching commits in corner cases (e.g. when a patch was split\n     @@ -26,6 +26,7 @@\n      +++ b/builtin/branch-diff.c\n      @@\n       #include \"cache.h\"\n     + #include \"builtin.h\"\n       #include \"parse-options.h\"\n      +#include \"string-list.h\"\n      +#include \"run-command.h\"\n     @@ -35,15 +36,7 @@\n      +#include \"hungarian.h\"\n       \n       static const char * const builtin_branch_diff_usage[] = {\n     - \tN_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n     - \tNULL\n     - };\n     - \n     --#define COLOR_DUAL_MODE 2\n     --\n     - static int parse_creation_weight(const struct option *opt, const char *arg,\n     - \t\t\t\t int unset)\n     - {\n     + N_(\"git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n      @@\n       \treturn 0;\n       }\n     @@ -128,7 +121,6 @@\n      +\t\t\t\tstrbuf_addbuf(&buf, &line);\n      +\t\t\t\tstrbuf_addch(&buf, '\\n');\n      +\t\t\t}\n     -+\n      +\t\t\tcontinue;\n      +\t\t} else if (starts_with(line.buf, \"@@ \"))\n      +\t\t\tstrbuf_addstr(&buf, \"@@\");\n     @@ -148,6 +140,7 @@\n      +\t\tutil->diffsize++;\n      +\t}\n      +\tfclose(in);\n     ++\tstrbuf_release(&line);\n      +\n      +\tif (util)\n      +\t\tstring_list_append(list, buf.buf)->util = util;\n     @@ -323,7 +316,7 @@\n      +\n       int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n       {\n     - \tint no_patches = 0;\n     + \tdouble creation_weight = 0.6;\n      @@\n       \t\t\t0, parse_creation_weight },\n       \t\tOPT_END()\n     @@ -366,7 +359,7 @@\n      +\t\tstrbuf_addf(&range1, \"%s..%.*s\", b, a_len, a);\n      +\t\tstrbuf_addf(&range2, \"%.*s..%s\", a_len, a, b);\n      +\t} else {\n     -+\t\terror(\"Need two commit ranges\");\n     ++\t\terror(_(\"need two commit ranges\"));\n      +\t\tusage_with_options(builtin_branch_diff_usage, options);\n      +\t}\n      +\n  3: 6a618d6010f =  3: 3032e2709b8 branch-diff: improve the order of the shown commits\n  4: 141e5b63e45 !  4: 12d9c7977fd branch-diff: also show the diff between patches\n     @@ -4,10 +4,12 @@\n          \n          Just like tbdiff, we now show the diff between matching patches. This is\n          a \"diff of two diffs\", so it can be a bit daunting to read for the\n     -    beginnger.\n     +    beginner.\n          \n     -    This brings branch-diff closer to be feature-complete with regard to\n     -    tbdiff.\n     +    And just like tbdiff, we now also accept the `--no-patches` option\n     +    (which is actually equivalent to the diff option `-s`).\n     +    \n     +    This brings branch-diff closer to feature parity with regard to tbdiff.\n          \n          An alternative would be to display an interdiff, i.e. the hypothetical\n          diff which is the result of first reverting the old diff and then\n     @@ -34,7 +36,7 @@\n      +#include \"diffcore.h\"\n       \n       static const char * const builtin_branch_diff_usage[] = {\n     - \tN_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n     + N_(\"git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n      @@\n       \treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n       }\n     @@ -82,12 +84,9 @@\n       \n       int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n       {\n     --\tint no_patches = 0;\n     -+\tstruct diff_options diffopt = { 0 };\n     ++\tstruct diff_options diffopt = { NULL };\n       \tdouble creation_weight = 0.6;\n       \tstruct option options[] = {\n     --\t\tOPT_BOOL(0, \"no-patches\", &no_patches,\n     --\t\t\t N_(\"short format (no diffs)\")),\n      +\t\tOPT_SET_INT(0, \"no-patches\", &diffopt.output_format,\n      +\t\t\t    N_(\"short format (no diffs)\"),\n      +\t\t\t    DIFF_FORMAT_NO_OUTPUT),\n  5: 303419c56c4 =  5: 53ee6ba3873 branch-diff: right-trim commit messages\n  6: 218e56a69e0 !  6: c856c460a47 branch-diff: indent the diffs just like tbdiff\n     @@ -26,7 +26,7 @@\n      @@\n       int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n       {\n     - \tstruct diff_options diffopt = { 0 };\n     + \tstruct diff_options diffopt = { NULL };\n      +\tstruct strbuf four_spaces = STRBUF_INIT;\n       \tdouble creation_weight = 0.6;\n       \tstruct option options[] = {\n  7: 00ea45123ae =  7: 35a9681a192 branch-diff: suppress the diff headers\n  8: 40471263d3c !  8: 0e4c8279e46 branch-diff: adjust the output of the commit pairs\n     @@ -19,7 +19,7 @@\n      +#include \"pretty.h\"\n       \n       static const char * const builtin_branch_diff_usage[] = {\n     - \tN_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n     + N_(\"git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n      @@\n       \treturn res;\n       }\n  9: 9810869ced9 !  9: 2695a6abc46 branch-diff: do not show \"function names\" in hunk headers\n     @@ -17,7 +17,7 @@\n      +#include \"userdiff.h\"\n       \n       static const char * const builtin_branch_diff_usage[] = {\n     - \tN_(\"git rebase--helper [<options>] ( A..B C..D | A...B | base A B )\"),\n     + N_(\"git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n      @@\n       \treturn data;\n       }\n 10: fe12b99a0b4 ! 10: 313beeed3d1 branch-diff: add tests\n     @@ -12,7 +12,7 @@\n          sometimes ambiguous. In this case, a comment line and an empty line are\n          added, but it is ambiguous whether they were added after the existing\n          empty line, or whether an empty line and the comment line are added\n     -    *before* the existing emtpy line. And apparently xdiff picks a different\n     +    *before* the existing empty line. And apparently xdiff picks a different\n          option here than Python's difflib.\n          \n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n 11: c89937afc28 = 11: ba4791918c7 branch-diff: use color for the commit pairs\n 12: a94e94edf65 = 12: 1ebbe359547 color: provide inverted colors, too\n 13: 153425de2df = 13: ae0ea5dfca5 diff: add an internal option to dual-color diffs of diffs\n 14: 2f8017c732f ! 14: b9be01705d6 branch-diff: offer to dual-color the diffs\n     @@ -18,7 +18,7 @@\n      +++ b/builtin/branch-diff.c\n      @@\n       {\n     - \tstruct diff_options diffopt = { 0 };\n     + \tstruct diff_options diffopt = { NULL };\n       \tstruct strbuf four_spaces = STRBUF_INIT;\n      +\tint dual_color = 0;\n       \tdouble creation_weight = 0.6;\n 15: 62f0e2cf73f = 15: b99ab186c4f branch-diff --dual-color: work around bogus white-space warning\n 16: edb34bd4f8d ! 16: 950c7537701 branch-diff: add a man page\n     @@ -158,7 +158,7 @@\n      +series), the cost matrix is extended to allow for that, by adding\n      +fixed-cost entries for wholesale deletes/adds.\n      +\n     -+Example: let commits `1--2` be the first iteration of a patch series and\n     ++Example: Let commits `1--2` be the first iteration of a patch series and\n      +`A--C` the second iteration. Let's assume that `A` is a cherry-pick of\n      +`2,` and `C` is a cherry-pick of `1` but with a small modification (say,\n      +a fixed typo). Visualize the commits as a bipartite graph:\n 17: 5d047a830f1 = 17: 71698f11835 completion: support branch-diff\n-- \n2.17.0.409.g71698f11835\n\n"},{"id":"346659","messageId":"a1ea0320b64527ee6ce9856dcf359513d13052b7.1525448066.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:34:32Z","receivedAt":"2018-05-04T15:34:50Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This builtin does not do a whole lot so far, apart from showing a usage\nthat is oddly similar to that of `git tbdiff`. And for a good reason:\nthe next commits will turn `branch-diff` into a full-blown replacement\nfor `tbdiff`.\n\nAt this point, we ignore tbdiff's color options as well as the\n--no-patches option, as they will all be implemented later using\ndiff_options.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n .gitignore            |  1 +\n Makefile              |  1 +\n builtin.h             |  1 +\n builtin/branch-diff.c | 38 ++++++++++++++++++++++++++++++++++++++\n command-list.txt      |  1 +\n git.c                 |  1 +\n 6 files changed, 43 insertions(+)\n create mode 100644 builtin/branch-diff.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 833ef3b0b78..1346a64492f 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -20,6 +20,7 @@\n /git-bisect--helper\n /git-blame\n /git-branch\n+/git-branch-diff\n /git-bundle\n /git-cat-file\n /git-check-attr\ndiff --git a/Makefile b/Makefile\nindex 96f2e76a904..9b1984776d8 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -953,6 +953,7 @@ BUILTIN_OBJS += builtin/archive.o\n BUILTIN_OBJS += builtin/bisect--helper.o\n BUILTIN_OBJS += builtin/blame.o\n BUILTIN_OBJS += builtin/branch.o\n+BUILTIN_OBJS += builtin/branch-diff.o\n BUILTIN_OBJS += builtin/bundle.o\n BUILTIN_OBJS += builtin/cat-file.o\n BUILTIN_OBJS += builtin/check-attr.o\ndiff --git a/builtin.h b/builtin.h\nindex 42378f3aa47..e1c4d2a529a 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -135,6 +135,7 @@ extern int cmd_archive(int argc, const char **argv, const char *prefix);\n extern int cmd_bisect__helper(int argc, const char **argv, const char *prefix);\n extern int cmd_blame(int argc, const char **argv, const char *prefix);\n extern int cmd_branch(int argc, const char **argv, const char *prefix);\n+extern int cmd_branch_diff(int argc, const char **argv, const char *prefix);\n extern int cmd_bundle(int argc, const char **argv, const char *prefix);\n extern int cmd_cat_file(int argc, const char **argv, const char *prefix);\n extern int cmd_checkout(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nnew file mode 100644\nindex 00000000000..60a4b4fbe30\n--- /dev/null\n+++ b/builtin/branch-diff.c\n@@ -0,0 +1,38 @@\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"parse-options.h\"\n+\n+static const char * const builtin_branch_diff_usage[] = {\n+N_(\"git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n+N_(\"git branch-diff [<options>] <old-tip>...<new-tip>\"),\n+N_(\"git branch-diff [<options>] <base> <old-tip> <new-tip>\"),\n+NULL\n+};\n+\n+static int parse_creation_weight(const struct option *opt, const char *arg,\n+\t\t\t\t int unset)\n+{\n+\tdouble *d = opt->value;\n+\tif (unset)\n+\t\t*d = 0.6;\n+\telse\n+\t\t*d = atof(arg);\n+\treturn 0;\n+}\n+\n+int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n+{\n+\tdouble creation_weight = 0.6;\n+\tstruct option options[] = {\n+\t\t{ OPTION_CALLBACK,\n+\t\t\t0, \"creation-weight\", &creation_weight, N_(\"factor\"),\n+\t\t\tN_(\"Fudge factor by which creation is weighted [0.6]\"),\n+\t\t\t0, parse_creation_weight },\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, options,\n+\t\t\tbuiltin_branch_diff_usage, 0);\n+\n+\treturn 0;\n+}\ndiff --git a/command-list.txt b/command-list.txt\nindex a1fad28fd82..d01b9063e81 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -19,6 +19,7 @@ git-archive                             mainporcelain\n git-bisect                              mainporcelain           info\n git-blame                               ancillaryinterrogators\n git-branch                              mainporcelain           history\n+git-branch-diff                         mainporcelain\n git-bundle                              mainporcelain\n git-cat-file                            plumbinginterrogators\n git-check-attr                          purehelpers\ndiff --git a/git.c b/git.c\nindex f598fae7b7a..d2794fb6f5d 100644\n--- a/git.c\n+++ b/git.c\n@@ -377,6 +377,7 @@ static struct cmd_struct commands[] = {\n \t{ \"bisect--helper\", cmd_bisect__helper, RUN_SETUP },\n \t{ \"blame\", cmd_blame, RUN_SETUP },\n \t{ \"branch\", cmd_branch, RUN_SETUP | DELAY_PAGER_CONFIG },\n+\t{ \"branch-diff\", cmd_branch_diff, RUN_SETUP | USE_PAGER },\n \t{ \"bundle\", cmd_bundle, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"cat-file\", cmd_cat_file, RUN_SETUP },\n \t{ \"check-attr\", cmd_check_attr, RUN_SETUP },\n-- \n2.17.0.409.g71698f11835\n\n\n"},{"id":"346660","messageId":"12d9c7977fdf9cc73c810d2ca31d86a4971cf7f4.1525448066.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 05/18] branch-diff: also show the diff between patches","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:34:42Z","receivedAt":"2018-05-04T15:34:53Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Just like tbdiff, we now show the diff between matching patches. This is\na \"diff of two diffs\", so it can be a bit daunting to read for the\nbeginner.\n\nAnd just like tbdiff, we now also accept the `--no-patches` option\n(which is actually equivalent to the diff option `-s`).\n\nThis brings branch-diff closer to feature parity with regard to tbdiff.\n\nAn alternative would be to display an interdiff, i.e. the hypothetical\ndiff which is the result of first reverting the old diff and then\napplying the new diff.\n\nEspecially when rebasing often, an interdiff is often not feasible,\nthough: if the old diff cannot be applied in reverse (due to a moving\nupstream), an interdiff can simply not be inferred.\n\nNote: while we now parse diff options such as --color, the effect is not\nyet the same as in tbdiff, where also the commit pairs would be colored.\nThis is left for a later commit.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 53 +++++++++++++++++++++++++++++++++++++++----\n 1 file changed, 49 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex 92302b1c339..b23d66a3b1c 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -7,6 +7,8 @@\n #include \"hashmap.h\"\n #include \"xdiff-interface.h\"\n #include \"hungarian.h\"\n+#include \"diff.h\"\n+#include \"diffcore.h\"\n \n static const char * const builtin_branch_diff_usage[] = {\n N_(\"git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n@@ -272,7 +274,31 @@ static const char *short_oid(struct patch_util *util)\n \treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n }\n \n-static void output(struct string_list *a, struct string_list *b)\n+static struct diff_filespec *get_filespec(const char *name, const char *p)\n+{\n+\tstruct diff_filespec *spec = alloc_filespec(name);\n+\n+\tfill_filespec(spec, &null_oid, 0, 0644);\n+\tspec->data = (char *)p;\n+\tspec->size = strlen(p);\n+\tspec->should_munmap = 0;\n+\tspec->is_stdin = 1;\n+\n+\treturn spec;\n+}\n+\n+static void patch_diff(const char *a, const char *b,\n+\t\t\t      struct diff_options *diffopt)\n+{\n+\tdiff_queue(&diff_queued_diff,\n+\t\t   get_filespec(\"a\", a), get_filespec(\"b\", b));\n+\n+\tdiffcore_std(diffopt);\n+\tdiff_flush(diffopt);\n+}\n+\n+static void output(struct string_list *a, struct string_list *b,\n+\t\t   struct diff_options *diffopt)\n {\n \tint i = 0, j = 0;\n \n@@ -314,6 +340,9 @@ static void output(struct string_list *a, struct string_list *b)\n \t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n \t\t\t       b_util->matching + 1, short_oid(a_util),\n \t\t\t       j + 1, short_oid(b_util));\n+\t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n+\t\t\t\tpatch_diff(a->items[b_util->matching].string,\n+\t\t\t\t\t   b->items[j].string, diffopt);\n \t\t\ta_util->shown = 1;\n \t\t\tj++;\n \t\t}\n@@ -322,21 +351,37 @@ static void output(struct string_list *a, struct string_list *b)\n \n int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n {\n+\tstruct diff_options diffopt = { NULL };\n \tdouble creation_weight = 0.6;\n \tstruct option options[] = {\n+\t\tOPT_SET_INT(0, \"no-patches\", &diffopt.output_format,\n+\t\t\t    N_(\"short format (no diffs)\"),\n+\t\t\t    DIFF_FORMAT_NO_OUTPUT),\n \t\t{ OPTION_CALLBACK,\n \t\t\t0, \"creation-weight\", &creation_weight, N_(\"factor\"),\n \t\t\tN_(\"Fudge factor by which creation is weighted [0.6]\"),\n \t\t\t0, parse_creation_weight },\n \t\tOPT_END()\n \t};\n-\tint res = 0;\n+\tint i, j, res = 0;\n \tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n \tstruct string_list branch1 = STRING_LIST_INIT_DUP;\n \tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n \n+\tdiff_setup(&diffopt);\n+\tdiffopt.output_format = DIFF_FORMAT_PATCH;\n+\n \targc = parse_options(argc, argv, NULL, options,\n-\t\t\tbuiltin_branch_diff_usage, 0);\n+\t\t\tbuiltin_branch_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n+\n+\tfor (i = j = 0; i < argc; i++) {\n+\t\tint c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);\n+\n+\t\tif (!c)\n+\t\t\targv[j++] = argv[i];\n+\t}\n+\targc = j;\n+\tdiff_setup_done(&diffopt);\n \n \tif (argc == 2) {\n \t\tif (!strstr(argv[0], \"..\"))\n@@ -380,7 +425,7 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n \t\tfind_exact_matches(&branch1, &branch2);\n \t\tres = get_correspondences(&branch1, &branch2, creation_weight);\n \t\tif (!res)\n-\t\t\toutput(&branch1, &branch2);\n+\t\t\toutput(&branch1, &branch2, &diffopt);\n \t}\n \n \tstrbuf_release(&range1);\n-- \n2.17.0.409.g71698f11835\n\n\n"},{"id":"346661","messageId":"53ee6ba3873013956c6d6067c5a911688014cef7.1525448066.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 06/18] branch-diff: right-trim commit messages","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:34:43Z","receivedAt":"2018-05-04T15:34:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"When comparing commit messages, we need to keep in mind that they are\nindented by four spaces. That is, empty lines are no longer empty, but\nhave \"trailing whitespace\". When displaying them in color, that results\nin those nagging red lines.\n\nLet's just right-trim the lines in the commit message, it's not like\ntrailing white-space in the commit messages are important enough to care\nabout in branch-diff.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex b23d66a3b1c..e2337b905b1 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -105,6 +105,7 @@ static int read_patches(const char *range, struct string_list *list)\n \t\t\t\tstrbuf_addbuf(&buf, &line);\n \t\t\t\tstrbuf_addstr(&buf, \"\\n\\n\");\n \t\t\t} else if (starts_with(line.buf, \"    \")) {\n+\t\t\t\tstrbuf_rtrim(&line);\n \t\t\t\tstrbuf_addbuf(&buf, &line);\n \t\t\t\tstrbuf_addch(&buf, '\\n');\n \t\t\t}\n-- \n2.17.0.409.g71698f11835\n\n\n"},{"id":"346662","messageId":"35a9681a192e63d1b5dd1a60571f5c5991e90457.1525448066.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 08/18] branch-diff: suppress the diff headers","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:34:47Z","receivedAt":"2018-05-04T15:35:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"When showing the diff between corresponding patches of the two branch\nversions, we have to make up a fake filename to run the diff machinery.\n\nThat filename does not carry any meaningful information, hence tbdiff\nsuppresses it. So we should, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 1 +\n diff.c                | 5 ++++-\n diff.h                | 1 +\n 3 files changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex 4fc9fd74531..ed520d6229d 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -377,6 +377,7 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n \n \tdiff_setup(&diffopt);\n \tdiffopt.output_format = DIFF_FORMAT_PATCH;\n+\tdiffopt.flags.suppress_diff_headers = 1;\n \tdiffopt.output_prefix = output_prefix_cb;\n \tstrbuf_addstr(&four_spaces, \"    \");\n \tdiffopt.output_prefix_data = &four_spaces;\ndiff --git a/diff.c b/diff.c\nindex 1289df4b1f9..f1bda0db3f5 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -3197,13 +3197,16 @@ static void builtin_diff(const char *name_a,\n \t\tmemset(&xpp, 0, sizeof(xpp));\n \t\tmemset(&xecfg, 0, sizeof(xecfg));\n \t\tmemset(&ecbdata, 0, sizeof(ecbdata));\n+\t\tif (o->flags.suppress_diff_headers)\n+\t\t\tlbl[0] = NULL;\n \t\tecbdata.label_path = lbl;\n \t\tecbdata.color_diff = want_color(o->use_color);\n \t\tecbdata.ws_rule = whitespace_rule(name_b);\n \t\tif (ecbdata.ws_rule & WS_BLANK_AT_EOF)\n \t\t\tcheck_blank_at_eof(&mf1, &mf2, &ecbdata);\n \t\tecbdata.opt = o;\n-\t\tecbdata.header = header.len ? &header : NULL;\n+\t\tif (header.len && !o->flags.suppress_diff_headers)\n+\t\t\tecbdata.header = &header;\n \t\txpp.flags = o->xdl_opts;\n \t\txpp.anchors = o->anchors;\n \t\txpp.anchors_nr = o->anchors_nr;\ndiff --git a/diff.h b/diff.h\nindex d29560f822c..0dd6a71af60 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -94,6 +94,7 @@ struct diff_flags {\n \tunsigned funccontext:1;\n \tunsigned default_follow_renames:1;\n \tunsigned stat_with_summary:1;\n+\tunsigned suppress_diff_headers:1;\n };\n \n static inline void diff_flags_or(struct diff_flags *a,\n-- \n2.17.0.409.g71698f11835\n\n\n"},{"id":"346663","messageId":"2695a6abc46ce55057762f8d6bdae6ece54274ac.1525448066.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 10/18] branch-diff: do not show \"function names\" in hunk headers","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:34:50Z","receivedAt":"2018-05-04T15:35:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"We are comparing complete, formatted commit messages with patches. There\nare no function names here, so stop looking for them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex 5b187890bdf..89d75c93115 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -11,6 +11,7 @@\n #include \"diffcore.h\"\n #include \"commit.h\"\n #include \"pretty.h\"\n+#include \"userdiff.h\"\n \n static const char * const builtin_branch_diff_usage[] = {\n N_(\"git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n@@ -330,6 +331,10 @@ static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)\n \treturn data;\n }\n \n+static struct userdiff_driver no_func_name = {\n+\t.funcname = { \"$^\", 0 }\n+};\n+\n static struct diff_filespec *get_filespec(const char *name, const char *p)\n {\n \tstruct diff_filespec *spec = alloc_filespec(name);\n@@ -339,6 +344,7 @@ static struct diff_filespec *get_filespec(const char *name, const char *p)\n \tspec->size = strlen(p);\n \tspec->should_munmap = 0;\n \tspec->is_stdin = 1;\n+\tspec->driver = &no_func_name;\n \n \treturn spec;\n }\n-- \n2.17.0.409.g71698f11835\n\n\n"},{"id":"346664","messageId":"e530e450ebd307b72a64c81e4192bbb0df57cf65.1525448066.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 03/18] branch-diff: first rudimentary implementation","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:34:36Z","receivedAt":"2018-05-04T15:35:04Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"At this stage, `git branch-diff` can determine corresponding commits of\ntwo related commit ranges. This makes use of the recently introduced\nimplementation of the Hungarian algorithm.\n\nThe core of this patch is a straight port of the ideas of tbdiff, the\nseemingly dormant project at https://github.com/trast/tbdiff.\n\nThe output does not at all match `tbdiff`'s output yet, as this patch\nreally concentrates on getting the patch matching part right.\n\nNote: due to differences in the diff algorithm (`tbdiff` uses the\nPython module `difflib`, Git uses its xdiff fork), the cost matrix\ncalculated by `branch-diff` is different (but very similar) to the one\ncalculated by `tbdiff`. Therefore, it is possible that they find\ndifferent matching commits in corner cases (e.g. when a patch was split\ninto two patches of roughly equal length).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 335 +++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 334 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex 60a4b4fbe30..c462681067c 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -1,6 +1,12 @@\n #include \"cache.h\"\n #include \"builtin.h\"\n #include \"parse-options.h\"\n+#include \"string-list.h\"\n+#include \"run-command.h\"\n+#include \"argv-array.h\"\n+#include \"hashmap.h\"\n+#include \"xdiff-interface.h\"\n+#include \"hungarian.h\"\n \n static const char * const builtin_branch_diff_usage[] = {\n N_(\"git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n@@ -20,6 +26,279 @@ static int parse_creation_weight(const struct option *opt, const char *arg,\n \treturn 0;\n }\n \n+struct patch_util {\n+\t/* For the search for an exact match */\n+\tstruct hashmap_entry e;\n+\tconst char *diff, *patch;\n+\n+\tint i;\n+\tint diffsize;\n+\tsize_t diff_offset;\n+\t/* the index of the matching item in the other branch, or -1 */\n+\tint matching;\n+\tstruct object_id oid;\n+};\n+\n+/*\n+ * Reads the patches into a string list, with the `util` field being populated\n+ * as struct object_id (will need to be free()d).\n+ */\n+static int read_patches(const char *range, struct string_list *list)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tFILE *in;\n+\tstruct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;\n+\tstruct patch_util *util = NULL;\n+\tint in_header = 1;\n+\n+\targv_array_pushl(&cp.args, \"log\", \"--no-color\", \"-p\", \"--no-merges\",\n+\t\t\t\"--reverse\", \"--date-order\", \"--decorate=no\",\n+\t\t\t\"--no-abbrev-commit\", range,\n+\t\t\tNULL);\n+\tcp.out = -1;\n+\tcp.no_stdin = 1;\n+\tcp.git_cmd = 1;\n+\n+\tif (start_command(&cp))\n+\t\treturn error_errno(_(\"could not start `log`\"));\n+\tin = fdopen(cp.out, \"r\");\n+\tif (!in) {\n+\t\terror_errno(_(\"could not read `log` output\"));\n+\t\tfinish_command(&cp);\n+\t\treturn -1;\n+\t}\n+\n+\twhile (strbuf_getline(&line, in) != EOF) {\n+\t\tconst char *p;\n+\n+\t\tif (skip_prefix(line.buf, \"commit \", &p)) {\n+\t\t\tif (util) {\n+\t\t\t\tstring_list_append(list, buf.buf)->util = util;\n+\t\t\t\tstrbuf_reset(&buf);\n+\t\t\t}\n+\t\t\tutil = xcalloc(sizeof(*util), 1);\n+\t\t\tif (get_oid(p, &util->oid)) {\n+\t\t\t\terror(_(\"could not parse commit '%s'\"), p);\n+\t\t\t\tfree(util);\n+\t\t\t\tstring_list_clear(list, 1);\n+\t\t\t\tstrbuf_release(&buf);\n+\t\t\t\tstrbuf_release(&line);\n+\t\t\t\tfclose(in);\n+\t\t\t\tfinish_command(&cp);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t\tutil->matching = -1;\n+\t\t\tin_header = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (starts_with(line.buf, \"diff --git\")) {\n+\t\t\tin_header = 0;\n+\t\t\tstrbuf_addch(&buf, '\\n');\n+\t\t\tif (!util->diff_offset)\n+\t\t\t\tutil->diff_offset = buf.len;\n+\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\t} else if (in_header) {\n+\t\t\tif (starts_with(line.buf, \"Author: \")) {\n+\t\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\t\t\tstrbuf_addstr(&buf, \"\\n\\n\");\n+\t\t\t} else if (starts_with(line.buf, \"    \")) {\n+\t\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\t\t\tstrbuf_addch(&buf, '\\n');\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t} else if (starts_with(line.buf, \"@@ \"))\n+\t\t\tstrbuf_addstr(&buf, \"@@\");\n+\t\telse if (line.buf[0] && !starts_with(line.buf, \"index \"))\n+\t\t\t/*\n+\t\t\t * A completely blank (not ' \\n', which is context)\n+\t\t\t * line is not valid in a diff.  We skip it\n+\t\t\t * silently, because this neatly handles the blank\n+\t\t\t * separator line between commits in git-log\n+\t\t\t * output.\n+\t\t\t */\n+\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\telse\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_addch(&buf, '\\n');\n+\t\tutil->diffsize++;\n+\t}\n+\tfclose(in);\n+\tstrbuf_release(&line);\n+\n+\tif (util)\n+\t\tstring_list_append(list, buf.buf)->util = util;\n+\tstrbuf_release(&buf);\n+\n+\tif (finish_command(&cp))\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+static int patch_util_cmp(const void *dummy, const struct patch_util *a,\n+\t\t     const struct patch_util *b, const char *keydata)\n+{\n+\treturn strcmp(a->diff, keydata ? keydata : b->diff);\n+}\n+\n+static void find_exact_matches(struct string_list *a, struct string_list *b)\n+{\n+\tstruct hashmap map;\n+\tint i;\n+\n+\thashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);\n+\n+\t/* First, add the patches of a to a hash map */\n+\tfor (i = 0; i < a->nr; i++) {\n+\t\tstruct patch_util *util = a->items[i].util;\n+\n+\t\tutil->i = i;\n+\t\tutil->patch = a->items[i].string;\n+\t\tutil->diff = util->patch + util->diff_offset;\n+\t\thashmap_entry_init(util, strhash(util->diff));\n+\t\thashmap_add(&map, util);\n+\t}\n+\n+\t/* Now try to find exact matches in b */\n+\tfor (i = 0; i < b->nr; i++) {\n+\t\tstruct patch_util *util = b->items[i].util, *other;\n+\n+\t\tutil->i = i;\n+\t\tutil->patch = b->items[i].string;\n+\t\tutil->diff = util->patch + util->diff_offset;\n+\t\thashmap_entry_init(util, strhash(util->diff));\n+\t\tother = hashmap_remove(&map, util, NULL);\n+\t\tif (other) {\n+\t\t\tif (other->matching >= 0)\n+\t\t\t\tBUG(\"already assigned!\");\n+\n+\t\t\tother->matching = i;\n+\t\t\tutil->matching = other->i;\n+\t\t}\n+\t}\n+\n+\thashmap_free(&map, 0);\n+}\n+\n+static void diffsize_consume(void *data, char *line, unsigned long len)\n+{\n+\t(*(int *)data)++;\n+}\n+\n+static int diffsize(const char *a, const char *b)\n+{\n+\txpparam_t pp = { 0 };\n+\txdemitconf_t cfg = { 0 };\n+\tmmfile_t mf1, mf2;\n+\tint count = 0;\n+\n+\tmf1.ptr = (char *)a;\n+\tmf1.size = strlen(a);\n+\tmf2.ptr = (char *)b;\n+\tmf2.size = strlen(b);\n+\n+\tcfg.ctxlen = 3;\n+\tif (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))\n+\t\treturn count;\n+\n+\terror(_(\"failed to generate diff\"));\n+\treturn INT_MAX;\n+}\n+\n+static int get_correspondences(struct string_list *a, struct string_list *b,\n+\t\t\t       double creation_weight)\n+{\n+\tint n = a->nr + b->nr;\n+\tdouble *cost = xmalloc(sizeof(double) * n * n), c;\n+\tint *a2b = xmalloc(sizeof(int) * n), *b2a = xmalloc(sizeof(int) * n);\n+\tint i, j, res;\n+\n+\tfor (i = 0; i < a->nr; i++) {\n+\t\tstruct patch_util *a_util = a->items[i].util;\n+\n+\t\tfor (j = 0; j < b->nr; j++) {\n+\t\t\tstruct patch_util *b_util = b->items[j].util;\n+\n+\t\t\tif (a_util->matching == j)\n+\t\t\t\tc = 0;\n+\t\t\telse if (a_util->matching < 0 && b_util->matching < 0)\n+\t\t\t\tc = diffsize(a_util->diff, b_util->diff);\n+\t\t\telse\n+\t\t\t\tc = INT_MAX;\n+\t\t\tcost[i + n * j] = c;\n+\t\t}\n+\n+\t\tc = a_util->matching < 0 ?\n+\t\t\ta_util->diffsize * creation_weight : INT_MAX;\n+\t\tfor (j = b->nr; j < n; j++)\n+\t\t\tcost[i + n * j] = c;\n+\t}\n+\n+\tfor (j = 0; j < b->nr; j++) {\n+\t\tstruct patch_util *util = b->items[j].util;\n+\n+\t\tc = util->matching < 0 ?\n+\t\t\tutil->diffsize * creation_weight : INT_MAX;\n+\t\tfor (i = a->nr; i < n; i++)\n+\t\t\tcost[i + n * j] = c;\n+\t}\n+\n+\tfor (i = a->nr; i < n; i++)\n+\t\tfor (j = b->nr; j < n; j++)\n+\t\t\tcost[i + n * j] = 0;\n+\n+\tres = compute_assignment(n, n, cost, a2b, b2a);\n+\n+\tfor (i = 0; i < a->nr; i++)\n+\t\tif (a2b[i] >= 0 && a2b[i] < b->nr) {\n+\t\t\tstruct patch_util *a_util = a->items[i].util;\n+\t\t\tstruct patch_util *b_util = b->items[a2b[i]].util;\n+\n+\t\t\ta_util->matching = a2b[i];\n+\t\t\tb_util->matching = i;\n+\t\t}\n+\n+\tfree(cost);\n+\tfree(a2b);\n+\tfree(b2a);\n+\n+\treturn res;\n+}\n+\n+static const char *short_oid(struct patch_util *util)\n+{\n+\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n+}\n+\n+static void output(struct string_list *a, struct string_list *b)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < b->nr; i++) {\n+\t\tstruct patch_util *util = b->items[i].util, *prev;\n+\n+\t\tif (util->matching < 0)\n+\t\t\tprintf(\"-: -------- > %d: %s\\n\",\n+\t\t\t\t\ti + 1, short_oid(util));\n+\t\telse {\n+\t\t\tprev = a->items[util->matching].util;\n+\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n+\t\t\t       util->matching + 1, short_oid(prev),\n+\t\t\t       i + 1, short_oid(util));\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i < a->nr; i++) {\n+\t\tstruct patch_util *util = a->items[i].util;\n+\n+\t\tif (util->matching < 0)\n+\t\t\tprintf(\"%d: %s < -: --------\\n\",\n+\t\t\t       i + 1, short_oid(util));\n+\t}\n+}\n+\n int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n {\n \tdouble creation_weight = 0.6;\n@@ -30,9 +309,63 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n \t\t\t0, parse_creation_weight },\n \t\tOPT_END()\n \t};\n+\tint res = 0;\n+\tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n+\tstruct string_list branch1 = STRING_LIST_INIT_DUP;\n+\tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n \n \targc = parse_options(argc, argv, NULL, options,\n \t\t\tbuiltin_branch_diff_usage, 0);\n \n-\treturn 0;\n+\tif (argc == 2) {\n+\t\tif (!strstr(argv[0], \"..\"))\n+\t\t\twarning(_(\"no .. in range: '%s'\"), argv[0]);\n+\t\tstrbuf_addstr(&range1, argv[0]);\n+\n+\t\tif (!strstr(argv[1], \"..\"))\n+\t\t\twarning(_(\"no .. in range: '%s'\"), argv[1]);\n+\t\tstrbuf_addstr(&range2, argv[1]);\n+\t} else if (argc == 3) {\n+\t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n+\t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n+\t} else if (argc == 1) {\n+\t\tconst char *b = strstr(argv[0], \"...\"), *a = argv[0];\n+\t\tint a_len;\n+\n+\t\tif (!b)\n+\t\t\tdie(_(\"single arg format requires a symmetric range\"));\n+\n+\t\ta_len = (int)(b - a);\n+\t\tif (!a_len) {\n+\t\t\ta = \"HEAD\";\n+\t\t\ta_len = strlen(a);\n+\t\t}\n+\t\tb += 3;\n+\t\tif (!*b)\n+\t\t\tb = \"HEAD\";\n+\t\tstrbuf_addf(&range1, \"%s..%.*s\", b, a_len, a);\n+\t\tstrbuf_addf(&range2, \"%.*s..%s\", a_len, a, b);\n+\t} else {\n+\t\terror(_(\"need two commit ranges\"));\n+\t\tusage_with_options(builtin_branch_diff_usage, options);\n+\t}\n+\n+\tif (read_patches(range1.buf, &branch1))\n+\t\tres = error(_(\"could not parse log for '%s'\"), range1.buf);\n+\tif (!res && read_patches(range2.buf, &branch2))\n+\t\tres = error(_(\"could not parse log for '%s'\"), range2.buf);\n+\n+\tif (!res) {\n+\t\tfind_exact_matches(&branch1, &branch2);\n+\t\tres = get_correspondences(&branch1, &branch2, creation_weight);\n+\t\tif (!res)\n+\t\t\toutput(&branch1, &branch2);\n+\t}\n+\n+\tstrbuf_release(&range1);\n+\tstrbuf_release(&range2);\n+\tstring_list_clear(&branch1, 1);\n+\tstring_list_clear(&branch2, 1);\n+\n+\treturn !!res;\n }\n-- \n2.17.0.409.g71698f11835\n\n\n"},{"id":"346665","messageId":"313beeed3d100a3b5198bd0ce3084744288b05c2.1525448066.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 11/18] branch-diff: add tests","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:34:52Z","receivedAt":"2018-05-04T15:35:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Thomas Rast <tr@thomasrast.ch>\n\nThese are essentially lifted from https://github.com/trast/tbdiff, with\nlight touch-ups to account for the new command name.\n\nApart from renaming `tbdiff` to `branch-diff`, only one test case needed\nto be adjusted: 11 - 'changed message'.\n\nThe underlying reason it had to be adjusted is that diff generation is\nsometimes ambiguous. In this case, a comment line and an empty line are\nadded, but it is ambiguous whether they were added after the existing\nempty line, or whether an empty line and the comment line are added\n*before* the existing empty line. And apparently xdiff picks a different\noption here than Python's difflib.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/.gitattributes       |   1 +\n t/t7910-branch-diff.sh | 144 ++++++++++\n t/t7910/history.export | 604 +++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 749 insertions(+)\n create mode 100755 t/t7910-branch-diff.sh\n create mode 100644 t/t7910/history.export\n\ndiff --git a/t/.gitattributes b/t/.gitattributes\nindex 3bd959ae523..af15d5aeedd 100644\n--- a/t/.gitattributes\n+++ b/t/.gitattributes\n@@ -18,5 +18,6 @@ t[0-9][0-9][0-9][0-9]/* -whitespace\n /t5515/* eol=lf\n /t556x_common eol=lf\n /t7500/* eol=lf\n+/t7910/* eol=lf\n /t8005/*.txt eol=lf\n /t9*/*.dump eol=lf\ndiff --git a/t/t7910-branch-diff.sh b/t/t7910-branch-diff.sh\nnew file mode 100755\nindex 00000000000..a7fece88045\n--- /dev/null\n+++ b/t/t7910-branch-diff.sh\n@@ -0,0 +1,144 @@\n+#!/bin/sh\n+\n+test_description='branch-diff tests'\n+\n+. ./test-lib.sh\n+\n+# Note that because of git-branch-diff's heuristics, test_commit does more\n+# harm than good.  We need some real history.\n+\n+test_expect_success 'setup' '\n+\tgit fast-import < \"$TEST_DIRECTORY\"/t7910/history.export\n+'\n+\n+test_expect_success 'simple A..B A..C (unmodified)' '\n+\tgit branch-diff --no-color master..topic master..unmodified >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  35b9b25 s/5/A/\n+\t2:  fccce22 = 2:  de345ab s/4/A/\n+\t3:  147e64e = 3:  9af6654 s/11/B/\n+\t4:  a63e992 = 4:  2901f77 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'simple B...C (unmodified)' '\n+\tgit branch-diff --no-color topic...unmodified >actual &&\n+\t# same \"expected\" as above\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'simple A B C (unmodified)' '\n+\tgit branch-diff --no-color master topic unmodified >actual &&\n+\t# same \"expected\" as above\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'trivial reordering' '\n+\tgit branch-diff --no-color master topic reordered >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  aca177a s/5/A/\n+\t3:  147e64e = 2:  14ad629 s/11/B/\n+\t4:  a63e992 = 3:  ee58208 s/12/B/\n+\t2:  fccce22 = 4:  307b27a s/4/A/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'removed a commit' '\n+\tgit branch-diff --no-color master topic removed >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  7657159 s/5/A/\n+\t2:  fccce22 < -:  ------- s/4/A/\n+\t3:  147e64e = 2:  43d84d3 s/11/B/\n+\t4:  a63e992 = 3:  a740396 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'added a commit' '\n+\tgit branch-diff --no-color master topic added >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  2716022 s/5/A/\n+\t2:  fccce22 = 2:  b62accd s/4/A/\n+\t-:  ------- > 3:  df46cfa s/6/A/\n+\t3:  147e64e = 4:  3e64548 s/11/B/\n+\t4:  a63e992 = 5:  12b4063 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'new base, A B C' '\n+\tgit branch-diff --no-color master topic rebased >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  cc9c443 s/5/A/\n+\t2:  fccce22 = 2:  c5d9641 s/4/A/\n+\t3:  147e64e = 3:  28cc2b6 s/11/B/\n+\t4:  a63e992 = 4:  5628ab7 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'new base, B...C' '\n+\t# this syntax includes the commits from master!\n+\tgit branch-diff --no-color topic...rebased >actual &&\n+\tcat >expected <<-EOF &&\n+\t-:  ------- > 1:  a31b12e unrelated\n+\t1:  4de457d = 2:  cc9c443 s/5/A/\n+\t2:  fccce22 = 3:  c5d9641 s/4/A/\n+\t3:  147e64e = 4:  28cc2b6 s/11/B/\n+\t4:  a63e992 = 5:  5628ab7 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'changed commit' '\n+\tgit branch-diff --no-color topic...changed >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  a4b3333 s/5/A/\n+\t2:  fccce22 = 2:  f51d370 s/4/A/\n+\t3:  147e64e ! 3:  0559556 s/11/B/\n+\t    @@ -10,7 +10,7 @@\n+\t      9\n+\t      10\n+\t     -11\n+\t    -+B\n+\t    ++BB\n+\t      12\n+\t      13\n+\t      14\n+\t4:  a63e992 ! 4:  d966c5c s/12/B/\n+\t    @@ -8,7 +8,7 @@\n+\t     @@\n+\t      9\n+\t      10\n+\t    - B\n+\t    + BB\n+\t     -12\n+\t     +B\n+\t      13\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'changed message' '\n+\tgit branch-diff --no-color topic...changed-message >actual &&\n+\tsed s/Z/\\ /g >expected <<-EOF &&\n+\t1:  4de457d = 1:  f686024 s/5/A/\n+\t2:  fccce22 ! 2:  4ab067d s/4/A/\n+\t    @@ -2,6 +2,8 @@\n+\t    Z\n+\t    Z    s/4/A/\n+\t    Z\n+\t    +    Also a silly comment here!\n+\t    +\n+\t    Zdiff --git a/file b/file\n+\t    Z--- a/file\n+\t    Z+++ b/file\n+\t3:  147e64e = 3:  b9cb956 s/11/B/\n+\t4:  a63e992 = 4:  8add5f1 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_done\ndiff --git a/t/t7910/history.export b/t/t7910/history.export\nnew file mode 100644\nindex 00000000000..b8ffff0940d\n--- /dev/null\n+++ b/t/t7910/history.export\n@@ -0,0 +1,604 @@\n+blob\n+mark :1\n+data 51\n+1\n+2\n+3\n+4\n+5\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+reset refs/heads/removed\n+commit refs/heads/removed\n+mark :2\n+author Thomas Rast <trast@inf.ethz.ch> 1374424921 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374484724 +0200\n+data 8\n+initial\n+M 100644 :1 file\n+\n+blob\n+mark :3\n+data 51\n+1\n+2\n+3\n+4\n+A\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :4\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+blob\n+mark :5\n+data 51\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :6\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+data 7\n+s/4/A/\n+from :4\n+M 100644 :5 file\n+\n+blob\n+mark :7\n+data 50\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :8\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+data 8\n+s/11/B/\n+from :6\n+M 100644 :7 file\n+\n+blob\n+mark :9\n+data 49\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :10\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+data 8\n+s/12/B/\n+from :8\n+M 100644 :9 file\n+\n+blob\n+mark :11\n+data 10\n+unrelated\n+\n+commit refs/heads/master\n+mark :12\n+author Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200\n+data 10\n+unrelated\n+from :2\n+M 100644 :11 otherfile\n+\n+commit refs/heads/rebased\n+mark :13\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485137 +0200\n+data 7\n+s/5/A/\n+from :12\n+M 100644 :3 file\n+\n+commit refs/heads/rebased\n+mark :14\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n+data 7\n+s/4/A/\n+from :13\n+M 100644 :5 file\n+\n+commit refs/heads/rebased\n+mark :15\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n+data 8\n+s/11/B/\n+from :14\n+M 100644 :7 file\n+\n+commit refs/heads/rebased\n+mark :16\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n+data 8\n+s/12/B/\n+from :15\n+M 100644 :9 file\n+\n+commit refs/heads/added\n+mark :17\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/added\n+mark :18\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 7\n+s/4/A/\n+from :17\n+M 100644 :5 file\n+\n+blob\n+mark :19\n+data 51\n+1\n+2\n+3\n+A\n+A\n+A\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/added\n+mark :20\n+author Thomas Rast <trast@inf.ethz.ch> 1374485186 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 7\n+s/6/A/\n+from :18\n+M 100644 :19 file\n+\n+blob\n+mark :21\n+data 50\n+1\n+2\n+3\n+A\n+A\n+A\n+7\n+8\n+9\n+10\n+B\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/added\n+mark :22\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 8\n+s/11/B/\n+from :20\n+M 100644 :21 file\n+\n+blob\n+mark :23\n+data 49\n+1\n+2\n+3\n+A\n+A\n+A\n+7\n+8\n+9\n+10\n+B\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/added\n+mark :24\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 8\n+s/12/B/\n+from :22\n+M 100644 :23 file\n+\n+commit refs/heads/reordered\n+mark :25\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+blob\n+mark :26\n+data 50\n+1\n+2\n+3\n+4\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/reordered\n+mark :27\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 8\n+s/11/B/\n+from :25\n+M 100644 :26 file\n+\n+blob\n+mark :28\n+data 49\n+1\n+2\n+3\n+4\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/reordered\n+mark :29\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 8\n+s/12/B/\n+from :27\n+M 100644 :28 file\n+\n+commit refs/heads/reordered\n+mark :30\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 7\n+s/4/A/\n+from :29\n+M 100644 :9 file\n+\n+commit refs/heads/changed\n+mark :31\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/changed\n+mark :32\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 7\n+s/4/A/\n+from :31\n+M 100644 :5 file\n+\n+blob\n+mark :33\n+data 51\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+BB\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/changed\n+mark :34\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 8\n+s/11/B/\n+from :32\n+M 100644 :33 file\n+\n+blob\n+mark :35\n+data 50\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+BB\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/changed\n+mark :36\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 8\n+s/12/B/\n+from :34\n+M 100644 :35 file\n+\n+commit refs/heads/changed-message\n+mark :37\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/changed-message\n+mark :38\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200\n+data 35\n+s/4/A/\n+\n+Also a silly comment here!\n+from :37\n+M 100644 :5 file\n+\n+commit refs/heads/changed-message\n+mark :39\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200\n+data 8\n+s/11/B/\n+from :38\n+M 100644 :7 file\n+\n+commit refs/heads/changed-message\n+mark :40\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200\n+data 8\n+s/12/B/\n+from :39\n+M 100644 :9 file\n+\n+commit refs/heads/unmodified\n+mark :41\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/unmodified\n+mark :42\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200\n+data 7\n+s/4/A/\n+from :41\n+M 100644 :5 file\n+\n+commit refs/heads/unmodified\n+mark :43\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200\n+data 8\n+s/11/B/\n+from :42\n+M 100644 :7 file\n+\n+commit refs/heads/unmodified\n+mark :44\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200\n+data 8\n+s/12/B/\n+from :43\n+M 100644 :9 file\n+\n+commit refs/heads/removed\n+mark :45\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/removed\n+mark :46\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n+data 8\n+s/11/B/\n+from :45\n+M 100644 :26 file\n+\n+commit refs/heads/removed\n+mark :47\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n+data 8\n+s/12/B/\n+from :46\n+M 100644 :28 file\n+\n+reset refs/heads/removed\n+from :47\n+\n-- \n2.17.0.409.g71698f11835\n\n\n"},{"id":"346666","messageId":"c856c460a47dbe885bbb82babc6be6848d31ed32.1525448066.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 07/18] branch-diff: indent the diffs just like tbdiff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:34:45Z","receivedAt":"2018-05-04T15:35:08Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The main information in the branch-diff view comes from the list of\nmatching and non-matching commits, the diffs are additional information.\nIndenting them helps with the reading flow.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 9 +++++++++\n 1 file changed, 9 insertions(+)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex e2337b905b1..4fc9fd74531 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -275,6 +275,11 @@ static const char *short_oid(struct patch_util *util)\n \treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n }\n \n+static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)\n+{\n+\treturn data;\n+}\n+\n static struct diff_filespec *get_filespec(const char *name, const char *p)\n {\n \tstruct diff_filespec *spec = alloc_filespec(name);\n@@ -353,6 +358,7 @@ static void output(struct string_list *a, struct string_list *b,\n int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n {\n \tstruct diff_options diffopt = { NULL };\n+\tstruct strbuf four_spaces = STRBUF_INIT;\n \tdouble creation_weight = 0.6;\n \tstruct option options[] = {\n \t\tOPT_SET_INT(0, \"no-patches\", &diffopt.output_format,\n@@ -371,6 +377,9 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n \n \tdiff_setup(&diffopt);\n \tdiffopt.output_format = DIFF_FORMAT_PATCH;\n+\tdiffopt.output_prefix = output_prefix_cb;\n+\tstrbuf_addstr(&four_spaces, \"    \");\n+\tdiffopt.output_prefix_data = &four_spaces;\n \n \targc = parse_options(argc, argv, NULL, options,\n \t\t\tbuiltin_branch_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n-- \n2.17.0.409.g71698f11835\n\n\n"},{"id":"346667","messageId":"0e4c8279e467e2e75864bcce8ec90cf4f81c2c34.1525448066.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 09/18] branch-diff: adjust the output of the commit pairs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:34:49Z","receivedAt":"2018-05-04T15:35:11Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This change brings branch-diff yet another step closer to feature parity\nwith tbdiff: it now shows the oneline, too, and indicates with `=` when\nthe commits have identical diffs.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 67 +++++++++++++++++++++++++++++++++++++------\n 1 file changed, 58 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex ed520d6229d..5b187890bdf 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -9,6 +9,8 @@\n #include \"hungarian.h\"\n #include \"diff.h\"\n #include \"diffcore.h\"\n+#include \"commit.h\"\n+#include \"pretty.h\"\n \n static const char * const builtin_branch_diff_usage[] = {\n N_(\"git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n@@ -270,9 +272,57 @@ static int get_correspondences(struct string_list *a, struct string_list *b,\n \treturn res;\n }\n \n-static const char *short_oid(struct patch_util *util)\n+static void output_pair_header(struct strbuf *buf,\n+\t\t\t       int i, struct patch_util *a_util,\n+\t\t\t       int j, struct patch_util *b_util)\n {\n-\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n+\tstatic char *dashes;\n+\tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n+\tstruct commit *commit;\n+\n+\tif (!dashes) {\n+\t\tchar *p;\n+\n+\t\tdashes = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV));\n+\t\tfor (p = dashes; *p; p++)\n+\t\t\t*p = '-';\n+\t}\n+\n+\tstrbuf_reset(buf);\n+\tif (i < 0)\n+\t\tstrbuf_addf(buf, \"-:  %s \", dashes);\n+\telse\n+\t\tstrbuf_addf(buf, \"%d:  %s \", i + 1,\n+\t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n+\n+\tif (i < 0)\n+\t\tstrbuf_addch(buf, '>');\n+\telse if (j < 0)\n+\t\tstrbuf_addch(buf, '<');\n+\telse if (strcmp(a_util->patch, b_util->patch))\n+\t\tstrbuf_addch(buf, '!');\n+\telse\n+\t\tstrbuf_addch(buf, '=');\n+\n+\tif (j < 0)\n+\t\tstrbuf_addf(buf, \" -:  %s\", dashes);\n+\telse\n+\t\tstrbuf_addf(buf, \" %d:  %s\", j + 1,\n+\t\t\t    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));\n+\n+\tcommit = lookup_commit_reference(oid);\n+\tif (commit) {\n+\t\tconst char *commit_buffer = get_commit_buffer(commit, NULL);\n+\t\tconst char *subject;\n+\n+\t\tfind_commit_subject(commit_buffer, &subject);\n+\t\tstrbuf_addch(buf, ' ');\n+\t\tformat_subject(buf, subject, \" \");\n+\t\tunuse_commit_buffer(commit, commit_buffer);\n+\t}\n+\tstrbuf_addch(buf, '\\n');\n+\n+\tfwrite(buf->buf, buf->len, 1, stdout);\n }\n \n static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)\n@@ -306,6 +356,7 @@ static void patch_diff(const char *a, const char *b,\n static void output(struct string_list *a, struct string_list *b,\n \t\t   struct diff_options *diffopt)\n {\n+\tstruct strbuf buf = STRBUF_INIT;\n \tint i = 0, j = 0;\n \n \t/*\n@@ -327,25 +378,22 @@ static void output(struct string_list *a, struct string_list *b,\n \n \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n \t\tif (i < a->nr && a_util->matching < 0) {\n-\t\t\tprintf(\"%d: %s < -: --------\\n\",\n-\t\t\t       i + 1, short_oid(a_util));\n+\t\t\toutput_pair_header(&buf, i, a_util, -1, NULL);\n \t\t\ti++;\n \t\t\tcontinue;\n \t\t}\n \n \t\t/* Show unmatched RHS commits. */\n \t\twhile (j < b->nr && b_util->matching < 0) {\n-\t\t\tprintf(\"-: -------- > %d: %s\\n\",\n-\t\t\t       j + 1, short_oid(b_util));\n+\t\t\toutput_pair_header(&buf, -1, NULL, j, b_util);\n \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n \n \t\t/* Show matching LHS/RHS pair. */\n \t\tif (j < b->nr) {\n \t\t\ta_util = a->items[b_util->matching].util;\n-\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n-\t\t\t       b_util->matching + 1, short_oid(a_util),\n-\t\t\t       j + 1, short_oid(b_util));\n+\t\t\toutput_pair_header(&buf,\n+\t\t\t\t\t   b_util->matching, a_util, j, b_util);\n \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n \t\t\t\t\t   b->items[j].string, diffopt);\n@@ -353,6 +401,7 @@ static void output(struct string_list *a, struct string_list *b,\n \t\t\tj++;\n \t\t}\n \t}\n+\tstrbuf_release(&buf);\n }\n \n int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n-- \n2.17.0.409.g71698f11835\n\n\n"},{"id":"346668","messageId":"1ebbe359547689d32aa27564929d733a26bb8054.1525448066.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 13/18] color: provide inverted colors, too","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:34:58Z","receivedAt":"2018-05-04T15:35:12Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"For every regular color, there exists the inverted equivalent where\nbackground and foreground colors are exchanged.\n\nWe will use this in the next commit to allow inverting *just* the +/-\nsigns in a diff.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n color.h | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/color.h b/color.h\nindex cd0bcedd084..f0984b09583 100644\n--- a/color.h\n+++ b/color.h\n@@ -36,6 +36,12 @@ struct strbuf;\n #define GIT_COLOR_BOLD_BLUE\t\"\\033[1;34m\"\n #define GIT_COLOR_BOLD_MAGENTA\t\"\\033[1;35m\"\n #define GIT_COLOR_BOLD_CYAN\t\"\\033[1;36m\"\n+#define GIT_COLOR_INV_RED\t\"\\033[7;31m\"\n+#define GIT_COLOR_INV_GREEN\t\"\\033[7;32m\"\n+#define GIT_COLOR_INV_YELLOW\t\"\\033[7;33m\"\n+#define GIT_COLOR_INV_BLUE\t\"\\033[7;34m\"\n+#define GIT_COLOR_INV_MAGENTA\t\"\\033[7;35m\"\n+#define GIT_COLOR_INV_CYAN\t\"\\033[7;36m\"\n #define GIT_COLOR_BG_RED\t\"\\033[41m\"\n #define GIT_COLOR_BG_GREEN\t\"\\033[42m\"\n #define GIT_COLOR_BG_YELLOW\t\"\\033[43m\"\n-- \n2.17.0.409.g71698f11835\n\n\n"},{"id":"346669","messageId":"b9be01705d6cfa1260dff33c1f2466526511dd18.1525448066.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 15/18] branch-diff: offer to dual-color the diffs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:35:06Z","receivedAt":"2018-05-04T15:35:20Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"When showing what changed between old and new commits, we show a diff of\nthe patches. This diff is a diff between diffs, therefore there are\nnested +/- signs, and it can be relatively hard to understand what is\ngoing on.\n\nWith the --dual-color option, the preimage and the postimage are colored\nlike the diffs they are, and the *outer* +/- sign is inverted for\nclarity.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 8 ++++++++\n 1 file changed, 8 insertions(+)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex 04efd30f0f6..8a16352e3a1 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -435,8 +435,11 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n {\n \tstruct diff_options diffopt = { NULL };\n \tstruct strbuf four_spaces = STRBUF_INIT;\n+\tint dual_color = 0;\n \tdouble creation_weight = 0.6;\n \tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"dual-color\", &dual_color,\n+\t\t\t    N_(\"color both diff and diff-between-diffs\")),\n \t\tOPT_SET_INT(0, \"no-patches\", &diffopt.output_format,\n \t\t\t    N_(\"short format (no diffs)\"),\n \t\t\t    DIFF_FORMAT_NO_OUTPUT),\n@@ -472,6 +475,11 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n \targc = j;\n \tdiff_setup_done(&diffopt);\n \n+\tif (dual_color) {\n+\t\tdiffopt.use_color = 1;\n+\t\tdiffopt.flags.dual_color_diffed_diffs = 1;\n+\t}\n+\n \tif (argc == 2) {\n \t\tif (!strstr(argv[0], \"..\"))\n \t\t\twarning(_(\"no .. in range: '%s'\"), argv[0]);\n-- \n2.17.0.409.g71698f11835\n\n\n"},{"id":"346670","messageId":"950c753770101699424c580d51c2a92b421ca18b.1525448066.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 17/18] branch-diff: add a man page","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:35:09Z","receivedAt":"2018-05-04T15:35:21Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is a heavily butchered version of the README written by Thomas\nRast and Thomas Gummerer, lifted from https://github.com/trast/tbdiff.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-branch-diff.txt | 239 ++++++++++++++++++++++++++++++\n 1 file changed, 239 insertions(+)\n create mode 100644 Documentation/git-branch-diff.txt\n\ndiff --git a/Documentation/git-branch-diff.txt b/Documentation/git-branch-diff.txt\nnew file mode 100644\nindex 00000000000..f9e23eaf721\n--- /dev/null\n+++ b/Documentation/git-branch-diff.txt\n@@ -0,0 +1,239 @@\n+git-branch-diff(1)\n+==================\n+\n+NAME\n+----\n+git-branch-diff - Compare two versions of a branch\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git branch-diff' [--color=[<when>]] [--no-color] [<diff-options>]\n+\t[--dual-color] [--no-patches] [--creation-weight=<weight>]\n+\t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n+\n+DESCRIPTION\n+-----------\n+\n+This command shows the differences between two versions of a patch\n+series, or more generally, two commit ranges (ignoring merges).\n+\n+To that end, it first finds pairs of commits from both commit ranges\n+that correspond with each other. Two commits are said to correspond when\n+the diff between their patches (i.e. the author information, the commit\n+message and the commit diff) is reasonably small compared to the\n+patches' size. See ``Algorithm` below for details.\n+\n+Finally, the list of matching commits is shown in the order of the\n+second commit range, with unmatched commits being inserted just after\n+all of their ancestors have been shown.\n+\n+\n+OPTIONS\n+-------\n+--no-patches::\n+\tSuppress the diffs between commit pairs that were deemed to\n+\tcorrespond; only show the pairings.\n+\n+--dual-color::\n+\tWhen the commit diffs differ, recreate the original diffs'\n+\tcoloring, and add outer -/+ diff markers with the *background*\n+\tbeing red/green to make it easier to see e.g. when there was a\n+\tchange in what exact lines were added.\n+\n+--creation-weight=<factor>::\n+\tSet the creation/deletion cost fudge factor to `<factor>`.\n+\tDefaults to 0.6. Try a larger value if `git branch-diff`\n+\terroneously considers a large change a total rewrite (deletion\n+\tof one commit and addition of another), and a smaller one in\n+\tthe reverse case. See the ``Algorithm`` section below for an\n+\texplanation why this is needed.\n+\n+<range1> <range2>::\n+\tCompare the commits specified by the two ranges, where\n+\t`<range1>` is considered an older version of `<range2>`.\n+\n+<rev1>...<rev2>::\n+\tEquivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.\n+\n+<base> <rev1> <rev2>::\n+\tEquivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.\n+\tNote that `<base>` does not need to be the exact branch point\n+\tof the branches. Example: after rebasing a branch `my-topic`,\n+\t`git branch-diff my-topic@{u} my-topic@{1} my-topic` would\n+\tshow the differences introduced by the rebase.\n+\n+`git branch-diff` also accepts the regular diff options (see\n+linkgit:git-diff[1]), most notably the `--color=[<when>]` and\n+`--no-color` options. These options are used when generating the \"diff\n+between patches\", i.e. to compare the author, commit message and diff of\n+corresponding old/new commits. There is currently no means to tweak the\n+diff options passed to `git log` when generating those patches.\n+\n+\n+CONFIGURATION\n+-------------\n+This command uses the `diff.color.*` and `pager.branch-diff` settings\n+(the latter is on by default).\n+See linkgit:git-config[1].\n+\n+\n+Examples\n+--------\n+\n+When a rebase required merge conflicts to be resolved, compare the changes\n+introduced by the rebase directly afterwards using:\n+\n+------------\n+$ git branch-diff @{u} @{1} @\n+------------\n+\n+\n+A typical output of `git branch-diff` would look like this:\n+\n+------------\n+-:  ------- > 1:  0ddba11 Prepare for the inevitable!\n+1:  c0debee = 2:  cab005e Add a helpful message at the start\n+2:  f00dbal ! 3:  decafe1 Describe a bug\n+    @@ -1,3 +1,3 @@\n+     Author: A U Thor <author@example.com>\n+\n+    -TODO: Describe a bug\n+    +Describe a bug\n+    @@ -324,5 +324,6\n+      This is expected.\n+\n+    -+What is unexpected is that it will also crash.\n+    ++Unexpectedly, it also crashes. This is a bug, and the jury is\n+    ++still out there how to fix it best. See ticket #314 for details.\n+\n+      Contact\n+3:  bedead < -:  ------- TO-UNDO\n+------------\n+\n+In this example, there are 3 old and 3 new commits, where the developer\n+removed the 3rd, added a new one before the first two, and modified the\n+commit message of the 2nd commit as well its diff.\n+\n+When the output goes to a terminal, it is color-coded by default, just\n+like regular `git diff`'s output. In addition, the first line (adding a\n+commit) is green, the last line (deleting a commit) is red, the second\n+line (with a perfect match) is yellow like the commit header of `git\n+show`'s output, and the third line colors the old commit red, the new\n+one green and the rest like `git show`'s commit header.\n+\n+The color-coded diff is actually a bit hard to read, though, as it\n+colors the entire lines red or green. The line that added \"What is\n+unexpected\" in the old commit, for example, is completely red, even if\n+the intent of the old commit was to add something.\n+\n+To help with that, use the `--dual-color` mode. In this mode, the diff\n+of diffs will retain the original diff colors, and prefix the lines with\n+-/+ markers that have their *background* red or green, to make it more\n+obvious that they describe how the diff itself changed.\n+\n+\n+Algorithm\n+---------\n+\n+The general idea is this: we generate a cost matrix between the commits\n+in both commit ranges, then solve the least-cost assignment.\n+\n+To avoid false positives (e.g. when a patch has been removed, and an\n+unrelated patch has been added between two iterations of the same patch\n+series), the cost matrix is extended to allow for that, by adding\n+fixed-cost entries for wholesale deletes/adds.\n+\n+Example: Let commits `1--2` be the first iteration of a patch series and\n+`A--C` the second iteration. Let's assume that `A` is a cherry-pick of\n+`2,` and `C` is a cherry-pick of `1` but with a small modification (say,\n+a fixed typo). Visualize the commits as a bipartite graph:\n+\n+------------\n+    1            A\n+\n+    2            B\n+\n+\t\t C\n+------------\n+\n+We are looking for a \"best\" explanation of the new series in terms of\n+the old one. We can represent an \"explanation\" as an edge in the graph:\n+\n+\n+------------\n+    1            A\n+\t       /\n+    2 --------'  B\n+\n+\t\t C\n+------------\n+\n+This explanation comes for \"free\" because there was no change. Similarly\n+`C` could be explained using `1`, but that comes at some cost c>0\n+because of the modification:\n+\n+------------\n+    1 ----.      A\n+\t  |    /\n+    2 ----+---'  B\n+\t  |\n+\t  `----- C\n+\t  c>0\n+------------\n+\n+In mathematical terms, what we are looking for is some sort of a minimum\n+cost bipartite matching; `1` is matched to `C` at some cost, etc. The\n+underlying graph is in fact a complete bipartite graph; the cost we\n+associate with every edge is the size of the diff between the two\n+commits' patches. To explain also new commits, we introduce dummy nodes\n+on both sides:\n+\n+------------\n+    1 ----.      A\n+\t  |    /\n+    2 ----+---'  B\n+\t  |\n+    o     `----- C\n+\t  c>0\n+    o            o\n+\n+    o            o\n+------------\n+\n+The cost of an edge `o--C` is the size of `C`'s diff, modified by a\n+fudge factor that should be smaller than 1.0. The cost of an edge `o--o`\n+is free. The fudge factor is necessary because even if `1` and `C` have\n+nothing in common, they may still share a few empty lines and such,\n+possibly making the assignment `1--C`, `o--o` slightly cheaper than\n+`1--o`, `o--C` even if `1` and `C` have nothing in common. With the\n+fudge factor we require a much larger common part to consider patches as\n+corresponding.\n+\n+The overall time needed to compute this algorithm is the time needed to\n+compute n+m commit diffs and then n*m diffs of patches, plus the time\n+needed to compute the least-cost assigment between n and m diffs. Git\n+uses an implementation of the Jonker-Volgenant algorithm to solve the\n+assignment problem, which has cubic runtime complexity. The matching\n+found in this case will look like this:\n+\n+------------\n+    1 ----.      A\n+\t  |    /\n+    2 ----+---'  B\n+       .--+-----'\n+    o -'  `----- C\n+\t  c>0\n+    o ---------- o\n+\n+    o ---------- o\n+------------\n+\n+\n+SEE ALSO\n+--------\n+linkgit:git-log[1]\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\n-- \n2.17.0.409.g71698f11835\n\n\n"},{"id":"346671","messageId":"ae0ea5dfca59a825fb775dc916850c6c2299c5f7.1525448066.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 14/18] diff: add an internal option to dual-color diffs of diffs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:34:59Z","receivedAt":"2018-05-04T15:35:24Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"When diffing diffs, it can be quite daunting to figure out what the heck\nis going on, as there are nested +/- signs.\n\nLet's make this easier by adding a flag in diff_options that allows\ncolor-coding the outer diff sign with inverted colors, so that the\npreimage and postimage is colored like the diff it is.\n\nOf course, this really only makes sense when the preimage and postimage\n*are* diffs. So let's not expose this flag via a command-line option for\nnow.\n\nThis is a feature that was invented by git-tbdiff, and it will be used\nin `branch-diff` in the next commit.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n diff.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++---------\n diff.h |  5 ++++-\n 2 files changed, 59 insertions(+), 11 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex f1bda0db3f5..98a41e88620 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -67,6 +67,8 @@ static char diff_colors[][COLOR_MAXLEN] = {\n \tGIT_COLOR_BOLD_YELLOW,\t/* NEW_MOVED ALTERNATIVE */\n \tGIT_COLOR_FAINT,\t/* NEW_MOVED_DIM */\n \tGIT_COLOR_FAINT_ITALIC,\t/* NEW_MOVED_ALTERNATIVE_DIM */\n+\tGIT_COLOR_INV_RED,\t/* OLD_INV */\n+\tGIT_COLOR_INV_GREEN,\t/* NEW_INV */\n };\n \n static NORETURN void die_want_option(const char *option_name)\n@@ -108,6 +110,10 @@ static int parse_diff_color_slot(const char *var)\n \t\treturn DIFF_FILE_NEW_MOVED_DIM;\n \tif (!strcasecmp(var, \"newmovedalternativedimmed\"))\n \t\treturn DIFF_FILE_NEW_MOVED_ALT_DIM;\n+\tif (!strcasecmp(var, \"oldinv\"))\n+\t\treturn DIFF_FILE_OLD_INV;\n+\tif (!strcasecmp(var, \"newinv\"))\n+\t\treturn DIFF_FILE_NEW_INV;\n \treturn -1;\n }\n \n@@ -577,7 +583,10 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res\n \tint nofirst;\n \tFILE *file = o->file;\n \n-\tfputs(diff_line_prefix(o), file);\n+\tif (first)\n+\t\tfputs(diff_line_prefix(o), file);\n+\telse if (!len)\n+\t\treturn;\n \n \tif (len == 0) {\n \t\thas_trailing_newline = (first == '\\n');\n@@ -596,7 +605,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res\n \n \tif (len || !nofirst) {\n \t\tfputs(set, file);\n-\t\tif (!nofirst)\n+\t\tif (first && !nofirst)\n \t\t\tfputc(first, file);\n \t\tfwrite(line, len, 1, file);\n \t\tfputs(reset, file);\n@@ -970,7 +979,8 @@ static void dim_moved_lines(struct diff_options *o)\n \n static void emit_line_ws_markup(struct diff_options *o,\n \t\t\t\tconst char *set, const char *reset,\n-\t\t\t\tconst char *line, int len, char sign,\n+\t\t\t\tconst char *line, int len,\n+\t\t\t\tconst char *set_sign, char sign,\n \t\t\t\tunsigned ws_rule, int blank_at_eof)\n {\n \tconst char *ws = NULL;\n@@ -981,14 +991,18 @@ static void emit_line_ws_markup(struct diff_options *o,\n \t\t\tws = NULL;\n \t}\n \n-\tif (!ws)\n+\tif (!ws && set_sign == set)\n \t\temit_line_0(o, set, reset, sign, line, len);\n-\telse if (blank_at_eof)\n+\telse if (!ws) {\n+\t\t/* Emit just the prefix, then the rest. */\n+\t\temit_line_0(o, set_sign, reset, sign, \"\", 0);\n+\t\temit_line_0(o, set, reset, 0, line, len);\n+\t} else if (blank_at_eof)\n \t\t/* Blank line at EOF - paint '+' as well */\n \t\temit_line_0(o, ws, reset, sign, line, len);\n \telse {\n \t\t/* Emit just the prefix, then the rest. */\n-\t\temit_line_0(o, set, reset, sign, \"\", 0);\n+\t\temit_line_0(o, set_sign, reset, sign, \"\", 0);\n \t\tws_check_emit(line, len, ws_rule,\n \t\t\t      o->file, set, reset, ws);\n \t}\n@@ -998,7 +1012,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\t\t\t struct emitted_diff_symbol *eds)\n {\n \tstatic const char *nneof = \" No newline at end of file\\n\";\n-\tconst char *context, *reset, *set, *meta, *fraginfo;\n+\tconst char *context, *reset, *set, *set_sign, *meta, *fraginfo;\n \tstruct strbuf sb = STRBUF_INIT;\n \n \tenum diff_symbol s = eds->s;\n@@ -1038,7 +1052,16 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \tcase DIFF_SYMBOL_CONTEXT:\n \t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n-\t\temit_line_ws_markup(o, set, reset, line, len, ' ',\n+\t\tset_sign = set;\n+\t\tif (o->flags.dual_color_diffed_diffs) {\n+\t\t\tchar c = !len ? 0 : line[0];\n+\n+\t\t\tif (c == '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n+\t\t\telse if (c == '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n+\t\t}\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',\n \t\t\t\t    flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);\n \t\tbreak;\n \tcase DIFF_SYMBOL_PLUS:\n@@ -1065,7 +1088,18 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n \t\t}\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n-\t\temit_line_ws_markup(o, set, reset, line, len, '+',\n+\t\tif (!o->flags.dual_color_diffed_diffs)\n+\t\t\tset_sign = set;\n+\t\telse {\n+\t\t\tchar c = !len ? 0 : line[0];\n+\n+\t\t\tset_sign = diff_get_color_opt(o, DIFF_FILE_NEW_INV);\n+\t\t\tif (c == '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n+\t\t\telse if (c != '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t}\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK,\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);\n \t\tbreak;\n@@ -1093,7 +1127,18 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n \t\t}\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n-\t\temit_line_ws_markup(o, set, reset, line, len, '-',\n+\t\tif (!o->flags.dual_color_diffed_diffs)\n+\t\t\tset_sign = set;\n+\t\telse {\n+\t\t\tchar c = !len ? 0 : line[0];\n+\n+\t\t\tset_sign = diff_get_color_opt(o, DIFF_FILE_OLD_INV);\n+\t\t\tif (c == '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n+\t\t\telse if (c != '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t}\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '-',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);\n \t\tbreak;\n \tcase DIFF_SYMBOL_WORDS_PORCELAIN:\ndiff --git a/diff.h b/diff.h\nindex 0dd6a71af60..c3e5d27967c 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -95,6 +95,7 @@ struct diff_flags {\n \tunsigned default_follow_renames:1;\n \tunsigned stat_with_summary:1;\n \tunsigned suppress_diff_headers:1;\n+\tunsigned dual_color_diffed_diffs:1;\n };\n \n static inline void diff_flags_or(struct diff_flags *a,\n@@ -242,7 +243,9 @@ enum color_diff {\n \tDIFF_FILE_NEW_MOVED = 13,\n \tDIFF_FILE_NEW_MOVED_ALT = 14,\n \tDIFF_FILE_NEW_MOVED_DIM = 15,\n-\tDIFF_FILE_NEW_MOVED_ALT_DIM = 16\n+\tDIFF_FILE_NEW_MOVED_ALT_DIM = 16,\n+\tDIFF_FILE_OLD_INV = 17,\n+\tDIFF_FILE_NEW_INV = 18\n };\n const char *diff_get_color(int diff_use_color, enum color_diff ix);\n #define diff_get_color_opt(o, ix) \\\n-- \n2.17.0.409.g71698f11835\n\n\n"},{"id":"346672","messageId":"71698f11835311c103aae565a2a761d10f4676b9.1525448066.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 18/18] completion: support branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:35:11Z","receivedAt":"2018-05-04T15:35:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Tab completion of `branch-diff` is very convenient, especially given\nthat the revision arguments that need to be passed to `git branch-diff`\nare typically more complex than, say, your grandfather's `git log`\narguments.\n\nWithout this patch, we would only complete the `branch-diff` part but\nnot the options and other arguments.\n\nThis of itself may already be slightly disruptive for well-trained\nfingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to\n`git branch origin/master`, as we now no longer automatically append a\nspace after completing `git branch`: this is now ambiguous.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n contrib/completion/git-completion.bash | 18 ++++++++++++++++++\n 1 file changed, 18 insertions(+)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 01dd9ff07a2..45addd525ac 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1496,6 +1496,24 @@ _git_format_patch ()\n \t__git_complete_revlist\n }\n \n+__git_branch_diff_options=\"\n+\t--no-patches --creation-weight= --dual-color\n+\"\n+\n+_git_branch_diff ()\n+{\n+\tcase \"$cur\" in\n+\t--*)\n+\t\t__gitcomp \"\n+\t\t\t$__git_branch_diff_options\n+\t\t\t$__git_diff_common_options\n+\t\t\t\"\n+\t\treturn\n+\t\t;;\n+\tesac\n+\t__git_complete_revlist\n+}\n+\n _git_fsck ()\n {\n \tcase \"$cur\" in\n-- \n2.17.0.409.g71698f11835\n"},{"id":"346673","messageId":"3032e2709b858c1c08e7ef47a0fd6deee7f0d010.1525448066.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 04/18] branch-diff: improve the order of the shown commits","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:34:39Z","receivedAt":"2018-05-04T15:35:36Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch lets branch-diff use the same order as tbdiff.\n\nThe idea is simple: for left-to-right readers, it is natural to assume\nthat the branch-diff is performed between an older vs a newer version of\nthe branch. As such, the user is probably more interested in the\nquestion \"where did this come from?\" rather than \"where did that one\ngo?\".\n\nTo that end, we list the commits in the order of the second commit range\n(\"the newer version\"), inserting the unmatched commits of the first\ncommit range as soon as all their predecessors have been shown.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 59 +++++++++++++++++++++++++++++--------------\n 1 file changed, 40 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex c462681067c..92302b1c339 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -31,7 +31,7 @@ struct patch_util {\n \tstruct hashmap_entry e;\n \tconst char *diff, *patch;\n \n-\tint i;\n+\tint i, shown;\n \tint diffsize;\n \tsize_t diff_offset;\n \t/* the index of the matching item in the other branch, or -1 */\n@@ -274,28 +274,49 @@ static const char *short_oid(struct patch_util *util)\n \n static void output(struct string_list *a, struct string_list *b)\n {\n-\tint i;\n-\n-\tfor (i = 0; i < b->nr; i++) {\n-\t\tstruct patch_util *util = b->items[i].util, *prev;\n+\tint i = 0, j = 0;\n+\n+\t/*\n+\t * We assume the user is really more interested in the second argument\n+\t * (\"newer\" version). To that end, we print the output in the order of\n+\t * the RHS (the `b` parameter). To put the LHS (the `a` parameter)\n+\t * commits that are no longer in the RHS into a good place, we place\n+\t * them once we have shown all of their predecessors in the LHS.\n+\t */\n+\n+\twhile (i < a->nr || j < b->nr) {\n+\t\tstruct patch_util *a_util, *b_util;\n+\t\ta_util = i < a->nr ? a->items[i].util : NULL;\n+\t\tb_util = j < b->nr ? b->items[j].util : NULL;\n+\n+\t\t/* Skip all the already-shown commits from the LHS. */\n+\t\twhile (i < a->nr && a_util->shown)\n+\t\t\ta_util = ++i < a->nr ? a->items[i].util : NULL;\n+\n+\t\t/* Show unmatched LHS commit whose predecessors were shown. */\n+\t\tif (i < a->nr && a_util->matching < 0) {\n+\t\t\tprintf(\"%d: %s < -: --------\\n\",\n+\t\t\t       i + 1, short_oid(a_util));\n+\t\t\ti++;\n+\t\t\tcontinue;\n+\t\t}\n \n-\t\tif (util->matching < 0)\n+\t\t/* Show unmatched RHS commits. */\n+\t\twhile (j < b->nr && b_util->matching < 0) {\n \t\t\tprintf(\"-: -------- > %d: %s\\n\",\n-\t\t\t\t\ti + 1, short_oid(util));\n-\t\telse {\n-\t\t\tprev = a->items[util->matching].util;\n-\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n-\t\t\t       util->matching + 1, short_oid(prev),\n-\t\t\t       i + 1, short_oid(util));\n+\t\t\t       j + 1, short_oid(b_util));\n+\t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n-\t}\n-\n-\tfor (i = 0; i < a->nr; i++) {\n-\t\tstruct patch_util *util = a->items[i].util;\n \n-\t\tif (util->matching < 0)\n-\t\t\tprintf(\"%d: %s < -: --------\\n\",\n-\t\t\t       i + 1, short_oid(util));\n+\t\t/* Show matching LHS/RHS pair. */\n+\t\tif (j < b->nr) {\n+\t\t\ta_util = a->items[b_util->matching].util;\n+\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n+\t\t\t       b_util->matching + 1, short_oid(a_util),\n+\t\t\t       j + 1, short_oid(b_util));\n+\t\t\ta_util->shown = 1;\n+\t\t\tj++;\n+\t\t}\n \t}\n }\n \n-- \n2.17.0.409.g71698f11835\n\n\n"},{"id":"346674","messageId":"ba4791918c78770005d552856d8669648d7004f1.1525448066.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 12/18] branch-diff: use color for the commit pairs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:34:53Z","receivedAt":"2018-05-04T15:35:39Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Arguably the most important part of branch-diff's output is the list of\ncommits in the two branches, together with their relationships.\n\nFor that reason, tbdiff introduced color-coding that is pretty\nintuitive, especially for unchanged patches (all dim yellow, like the\nfirst line in `git show`'s output) vs modified patches (old commit is\nred, new commit is green). Let's imitate that color scheme.\n\nWhile at it, also copy tbdiff's change of the fragment color to magenta.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch-diff.c | 49 +++++++++++++++++++++++++++++++------------\n 1 file changed, 36 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\nindex 89d75c93115..04efd30f0f6 100644\n--- a/builtin/branch-diff.c\n+++ b/builtin/branch-diff.c\n@@ -273,13 +273,19 @@ static int get_correspondences(struct string_list *a, struct string_list *b,\n \treturn res;\n }\n \n-static void output_pair_header(struct strbuf *buf,\n+static void output_pair_header(struct diff_options *diffopt, struct strbuf *buf,\n \t\t\t       int i, struct patch_util *a_util,\n \t\t\t       int j, struct patch_util *b_util)\n {\n \tstatic char *dashes;\n \tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n \tstruct commit *commit;\n+\tchar status;\n+\tconst char *color_reset = diff_get_color_opt(diffopt, DIFF_RESET);\n+\tconst char *color_old = diff_get_color_opt(diffopt, DIFF_FILE_OLD);\n+\tconst char *color_new = diff_get_color_opt(diffopt, DIFF_FILE_NEW);\n+\tconst char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT);\n+\tconst char *color;\n \n \tif (!dashes) {\n \t\tchar *p;\n@@ -289,21 +295,33 @@ static void output_pair_header(struct strbuf *buf,\n \t\t\t*p = '-';\n \t}\n \n+\tif (j < 0) {\n+\t\tcolor = color_old;\n+\t\tstatus = '<';\n+\t} else if (i < 0) {\n+\t\tcolor = color_new;\n+\t\tstatus = '>';\n+\t} else if (strcmp(a_util->patch, b_util->patch)) {\n+\t\tcolor = color_commit;\n+\t\tstatus = '!';\n+\t} else {\n+\t\tcolor = color_commit;\n+\t\tstatus = '=';\n+\t}\n+\n \tstrbuf_reset(buf);\n+\tstrbuf_addstr(buf, status == '!' ? color_old : color);\n \tif (i < 0)\n \t\tstrbuf_addf(buf, \"-:  %s \", dashes);\n \telse\n \t\tstrbuf_addf(buf, \"%d:  %s \", i + 1,\n \t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n \n-\tif (i < 0)\n-\t\tstrbuf_addch(buf, '>');\n-\telse if (j < 0)\n-\t\tstrbuf_addch(buf, '<');\n-\telse if (strcmp(a_util->patch, b_util->patch))\n-\t\tstrbuf_addch(buf, '!');\n-\telse\n-\t\tstrbuf_addch(buf, '=');\n+\tif (status == '!')\n+\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color);\n+\tstrbuf_addch(buf, status);\n+\tif (status == '!')\n+\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color_new);\n \n \tif (j < 0)\n \t\tstrbuf_addf(buf, \" -:  %s\", dashes);\n@@ -316,12 +334,15 @@ static void output_pair_header(struct strbuf *buf,\n \t\tconst char *commit_buffer = get_commit_buffer(commit, NULL);\n \t\tconst char *subject;\n \n+\t\tif (status == '!')\n+\t\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color);\n+\n \t\tfind_commit_subject(commit_buffer, &subject);\n \t\tstrbuf_addch(buf, ' ');\n \t\tformat_subject(buf, subject, \" \");\n \t\tunuse_commit_buffer(commit, commit_buffer);\n \t}\n-\tstrbuf_addch(buf, '\\n');\n+\tstrbuf_addf(buf, \"%s\\n\", color_reset);\n \n \tfwrite(buf->buf, buf->len, 1, stdout);\n }\n@@ -384,21 +405,21 @@ static void output(struct string_list *a, struct string_list *b,\n \n \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n \t\tif (i < a->nr && a_util->matching < 0) {\n-\t\t\toutput_pair_header(&buf, i, a_util, -1, NULL);\n+\t\t\toutput_pair_header(diffopt, &buf, i, a_util, -1, NULL);\n \t\t\ti++;\n \t\t\tcontinue;\n \t\t}\n \n \t\t/* Show unmatched RHS commits. */\n \t\twhile (j < b->nr && b_util->matching < 0) {\n-\t\t\toutput_pair_header(&buf, -1, NULL, j, b_util);\n+\t\t\toutput_pair_header(diffopt, &buf, -1, NULL, j, b_util);\n \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n \n \t\t/* Show matching LHS/RHS pair. */\n \t\tif (j < b->nr) {\n \t\t\ta_util = a->items[b_util->matching].util;\n-\t\t\toutput_pair_header(&buf,\n+\t\t\toutput_pair_header(diffopt, &buf,\n \t\t\t\t\t   b_util->matching, a_util, j, b_util);\n \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n@@ -430,6 +451,8 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n \tstruct string_list branch1 = STRING_LIST_INIT_DUP;\n \tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n \n+\tgit_diff_basic_config(\"diff.color.frag\", \"magenta\", NULL);\n+\n \tdiff_setup(&diffopt);\n \tdiffopt.output_format = DIFF_FORMAT_PATCH;\n \tdiffopt.flags.suppress_diff_headers = 1;\n-- \n2.17.0.409.g71698f11835\n\n\n"},{"id":"346675","messageId":"b99ab186c4f11239a10950b9902d9c87d0e60513.1525448066.git.johannes.schindelin@gmx.de","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 16/18] branch-diff --dual-color: work around bogus white-space warning","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-04T15:35:07Z","receivedAt":"2018-05-04T15:35:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"When displaying a diff of diffs, it is possible that there is an outer\n`+` before a context line. That happens when the context changed between\nold and new commit. When that context line starts with a tab (after the\nspace that marks it as context line), our diff machinery spits out a\nwhite-space error (space before tab), but in this case, that is\nincorrect.\n\nWork around this by detecting that situation and simply *not* printing\nthe space in that case.\n\nThis is slightly improper a fix because it is conceivable that an\noutput_prefix might be configured with *just* the right length to let\nthat tab jump to a different tab stop depending whether we emit that\nspace or not.\n\nHowever, the proper fix would be relatively ugly and intrusive because\nit would have to *weaken* the WS_SPACE_BEFORE_TAB option in ws.c.\nBesides, we do not expose the --dual-color option in cases other than\nthe `branch-diff` command, which only uses a hard-coded output_prefix of\nfour spaces (which misses the problem by one column ;-)).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n diff.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/diff.c b/diff.c\nindex 98a41e88620..b98a18fe014 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -1098,6 +1098,12 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n \t\t\telse if (c != '+')\n \t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t\t/* Avoid space-before-tab warning */\n+\t\t\tif (c == ' ' && (len < 2 || line[1] == '\\t' ||\n+\t\t\t\t\t line[1] == '\\r' || line[1] == '\\n')) {\n+\t\t\t\tline++;\n+\t\t\t\tlen--;\n+\t\t\t}\n \t\t}\n \t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK,\n-- \n2.17.0.409.g71698f11835\n\n\n"},{"id":"346676","messageId":"019cce70-c109-496e-e043-c471dcb21e00@ramsayjones.plus.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805040829390.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Ramsay Jones","fromEmail":"ramsay@ramsayjones.plus.com","sentAt":"2018-05-04T15:37:02Z","receivedAt":"2018-05-04T15:37:08Z","isPatch":true,"sender":{"key":"ramsay@ramsayjones.plus.com","avatar":"https://avatars.githubusercontent.com/u/33702710?v=4"},"body":"\n\nOn 04/05/18 07:40, Johannes Schindelin wrote:\n[snip] \n> BTW I ran `make sparse` for the first time, and it spits out tons of\n> stuff. And I notice that they are all non-fatal warnings, but so were the\n> ones you pointed out above. This is a bit sad, as I would *love* to\n> install a VSTS build job to run `make sparse` automatically. Examples of\n> warnings *after* applying your patch:\n> \n> connect.c:481:40: warning: incorrect type in argument 2 (invalid types)\n> connect.c:481:40:    expected union __CONST_SOCKADDR_ARG [usertype] __addr\n> connect.c:481:40:    got struct sockaddr *ai_addr\n> \n> or\n> \n> pack-revindex.c:65:23: warning: memset with byte count of 262144\n> \n> What gives?\n\nMy stock answer, until recently, was that you are using a very\nold version of sparse. Which is probably still true here - but\nI recently noticed that more up-to-date platforms/gcc versions\nalso have many problems. (The main sparse contributors tend to\nstick with conservative distros and/or don't use sparse on any\nsoftware that uses system headers - thus they tend not to notice\nthe problems caused by new gcc/glibc versions! ;-) )\n\nSince I am on Linux Mint 18.3 (based on the last Ubuntu LTS) and\nbuild sparse from source, the current 'master', 'next' and 'pu'\nbranches are all 'sparse-clean' for me. (On cygwin, which is\nbuilt with NO_REGEX, I have a single sparse warning).\n\nI was just about to say that, unusually for me, I was using a\nsparse built from a release tag, but then remembered that I have\nsome additional patches which fixes a problem on fedora 27!\nUsing sparse on fedora 27 is otherwise useless. (There are still\nmany warnings spewed on f27 - but they are caused by incorrect\nsystem headers :( ).\n\nThe current release of sparse is v0.5.2, which probably hasn't\nbeen included in any distro yet (I think the previous release\nv0.5.1, which should also work for you, is in Debian unstable).\nIf you wanted to try building a more up-to-date sparse, the repo\nis at: git://git.kernel.org/pub/scm/devel/sparse/sparse.git.\n\nLinux Mint 19, which will be released in about a month, will be\nusing the Ubuntu 18.04 LTS as a base, so I guess it is possible\nthat I will need to debug sparse again ...\n\nBTW, I spent some time last night playing with 'git-branch-diff'.\n\nFirst of all - Good Job! This tool will be very useful (thanks\nalso go to Thomas, of course).\n\nI noticed that there seemed to be an occasional 'whitespace error'\nindicator (red background) directly after an +/- change character\nwhich I suspect is an error (I haven't actually checked). However,\nthis indicator disappears if you add the --dual-color option.\n\nThanks!\n\nATB,\nRamsay Jones\n"},{"id":"346679","messageId":"CABPp-BEEgeo=5hkaTe8LrOMONSv3VdPi_cP4ADMC69oG3htC1g@mail.gmail.com","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-05-04T16:21:05Z","receivedAt":"2018-05-04T16:21:10Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, May 4, 2018 at 8:34 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> The incredibly useful `git-tbdiff` tool to compare patch series (say, to see\n> what changed between two iterations sent to the Git mailing list) is slightly\n> less useful for this developer due to the fact that it requires the `hungarian`\n> and `numpy` Python packages which are for some reason really hard to build in\n> MSYS2. So hard that I even had to give up, because it was simply easier to\n> reimplement the whole shebang as a builtin command.\n\ntbdiff is awesome; thanks for bringing it in as a builtin to git.\n\nI've run through a few cases, comparing output of tbdiff and\nbranch-diff.  So far, what I've noted is that they produce largely the\nsame output except that:\n\n- tbdiff seems to shorten shas to 7 characters, branch-diff is using\n10, in git.git at least.  (Probably a good change)\n- tbdiff aligned output columns better when there were more than 9\npatches (I'll comment more on patch 09/18)\n- As noted elsewhere in the review of round 1, tbdiff uses difflib\nwhile branch-diff uses xdiff.  I found some cases where that mattered,\nand in all of them, I either felt like the difference was irrelevant\nor that difflib was suboptimal, so this is definitely an improvement\nfor me.\n- branch-diff produces it's output faster, and it is automatically\npaged.  This is really cool.\n\nAlso, I don't have bash-completion for either tbdiff or branch-diff.\n:-(  But I saw some discussion on the v1 patches about how this gets\nhandled...  :-)\n\n\nElijah\n"},{"id":"346680","messageId":"CABPp-BHbVTmNrZ32ZYQuoH0-XtFL+v7k-J7+t4_JWxds4K1U2Q@mail.gmail.com","threadId":"48405","inReplyTo":"0e4c8279e467e2e75864bcce8ec90cf4f81c2c34.1525448066.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 09/18] branch-diff: adjust the output of the commit pairs","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-05-04T16:25:34Z","receivedAt":"2018-05-04T16:25:39Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Dscho,\n\nOn Fri, May 4, 2018 at 8:34 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> This change brings branch-diff yet another step closer to feature parity\n> with tbdiff: it now shows the oneline, too, and indicates with `=` when\n> the commits have identical diffs.\n>\n<snip>\n> @@ -270,9 +272,57 @@ static int get_correspondences(struct string_list *a, struct string_list *b,\n>         return res;\n>  }\n>\n> -static const char *short_oid(struct patch_util *util)\n> +static void output_pair_header(struct strbuf *buf,\n> +                              int i, struct patch_util *a_util,\n> +                              int j, struct patch_util *b_util)\n>  {\n> -       return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n> +       static char *dashes;\n> +       struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n> +       struct commit *commit;\n> +\n> +       if (!dashes) {\n> +               char *p;\n> +\n> +               dashes = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV));\n> +               for (p = dashes; *p; p++)\n> +                       *p = '-';\n> +       }\n> +\n> +       strbuf_reset(buf);\n> +       if (i < 0)\n> +               strbuf_addf(buf, \"-:  %s \", dashes);\n> +       else\n> +               strbuf_addf(buf, \"%d:  %s \", i + 1,\n\n\nOne nice thing tbdiff did was to right align patch numbers (which also\nhelped align other columns in the output).  So, for example when there\nare more than 9 patches I would see output like:\n\n...\n 8: a980de43fd =  8: 362ab315ac directory rename detection: testcases\nexploring possibly suboptimal merges\n 9: 3633e79ed9 =  9: 792e1371d9 directory rename detection:\nmiscellaneous testcases to complete coverage\n10: e10d07ef40 = 10: a0b0a15103 directory rename detection: tests for\nhandling overwriting untracked files\n11: f6d84b503e = 11: a7a436042a directory rename detection: tests for\nhandling overwriting dirty files\n...\n\nwhereas branch-diff here is instead giving output of the form\n\n...\n8:  a980de43fd = 8:  362ab315ac directory rename detection: testcases\nexploring possibly suboptimal merges\n9:  3633e79ed9 = 9:  792e1371d9 directory rename detection:\nmiscellaneous testcases to complete coverage\n10:  e10d07ef40 = 10:  a0b0a15103 directory rename detection: tests\nfor handling overwriting untracked files\n11:  f6d84b503e = 11:  a7a436042a directory rename detection: tests\nfor handling overwriting dirty files\n...\n\n\nNot a critical difference, but it'd be nice to match tbdiff here all the same.\n\n> +                           find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n> +\n> +       if (i < 0)\n> +               strbuf_addch(buf, '>');\n> +       else if (j < 0)\n> +               strbuf_addch(buf, '<');\n> +       else if (strcmp(a_util->patch, b_util->patch))\n> +               strbuf_addch(buf, '!');\n> +       else\n> +               strbuf_addch(buf, '=');\n> +\n> +       if (j < 0)\n> +               strbuf_addf(buf, \" -:  %s\", dashes);\n> +       else\n> +               strbuf_addf(buf, \" %d:  %s\", j + 1,\n\nSame comment on these last two strbuf_addf's about alignment.\n\n\n\nElijah\n"},{"id":"346682","messageId":"CABPp-BF_F2rzmyP+1C7=ucM25DGP6w-u2Qd7QNMUcdtGjwZs2Q@mail.gmail.com","threadId":"48405","inReplyTo":"CABPp-BEEgeo=5hkaTe8LrOMONSv3VdPi_cP4ADMC69oG3htC1g@mail.gmail.com","subject":"Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-05-04T16:30:37Z","receivedAt":"2018-05-04T16:30:42Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi,\n\nOn Fri, May 4, 2018 at 9:21 AM, Elijah Newren <newren@gmail.com> wrote:\n> On Fri, May 4, 2018 at 8:34 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n>> The incredibly useful `git-tbdiff` tool to compare patch series (say, to see\n>> what changed between two iterations sent to the Git mailing list) is slightly\n>> less useful for this developer due to the fact that it requires the `hungarian`\n>> and `numpy` Python packages which are for some reason really hard to build in\n>> MSYS2. So hard that I even had to give up, because it was simply easier to\n>> reimplement the whole shebang as a builtin command.\n>\n> tbdiff is awesome; thanks for bringing it in as a builtin to git.\n>\n> I've run through a few cases, comparing output of tbdiff and\n> branch-diff.  So far, what I've noted is that they produce largely the\n> same output except that:\n>\n> - tbdiff seems to shorten shas to 7 characters, branch-diff is using\n> 10, in git.git at least.  (Probably a good change)\n\nSorry, a quick self-correction here:\n\ntbdiff, when using an actual shortened sha, uses 10 characters.  But\nwhen a patch doesn't have a match, tbdiff seems to use seven dashes on\none side in lieu of a shortened sha, whereas branch-diff will use 10\ncharacters whether it has an actual shortened sha or is just putting a\nbunch of dashes there.  So, this is definitely a good change.\n\n> - tbdiff aligned output columns better when there were more than 9\n> patches (I'll comment more on patch 09/18)\n> - As noted elsewhere in the review of round 1, tbdiff uses difflib\n> while branch-diff uses xdiff.  I found some cases where that mattered,\n> and in all of them, I either felt like the difference was irrelevant\n> or that difflib was suboptimal, so this is definitely an improvement\n> for me.\n> - branch-diff produces it's output faster, and it is automatically\n> paged.  This is really cool.\n>\n> Also, I don't have bash-completion for either tbdiff or branch-diff.\n> :-(  But I saw some discussion on the v1 patches about how this gets\n> handled...  :-)\n"},{"id":"346684","messageId":"CABPp-BERh1FGzEJSu+6Z0aGC3dJxx+P=9xwdCCsPgnG8jWvQMg@mail.gmail.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805040829390.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-05-04T16:34:59Z","receivedAt":"2018-05-04T16:35:03Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, May 3, 2018 at 11:40 PM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> I actually have a hacky script to fixup commits in a patch series. It lets\n> me stage part of the current changes, then figures out which of the\n> commits' changes overlap with the staged changed. If there is only one\n> commit, it automatically commits with --fixup, otherwise it lets me choose\n> which one I want to fixup (giving me the list of candidates).\n\nOoh, interesting.  Are you willing to share said hacky script by chance?\n\n(And as a total aside, I found your apply-from-public-inbox.sh script\nand really like it.  Thanks for making it public.)\n"},{"id":"346723","messageId":"20180505182427.GB17700@sigill.intra.peff.net","threadId":"48405","inReplyTo":"3f51970cbc44bfe34133c48c0844ed3723e83808.1525448066.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 01/18] Add a function to solve least-cost assignment problems","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-05T18:24:27Z","receivedAt":"2018-05-05T18:24:32Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, May 04, 2018 at 05:34:29PM +0200, Johannes Schindelin wrote:\n\n> The Jonker-Volgenant algorithm was implemented to answer questions such\n> as: given two different versions of a topic branch (or iterations of a\n> patch series), what is the best pairing of commits/patches between the\n> different versions?\n\nI love git-tbdiff, so I'm excited to see it getting more exposure (and a\nspeedup). Thanks for working on this!\n\nTwo minor nits on this patch:\n\n> +/*\n> + * The parameter `cost` is the cost matrix: the cost to assign column j to row\n> + * i is `cost[j + column_count * i].\n> + */\n> +int compute_assignment(int column_count, int row_count, double *cost,\n> +\t\t       int *column2row, int *row2column)\n> +{\n> +\tdouble *v = xmalloc(sizeof(double) * column_count), *d;\n\nPlease use st_mult, xcalloc, or ALLOC_ARRAY here to avoid unchecked\nmultiplication in an allocation. This is probably hard to exploit in\npractice (since you'd need sizeof(size_t)/8 columns, which presumably\nrequires allocating some heavier-weight struct per item). But it makes\nauditing easier if we avoid the pattern altogether.\n\n> +/*\n> + * Compute an assignment of columns -> rows (and vice versa) such that every\n> + * column is assigned to at most one row (and vice versa) minimizing the\n> + * overall cost.\n> + *\n> + * The parameter `cost` is the cost matrix: the cost to assign column j to row\n> + * i is `cost[j + column_count * i].\n> + *\n> + * The arrays column2row and row2column will be populated with the respective\n> + * assignments (-1 for unassigned, which can happen only if column_count !=\n> + * row_count).\n> + */\n> +int compute_assignment(int column_count, int row_count, double *cost,\n> +\t\t       int *column2row, int *row2column);\n\nIt looks like this always returns zero. Is there a ever a case where we\nwould return an error if this? If not, should it just be void?\n\n-Peff\n"},{"id":"346724","messageId":"20180505182631.GC17700@sigill.intra.peff.net","threadId":"48405","inReplyTo":"a1ea0320b64527ee6ce9856dcf359513d13052b7.1525448066.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-05T18:26:31Z","receivedAt":"2018-05-05T18:26:36Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, May 04, 2018 at 05:34:32PM +0200, Johannes Schindelin wrote:\n\n> This builtin does not do a whole lot so far, apart from showing a usage\n> that is oddly similar to that of `git tbdiff`. And for a good reason:\n> the next commits will turn `branch-diff` into a full-blown replacement\n> for `tbdiff`.\n\nOne minor point about the name: will it become annoying as a tab\ncompletion conflict with git-branch?\n\nIt feels really petty complaining about the name, but I just want to\nraise the point, since it will never be easier to change than right now.\n\n(And no, I don't really have another name in mind; I'm just wondering if\n\"subset\" names like this might be a mild annoyance in the long run).\n\n-Peff\n"},{"id":"346725","messageId":"20180505182922.GD17700@sigill.intra.peff.net","threadId":"48405","inReplyTo":"1ebbe359547689d32aa27564929d733a26bb8054.1525448066.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 13/18] color: provide inverted colors, too","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-05T18:29:22Z","receivedAt":"2018-05-05T18:29:28Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, May 04, 2018 at 05:34:58PM +0200, Johannes Schindelin wrote:\n\n> For every regular color, there exists the inverted equivalent where\n> background and foreground colors are exchanged.\n> \n> We will use this in the next commit to allow inverting *just* the +/-\n> signs in a diff.\n\nThere's a \"reverse\" attribute (which we already parse and support) that\ncan do this without having to repeat the colors. AFAIK it's well\nsupported everywhere, but I could be wrong.\n\nI wonder if that would make configuring this slightly more pleasant,\nsince it saves the user having to define \"oldinv\" whenever they change\n\"old\".\n\n-Peff\n"},{"id":"346730","messageId":"nycvar.QRO.7.76.6.1805052130360.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"019cce70-c109-496e-e043-c471dcb21e00@ramsayjones.plus.com","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-05T19:41:29Z","receivedAt":"2018-05-05T19:41:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Ramsay,\n\nOn Fri, 4 May 2018, Ramsay Jones wrote:\n\n> On 04/05/18 07:40, Johannes Schindelin wrote:\n> [snip] \n> > BTW I ran `make sparse` for the first time, and it spits out tons of\n> > stuff. And I notice that they are all non-fatal warnings, but so were the\n> > ones you pointed out above. This is a bit sad, as I would *love* to\n> > install a VSTS build job to run `make sparse` automatically. Examples of\n> > warnings *after* applying your patch:\n> > \n> > connect.c:481:40: warning: incorrect type in argument 2 (invalid types)\n> > connect.c:481:40:    expected union __CONST_SOCKADDR_ARG [usertype] __addr\n> > connect.c:481:40:    got struct sockaddr *ai_addr\n> > \n> > or\n> > \n> > pack-revindex.c:65:23: warning: memset with byte count of 262144\n> > \n> > What gives?\n> \n> My stock answer, until recently, was that you are using a very\n> old version of sparse.\n\nSure. I did this in an Ubuntu 16.04 LTS VM, via `sudo apt-get install\nsparse`.\n\n> Which is probably still true here - but I recently noticed that more\n> up-to-date platforms/gcc versions also have many problems. (The main\n> sparse contributors tend to stick with conservative distros and/or don't\n> use sparse on any software that uses system headers - thus they tend not\n> to notice the problems caused by new gcc/glibc versions! ;-) )\n> \n> Since I am on Linux Mint 18.3 (based on the last Ubuntu LTS) and build\n> sparse from source, the current 'master', 'next' and 'pu' branches are\n> all 'sparse-clean' for me. (On cygwin, which is built with NO_REGEX, I\n> have a single sparse warning).\n> \n> I was just about to say that, unusually for me, I was using a sparse\n> built from a release tag, but then remembered that I have some\n> additional patches which fixes a problem on fedora 27!  Using sparse on\n> fedora 27 is otherwise useless. (There are still many warnings spewed on\n> f27 - but they are caused by incorrect system headers :( ).\n> \n> The current release of sparse is v0.5.2, which probably hasn't been\n> included in any distro yet (I think the previous release v0.5.1, which\n> should also work for you, is in Debian unstable).  If you wanted to try\n> building a more up-to-date sparse, the repo is at:\n> git://git.kernel.org/pub/scm/devel/sparse/sparse.git.\n\nWell, what I would want to do is let the cloud work for me. By adding an\nautomated build to my Visual Studio Team Services (VSTS) account, of\ncourse, as I have \"cloud privilege\" (i.e. I work in the organization\nproviding the service, so I get to play with all of it for free).\n\nSo I really don't want to build sparse every time a new revision needs to\nbe tested (whether that be from one of my branches, an internal PR for\npre-review of patches to be sent to the mailing list, or maybe even `pu`\nor the personalized branches on https://github.com/gitster/git).\n\nI really would need a ready-to-install sparse, preferably as light-weight\nas possible (by not requiring any dependencies outside what is available\nin VSTS' hosted Linux build agents.\n\nMaybe there is a specific apt source for sparse?\n\n> Linux Mint 19, which will be released in about a month, will be\n> using the Ubuntu 18.04 LTS as a base, so I guess it is possible\n> that I will need to debug sparse again ...\n\n:-)\n\n> BTW, I spent some time last night playing with 'git-branch-diff'.\n\nGreat!\n\n> First of all - Good Job! This tool will be very useful (thanks\n> also go to Thomas, of course).\n\nBoth Thomases. Thomas Rast and Thomas Gummerer.\n\n> I noticed that there seemed to be an occasional 'whitespace error'\n> indicator (red background) directly after an +/- change character\n> which I suspect is an error (I haven't actually checked). However,\n> this indicator disappears if you add the --dual-color option.\n\nIndeed. This is a quirk of the whitespace error paired with diffing diffs:\nthe whitespace error correctly treats the leading space as marker for a\ncontext line, but if you diff diffs, the next character may still be a\nmarker for a context line (but this time the \"inner\" diff). And our\nwhitespace error detection mechanism cannot guess that it looks at a diff\nof diffs.\n\nHowever, in dual-color mode, we know that we will almost certainly look at\ndiffs of diffs (except if the change is in the commit message, in which\ncase I don't care about whitespace errors at all, anyway).\n\nSo I have this hack in 16/18:\nhttps://public-inbox.org/git/b99ab186c4f11239a10950b9902d9c87d0e60513.1525448066.git.johannes.schindelin@gmx.de/T/#u\n\nEssentially, I extend the dual-color mode to detect where such a bogus\nwhitespace error would be detected, and simply *skip the space*! I can get\naway with that because dual-color is meant for human consumption, and if a\nhorizontal tab follows, it would not matter whether there was a space: it\nwould find the same tab stop. Likewise, if the space comes before a CR or\nLF, or even just before the final NUL, the space can be safely omitted\nfrom the output, too.\n\nCiao,\nDscho\n"},{"id":"351595","messageId":"144363006cf79624ace020c1856bfd760bdb2c62.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 19/20] range-diff: left-pad patch numbers","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-05-05T19:52:20Z","receivedAt":"2018-05-05T19:52:20Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nAs pointed out by Elijah Newren, tbdiff has this neat little alignment\ntrick where it outputs the commit pairs with patch numbers that are\npadded to the maximal patch number's width:\n\n\t  1: cafedead =   1: acefade first patch\n\t[...]\n\t314: beefeada < 314: facecab up to PI!\n\nLet's do the same in range-diff, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 21 +++++++++++++--------\n 1 file changed, 13 insertions(+), 8 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 870c3680c..d3e51bf36 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -254,7 +254,8 @@ static void get_correspondences(struct string_list *a, struct string_list *b,\n \tfree(b2a);\n }\n \n-static void output_pair_header(struct diff_options *diffopt, struct strbuf *buf,\n+static void output_pair_header(struct diff_options *diffopt, int patch_no_width,\n+\t\t\t       struct strbuf *buf,\n \t\t\t       struct patch_util *a_util,\n \t\t\t       struct patch_util *b_util)\n {\n@@ -293,9 +294,9 @@ static void output_pair_header(struct diff_options *diffopt, struct strbuf *buf,\n \tstrbuf_reset(buf);\n \tstrbuf_addstr(buf, status == '!' ? color_old : color);\n \tif (!a_util)\n-\t\tstrbuf_addf(buf, \"-:  %s \", dashes);\n+\t\tstrbuf_addf(buf, \"%*s:  %s \", patch_no_width, \"-\", dashes);\n \telse\n-\t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n+\t\tstrbuf_addf(buf, \"%*d:  %s \", patch_no_width, a_util->i + 1,\n \t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n \n \tif (status == '!')\n@@ -305,9 +306,9 @@ static void output_pair_header(struct diff_options *diffopt, struct strbuf *buf,\n \t\tstrbuf_addf(buf, \"%s%s\", color_reset, color_new);\n \n \tif (!b_util)\n-\t\tstrbuf_addf(buf, \" -:  %s\", dashes);\n+\t\tstrbuf_addf(buf, \" %*s:  %s\", patch_no_width, \"-\", dashes);\n \telse\n-\t\tstrbuf_addf(buf, \" %d:  %s\", b_util->i + 1,\n+\t\tstrbuf_addf(buf, \" %*d:  %s\", patch_no_width, b_util->i + 1,\n \t\t\t    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));\n \n \tcommit = lookup_commit_reference(oid);\n@@ -360,6 +361,7 @@ static void output(struct string_list *a, struct string_list *b,\n \t\t   struct diff_options *diffopt)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n+\tint patch_no_width = decimal_width(1 + (a->nr > b->nr ? a->nr : b->nr));\n \tint i = 0, j = 0;\n \n \t/*\n@@ -381,21 +383,24 @@ static void output(struct string_list *a, struct string_list *b,\n \n \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n \t\tif (i < a->nr && a_util->matching < 0) {\n-\t\t\toutput_pair_header(diffopt, &buf, a_util, NULL);\n+\t\t\toutput_pair_header(diffopt, patch_no_width, &buf,\n+\t\t\t\t\t   a_util, NULL);\n \t\t\ti++;\n \t\t\tcontinue;\n \t\t}\n \n \t\t/* Show unmatched RHS commits. */\n \t\twhile (j < b->nr && b_util->matching < 0) {\n-\t\t\toutput_pair_header(diffopt, &buf, NULL, b_util);\n+\t\t\toutput_pair_header(diffopt, patch_no_width, &buf,\n+\t\t\t\t\t   NULL, b_util);\n \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n \n \t\t/* Show matching LHS/RHS pair. */\n \t\tif (j < b->nr) {\n \t\t\ta_util = a->items[b_util->matching].util;\n-\t\t\toutput_pair_header(diffopt, &buf, a_util, b_util);\n+\t\t\toutput_pair_header(diffopt, patch_no_width, &buf,\n+\t\t\t\t\t   a_util, b_util);\n \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n \t\t\t\t\t   b->items[j].string, diffopt);\n-- \ngitgitgadget\n\n"},{"id":"346731","messageId":"nycvar.QRO.7.76.6.1805052141550.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CABPp-BEEgeo=5hkaTe8LrOMONSv3VdPi_cP4ADMC69oG3htC1g@mail.gmail.com","subject":"Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-05T20:03:15Z","receivedAt":"2018-05-05T20:03:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Fri, 4 May 2018, Elijah Newren wrote:\n\n> On Fri, May 4, 2018 at 8:34 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > The incredibly useful `git-tbdiff` tool to compare patch series (say, to see\n> > what changed between two iterations sent to the Git mailing list) is slightly\n> > less useful for this developer due to the fact that it requires the `hungarian`\n> > and `numpy` Python packages which are for some reason really hard to build in\n> > MSYS2. So hard that I even had to give up, because it was simply easier to\n> > reimplement the whole shebang as a builtin command.\n> \n> tbdiff is awesome; thanks for bringing it in as a builtin to git.\n\nYou're welcome.\n\n> I've run through a few cases, comparing output of tbdiff and\n> branch-diff.  So far, what I've noted is that they produce largely the\n> same output except that:\n> \n> - tbdiff seems to shorten shas to 7 characters, branch-diff is using\n> 10, in git.git at least.  (Probably a good change)\n\nYes, it is a good change ;-)\n\n> - tbdiff aligned output columns better when there were more than 9\n> patches (I'll comment more on patch 09/18)\n\nI added a new patch to align the patch numbers specifically. I considered\nsquashing it into 9/18, but decided against it: it will make it easier to\nread through the rationale when calling `git annotate` on those lines.\n\n> - As noted elsewhere in the review of round 1, tbdiff uses difflib\n> while branch-diff uses xdiff.  I found some cases where that mattered,\n> and in all of them, I either felt like the difference was irrelevant\n> or that difflib was suboptimal, so this is definitely an improvement\n> for me.\n\nIndeed. It is more or less ambiguities that get resolved differently.\n\n> - branch-diff produces it's output faster, and it is automatically\n> paged.  This is really cool.\n\n:-)\n\nIt was actually the paging that made the most difference for me. The `git\ntbdiff` command broke for me as soon as I switched on the pager, as tbdiff\ngot confused by the decoration (AEvar had put up a PR to fix that, but\nthat PR has not even so much as been answered in the meantime, so I\nthought it'd be a good time to rewrite the entire shebang in C, also\nbecause I could not use tbdiff *at all* on Windows due to its hefty\ndependencies).\n\n> Also, I don't have bash-completion for either tbdiff or branch-diff.\n> :-(  But I saw some discussion on the v1 patches about how this gets\n> handled...  :-)\n\nOh? Does 18/18 not work for you?\nhttps://public-inbox.org/git/71698f11835311c103aae565a2a761d10f4676b9.1525448066.git.johannes.schindelin@gmx.de/\n\nCiao,\nDscho\n"},{"id":"346732","messageId":"nycvar.QRO.7.76.6.1805052220360.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CABPp-BERh1FGzEJSu+6Z0aGC3dJxx+P=9xwdCCsPgnG8jWvQMg@mail.gmail.com","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-05T20:24:21Z","receivedAt":"2018-05-05T20:24:34Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Fri, 4 May 2018, Elijah Newren wrote:\n\n> On Thu, May 3, 2018 at 11:40 PM, Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> > I actually have a hacky script to fixup commits in a patch series. It lets\n> > me stage part of the current changes, then figures out which of the\n> > commits' changes overlap with the staged changed. If there is only one\n> > commit, it automatically commits with --fixup, otherwise it lets me choose\n> > which one I want to fixup (giving me the list of candidates).\n> \n> Ooh, interesting.  Are you willing to share said hacky script by chance?\n\nIt is part of a real huge hacky script of pretty much all things I add as\naliases, so I extracted the relevant part for you:\n\n-- snip --\n#!/bin/sh\n\nfixup () { # [--upstream=<branch>] [--not=<tip-to-skip>]\n\tupstream=\n\tnot=\n\twhile case \"$1\" in\n\t--upstream) shift; upstream=\"$1\";;\n\t--upstream=*) upstream=\"${1#*=}\";;\n\t--not) shift; not=\"$not $1\";;\n\t--not=*) not=\"$not ${1#*=}\";;\n\t-*) die \"Unknown option: $1\";;\n\t*) break;;\n\tesac; do shift; done\n\n\ttest $# -le 1 ||\n\tdie \"Need 0 or 1 commit\"\n\n\t! git diff-index --cached --quiet --ignore-submodules HEAD -- || {\n\t\tgit update-index --ignore-submodules --refresh\n\t        ! git diff-files --quiet --ignore-submodules -- ||\n\t\tdie \"No changes\"\n\n\t\tgit add -p ||\n\t\texit\n\t}\n\t! git diff-index --cached --quiet --ignore-submodules HEAD -- ||\n\tdie \"No staged changes\"\n\n\ttest $# = 1 || {\n\t\tif test -z \"$upstream\"\n\t\tthen\n\t\t\tupstream=\"$(git rev-parse --symbolic-full-name \\\n\t\t\t\tHEAD@{upstream} 2> /dev/null)\" &&\n\t\t\ttest \"$(git rev-parse HEAD)\" != \\\n\t\t\t\t\"$(git rev-parse $upstream)\" ||\n\t\t\tupstream=origin/master\n\t\tfi\n\n\t\trevs=\"$(git rev-list $upstream.. --not $not --)\" ||\n\t\tdie \"Could not get commits between $upstream and HEAD\"\n\n\t\ttest -n \"$revs\" ||\n\t\tdie \"No commits between $upstream and HEAD\"\n\n\t\twhile count=$(test -z \"$revs\" && echo 0 || echo \"$revs\" | wc -l | tr -dc 0-9) &&\n\t\t\ttest $count -gt 1\n\t\tdo\n\t\t\tprintf '\\nMultiple candidates:\\n'\n\t\t\techo $revs | xargs git log --no-walk --oneline | cat -n\n\n\t\t\tread input\n\t\t\tcase \"$input\" in\n\t\t\t[0-9]|[0-9][0-9]|[0-9][0-9][0-9])\n\t\t\t\trevs=\"$(echo \"$revs\" | sed -n \"${input}p\")\"\n\t\t\t\tcount=1\n\t\t\t\tbreak\n\t\t\t\t;;\n\t\t\th|hh)\n\t\t\t\trevs=$(history_of_staged_changes $upstream..)\n\t\t\t\tcontinue\n\t\t\t\t;;\n\t\t\thhhh)\n\t\t\t\thistory_of_staged_changes -p $upstream..\n\t\t\t\tcontinue\n\t\t\t\t;;\n\t\t\tp)\n\t\t\t\tgit log -p --no-walk $revs\n\t\t\t\tcontinue\n\t\t\t\t;;\n\t\t\t[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f])\n\t\t\t\trevs=$input\n\t\t\t\tcontinue\n\t\t\tesac\n\t\t\trevs=\"$(git rev-list --no-walk --grep=\"$input\" $revs)\"\n\t\tdone\n\n\t\ttest $count = 1 ||\n\t\tdie \"No commit given\"\n\n\t\tset $revs\n\t}\n\n\tgit commit --fixup \"$1\"\n\tmessage=\"$(git show -s --format=%s%n%n%b)\"\n\tcase \"$message\" in\n\t'fixup! fixup! '*)\n\t\tmessage=\"${message#fixup! }\"\n\t\tmessage=\"${message#fixup! }\"\n\t\tmessage=\"${message#fixup! }\"\n\t\tgit commit --amend -m \"fixup! $message\"\n\t\t;;\n\tesac\n}\n\nhistory_of_staged_changes () { # <commit-range>\n\tpretty=\n\tif test \"a-p\" = \"a$1\"\n\tthen\n\t\tpretty=-p\n\t\tshift\n\tfi\n\n\ttest $# -le 1 ||\n\tdie \"Usage: $0 <commit-range>\"\n\n\ttest $# = 1 || {\n\t\tupstream=$(git rev-parse --verify HEAD@{u} 2>/dev/null) ||\n\t\tupstream=origin/master\n\t\tset $upstream..\n\t}\n\n\targs=$(for file in $(git diff --no-color --cached --name-only)\n\t\tdo\n\t\t\tfor hunk in $(get_hunks --cached -- \"$file\")\n\t\t\tdo\n\t\t\t\thunk1=${hunk%:*}\n\t\t\t\tstart1=${hunk1%,*}\n\t\t\t\tend1=$(($start1+${hunk1#*,}-1))\n\t\t\t\techo \"'$file:$start1-$end1'\"\n\t\t\tdone\n\t\tdone)\n\n\ttest -n \"$args\" ||\n\tdie \"No staged files!\"\n\n\teval hunk_history $pretty \"$1\" -- $args\n}\n\nhunk_history () { # <commit-range> -- <file>:<start>[-<end>]...\n\tpretty=\n\tif test \"a-p\" = \"a$1\"\n\tthen\n\t\tpretty=t\n\t\tshift\n\tfi\n\n\ttest $# -ge 3 && test a-- = \"a$2\" ||\n\tdie \"Usage: $0 <commit-range> -- <file>:<start>[-<end>]...\"\n\n\trange=\"$1\"\n\tshift; shift\n\n\tfiles=\"$(for arg\n\t\tdo\n\t\t\techo \"'${arg%:*}'\"\n\t\tdone)\"\n\n\tfor commit in $(eval git rev-list --topo-order \"$range\" -- $files)\n\tdo\n\t\tif test -z \"$lines\"\n\t\tthen\n\t\t\tlines=\"$(echo \"$*\" |\n\t\t\t\ttr ' ' '\\n' |\n\t\t\t\tsed \"s/^/$commit /\")\"\n\t\tfi\n\n\t\ttouched=\n\t\tfor line in $(echo \"$lines\" |\n\t\t\t\tsed -n \"s|^$commit ||p\")\n\t\tdo\n\t\t\tfile=\"${line%:*}\"\n\t\t\tcurstart=\"${line#$file:}\"\n\t\t\tcurend=\"${curstart#*-}\"\n\t\t\tcurstart=\"${curstart%%-*}\"\n\n\t\t\tdiff_output=\n\t\t\tparentstart=$curstart\n\t\t\tparentend=$curend\n\t\t\tparents=$(git rev-list --no-walk --parents \\\n\t\t\t\t\t$commit -- \"$file\" |\n\t\t\t\tcut -c 41-)\n\t\t\tif test -z \"$parents\"\n\t\t\tthen\n\t\t\t\ttouched=t\n\t\t\tfi\n\n\t\t\tfor parent in $parents\n\t\t\tdo\n\t\t\t\tfor hunk in $(get_hunks ^$parent $commit -- \\\n\t\t\t\t\t\"$file\")\n\t\t\t\tdo\n\t\t\t\t\thunk1=${hunk%:*}\n\t\t\t\t\tstart1=${hunk1%,*}\n\t\t\t\t\tend1=$(($start1+${hunk1#*,}-1))\n\n\t\t\t\t\thunk2=${hunk#*:}\n\t\t\t\t\tstart2=${hunk2%,*}\n\t\t\t\t\tend2=$(($start2+${hunk2#*,}-1))\n\n\t\t\t\t\tif test $start2 -le $curend &&\n\t\t\t\t\t\ttest $end2 -ge $curstart\n\t\t\t\t\tthen\n\t\t\t\t\t\ttouched=t\n\t\t\t\t\tfi\n\n\t\t\t\t\tif test $end2 -le $curstart\n\t\t\t\t\tthen\n\t\t\t\t\t\tdiff=$(($end1-$end2))\n\t\t\t\t\t\tparentstart=$(($curstart+$diff))\n\t\t\t\t\telif test $start2 -le $curstart\n\t\t\t\t\tthen\n\t\t\t\t\t\tparentstart=$start1\n\t\t\t\t\tfi\n\n\t\t\t\t\tif test $end2 -le $curend\n\t\t\t\t\tthen\n\t\t\t\t\t\tdiff=$(($end1-$end2))\n\t\t\t\t\t\tparentend=$(($curend+$diff))\n\t\t\t\t\telif test $start2 -le $curend\n\t\t\t\t\tthen\n\t\t\t\t\t\tparentend=$end1\n\t\t\t\t\tfi\n\t\t\t\tdone\n\n\t\t\t\tif test -n \"$pretty\" &&\n\t\t\t\t\ttest $curstart != $parentstart ||\n\t\t\t\t\ttest $curend != $parentend\n\t\t\t\tthen\n\t\t\t\t\ttest -n \"$(echo \"$diff_output\" |\n\t\t\t\t\t\tsed -n \"s|^\\([^ -]\\[[^m]*m\\)*diff --git a/$file b/||p\")\" ||\n\t\t\t\t\tdiff_output=\"$(printf '%s%s\\n' \"$diff_output\" \\\n\t\t\t\t\t\t\"$(git diff --color \\\n\t\t\t\t\t\t\t^$parent $commit -- $file |\n\t\t\t\t\t\tsed '/^\\([^ -]\\[[^m]*m\\)*@@ /,$d')\")\"\n\t\t\t\t\tprefix=\"$(git rev-parse --git-dir)\"\n\t\t\t\t\toldfile=\"${prefix}${prefix:+/}.old\"\n\t\t\t\t\tgit show $parent:$file 2>/dev/null |\n\t\t\t\t\tsed -n \"$parentstart,${parentend}p\" >$oldfile\n\t\t\t\t\tnewfile=\"${prefix}${prefix:+/}.new\"\n\t\t\t\t\tgit show $commit:$file |\n\t\t\t\t\tsed -n \"$curstart,${curend}p\" >$newfile\n\t\t\t\t\tdiff1=\"$(git diff --no-index --color $oldfile $newfile |\n\t\t\t\t\t\tsed '1,4d')\"\n\t\t\t\t\tdiff_output=\"$(printf '%s%s\\n' \"$diff_output\" \\\n\t\t\t\t\t\t\"$diff1\")\"\n\t\t\t\tfi\n\n\t\t\t\t# TODO: support renames here\n\t\t\t\tprefix=\"$parent $file:\"\n\t\t\t\tlines=\"$(printf '%s\\n%s%s' \"$lines\" \"$prefix\" \\\n\t\t\t\t\t\"$parentstart-$parentend\")\"\n\t\t\tdone\n\t\tdone\n\t\ttest -z \"$touched\" || {\n\t\t\tif test -z \"$pretty\"\n\t\t\tthen\n\t\t\t\techo $commit\n\t\t\telse\n\t\t\t\tgit show --color -s $commit --\n\t\t\t\techo \"$diff_output\"\n\t\t\tfi\n\t\t}\n\tdone\n}\n\n# takes a commit range and a file name\n# returns a list of <offset>,<count>:<offset>,<count>\nget_hunks () {\n\t# TODO: support renames here\n\tgit diff --no-color -U0 \"$@\" |\n\tsed -n -e 's/\\([-+][0-9][0-9]*\\) /\\1,1 /g' \\\n\t\t-e 's/^@@ -\\([0-9]*,[0-9]*\\) +\\([0-9]*,[0-9]*\\) .*/\\1 \\2/p' |\n\tfix_hunks\n}\n\nfix_hunks () {\n\twhile read hunk1 hunk2\n\tdo\n\t\tcase $hunk1 in\n\t\t*,0)\n\t\t\tprintf '%d,0:' $((${hunk1%,0}+1))\n\t\t\t;;\n\t\t*)\n\t\t\tprintf '%s:' $hunk1\n\t\tesac\n\t\tcase $hunk2 in\n\t\t*,0)\n\t\t\tprintf '%d,0\\n' $((${hunk2%,0}+1))\n\t\t\t;;\n\t\t*)\n\t\t\tprintf '%s\\n' $hunk2\n\t\tesac\n\tdone\n}\n\nfixup \"$@\"\n-- snap --\n\nQuite a handful to read, eh? And no code comments. I always meant to\nannotate it with some helpful remarks for future me, but never got around\nto do that, either. These days, I would probably\n\n1. write the whole thing based on `git log -L <line-range>:<file>`, and\n\n2. either implement it in node.js for speed, or directly in C.\n\n> (And as a total aside, I found your apply-from-public-inbox.sh script\n> and really like it.  Thanks for making it public.)\n\nYou're welcome! I am glad it is useful to you.\n\nCiao,\nDscho\n"},{"id":"346734","messageId":"nycvar.QRO.7.76.6.1805052351560.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180505182427.GB17700@sigill.intra.peff.net","subject":"Re: [PATCH v2 01/18] Add a function to solve least-cost assignment problems","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-05T21:55:04Z","receivedAt":"2018-05-05T21:55:27Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Sat, 5 May 2018, Jeff King wrote:\n\n> On Fri, May 04, 2018 at 05:34:29PM +0200, Johannes Schindelin wrote:\n> \n> > The Jonker-Volgenant algorithm was implemented to answer questions such\n> > as: given two different versions of a topic branch (or iterations of a\n> > patch series), what is the best pairing of commits/patches between the\n> > different versions?\n> \n> I love git-tbdiff, so I'm excited to see it getting more exposure (and a\n> speedup). Thanks for working on this!\n\n:-)\n\n> Two minor nits on this patch:\n> \n> > +/*\n> > + * The parameter `cost` is the cost matrix: the cost to assign column j to row\n> > + * i is `cost[j + column_count * i].\n> > + */\n> > +int compute_assignment(int column_count, int row_count, double *cost,\n> > +\t\t       int *column2row, int *row2column)\n> > +{\n> > +\tdouble *v = xmalloc(sizeof(double) * column_count), *d;\n> \n> Please use st_mult, xcalloc, or ALLOC_ARRAY here to avoid unchecked\n> multiplication in an allocation. This is probably hard to exploit in\n> practice (since you'd need sizeof(size_t)/8 columns, which presumably\n> requires allocating some heavier-weight struct per item). But it makes\n> auditing easier if we avoid the pattern altogether.\n\nSure. I did mean to return errors in those case, but I guess it is not\nworth the trouble (what would we do in case of out-of-memory?).\n\n> > +/*\n> > + * Compute an assignment of columns -> rows (and vice versa) such that every\n> > + * column is assigned to at most one row (and vice versa) minimizing the\n> > + * overall cost.\n> > + *\n> > + * The parameter `cost` is the cost matrix: the cost to assign column j to row\n> > + * i is `cost[j + column_count * i].\n> > + *\n> > + * The arrays column2row and row2column will be populated with the respective\n> > + * assignments (-1 for unassigned, which can happen only if column_count !=\n> > + * row_count).\n> > + */\n> > +int compute_assignment(int column_count, int row_count, double *cost,\n> > +\t\t       int *column2row, int *row2column);\n> \n> It looks like this always returns zero. Is there a ever a case where we\n> would return an error if this? If not, should it just be void?\n\nFixed.\n\nCiao,\nDscho\n"},{"id":"346735","messageId":"nycvar.QRO.7.76.6.1805052355190.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180505182631.GC17700@sigill.intra.peff.net","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-05T21:57:26Z","receivedAt":"2018-05-05T21:57:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Sat, 5 May 2018, Jeff King wrote:\n\n> On Fri, May 04, 2018 at 05:34:32PM +0200, Johannes Schindelin wrote:\n> \n> > This builtin does not do a whole lot so far, apart from showing a usage\n> > that is oddly similar to that of `git tbdiff`. And for a good reason:\n> > the next commits will turn `branch-diff` into a full-blown replacement\n> > for `tbdiff`.\n> \n> One minor point about the name: will it become annoying as a tab\n> completion conflict with git-branch?\n\nI did mention this in the commit message of 18/18:\n\n    Without this patch, we would only complete the `branch-diff` part but\n    not the options and other arguments.\n\n    This of itself may already be slightly disruptive for well-trained\n    fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to\n    `git branch origin/master`, as we now no longer automatically append a\n    space after completing `git branch`: this is now ambiguous.\n\n> It feels really petty complaining about the name, but I just want to\n> raise the point, since it will never be easier to change than right now.\n\nI do hear you. Especially since I hate `git cherry` every single time that\nI try to tab-complete `git cherry-pick`.\n\n> (And no, I don't really have another name in mind; I'm just wondering if\n> \"subset\" names like this might be a mild annoyance in the long run).\n\nThey totally are, and if you can come up with a better name, I am really\ninterested in changing it before this hits `next`, even.\n\nCiao,\nDscho\n"},{"id":"346736","messageId":"nycvar.QRO.7.76.6.1805060001230.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180505182922.GD17700@sigill.intra.peff.net","subject":"Re: [PATCH v2 13/18] color: provide inverted colors, too","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-05T22:03:50Z","receivedAt":"2018-05-05T22:04:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Sat, 5 May 2018, Jeff King wrote:\n\n> On Fri, May 04, 2018 at 05:34:58PM +0200, Johannes Schindelin wrote:\n> \n> > For every regular color, there exists the inverted equivalent where\n> > background and foreground colors are exchanged.\n> > \n> > We will use this in the next commit to allow inverting *just* the +/-\n> > signs in a diff.\n> \n> There's a \"reverse\" attribute (which we already parse and support) that\n> can do this without having to repeat the colors. AFAIK it's well\n> supported everywhere, but I could be wrong.\n\nHow would I use that here, though? I need to get the thing via\ndiff_get_color_opt() which takes a parameter of type `enum color_diff`.\nThere is no way I can specify `reverse` here, can I?\n\n> I wonder if that would make configuring this slightly more pleasant,\n> since it saves the user having to define \"oldinv\" whenever they change\n> \"old\".\n\nI am all for making the configuration more pleasant. So I hope I can make\nuse of the `reverse` thing here, without having to introduce a new enum\nvalue.\n\nCiao,\nDscho\n"},{"id":"346737","messageId":"20180505234852.GR26695@zaya.teonanacatl.net","threadId":"48405","inReplyTo":"ba4791918c78770005d552856d8669648d7004f1.1525448066.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 12/18] branch-diff: use color for the commit pairs","fromName":"Todd Zullinger","fromEmail":"tmz@pobox.com","sentAt":"2018-05-05T23:48:52Z","receivedAt":"2018-05-05T23:48:58Z","isPatch":true,"sender":{"key":"tmz@pobox.com","avatar":"https://avatars.githubusercontent.com/u/806319?v=4"},"body":"Hi Johannes,\n\nAs many others have already said, thanks for this series!\nI've used tbdiff a bit over the years, but having a builtin\nwill make it much more convenient (and the speed boost from\na C implementation will be a very nice bonus).\n\nJohannes Schindelin wrote:\n> @@ -430,6 +451,8 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n>  \tstruct string_list branch1 = STRING_LIST_INIT_DUP;\n>  \tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n>  \n> +\tgit_diff_basic_config(\"diff.color.frag\", \"magenta\", NULL);\n> +\n>  \tdiff_setup(&diffopt);\n>  \tdiffopt.output_format = DIFF_FORMAT_PATCH;\n>  \tdiffopt.flags.suppress_diff_headers = 1;\n\nShould this also (or only) check color.diff.frag?  I thought\nthat color.diff.* was preferred over diff.color.*, though\nthat doesn't seem to be entirely true in all parts of the\ncurrent codebase.\n\nIn testing this series it seems that setting color.diff\noptions to change the various colors read earlier in this\npatch via diff_get_color_opt, as well as the 'frag' slot,\nare ignored.  Setting them via diff.color.<slot> does work.\n\nThe later patch adding a man page documents branch-diff as\nusing `diff.color.*` and points to git-config(1), but the\nconfig docs only list color.diff.\n\nIs this a bug in the diff_get_color{,_opt}() tooling?\nIt's certainly not anything you've introduced here, of\ncourse.  I just noticed that some custom color.diff settings\nI've used weren't picked up by branch-diff, despite your\nclear intention to respect colors from the config.\n\n-- \nTodd\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nAbandon the search for Truth; settle for a good fantasy.\n\n"},{"id":"346738","messageId":"20180506002532.GS26695@zaya.teonanacatl.net","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805052355190.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Todd Zullinger","fromEmail":"tmz@pobox.com","sentAt":"2018-05-06T00:25:32Z","receivedAt":"2018-05-06T00:25:38Z","isPatch":true,"sender":{"key":"tmz@pobox.com","avatar":"https://avatars.githubusercontent.com/u/806319?v=4"},"body":"Hi Johannes,\n\nJohannes Schindelin wrote:\n> On Sat, 5 May 2018, Jeff King wrote:\n>> One minor point about the name: will it become annoying as a tab\n>> completion conflict with git-branch?\n> \n> I did mention this in the commit message of 18/18:\n> \n>     Without this patch, we would only complete the `branch-diff` part but\n>     not the options and other arguments.\n> \n>     This of itself may already be slightly disruptive for well-trained\n>     fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to\n>     `git branch origin/master`, as we now no longer automatically append a\n>     space after completing `git branch`: this is now ambiguous.\n> \n>> It feels really petty complaining about the name, but I just want to\n>> raise the point, since it will never be easier to change than right now.\n> \n> I do hear you. Especially since I hate `git cherry` every single time that\n> I try to tab-complete `git cherry-pick`.\n> \n>> (And no, I don't really have another name in mind; I'm just wondering if\n>> \"subset\" names like this might be a mild annoyance in the long run).\n> \n> They totally are, and if you can come up with a better name, I am really\n> interested in changing it before this hits `next`, even.\n\nWould it be possible and reasonable to teach 'git branch' to\ncall this as a subcommand, i.e. as 'git branch diff'?  Then\nthe completion wouldn't offer git branch-diff.\n\nUsers could still call it directly if they wanted, though\nI'd tend to think that should be discouraged and have it\ntreated as an implementation detail that it's a separate\nbinary.\n\nWe have a number of commands which take subcommands this way\n(bundle, bisect, notes, submodule, and stash come to mind).\nI don't know if any are used with and without a subcommand,\nbut it doesn't seem too strange from a UI point of view, to\nme.\n\n(I don't know if it's coincidental that of the existing\ncommands I noted above, 3 of the 5 are currently implemented\nas shell scripts.  But they've all seen at least some work\ntoward converting them to C, I believe).\n\nThe idea might be gross and/or unreasonable from an\nimplementation or UI view.  I'm not sure, but I thought I\nwould toss the idea out.\n\nThis wouldn't work for git cherry{,-pick} where you wouldn't\nconsider 'git cherry pick' as related to 'git cherry'\nthough.\n\nWe also have this with git show{,-branch} and some others.\nIt's a mild annoyance, but muscle memory adapts eventually.\n\n-- \nTodd\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nA budget is just a method of worrying before you spend money, as well\nas afterward.\n\n"},{"id":"346739","messageId":"20180506003842.GT26695@zaya.teonanacatl.net","threadId":"48405","inReplyTo":"20180506002532.GS26695@zaya.teonanacatl.net","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Todd Zullinger","fromEmail":"tmz@pobox.com","sentAt":"2018-05-06T00:38:42Z","receivedAt":"2018-05-06T00:38:48Z","isPatch":true,"sender":{"key":"tmz@pobox.com","avatar":"https://avatars.githubusercontent.com/u/806319?v=4"},"body":"I wrote:\n> Would it be possible and reasonable to teach 'git branch' to\n> call this as a subcommand, i.e. as 'git branch diff'?  Then\n> the completion wouldn't offer git branch-diff.\n\nOf course right after I sent this, it occurred to me that\n'git branch diff' would make mask the ability to create a\nbranch named diff.  Using 'git branch --diff ...' wouldn't\nsuffer that problem.\n\nIt does add a bit more overhead to the 'git branch' command,\nin terms of documentation and usage.  I'm not sure it's too\nmuch though.  The git-branch summary wouldn't change much:\n\n-git-branch - List, create, or delete branches\n+git-branch - List, create, delete, or diff branches\n\nI hesitate to hit send again, in case I'm once again\noverlooking a glaringly obvious problem with this idea. ;)\n\n-- \nTodd\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nQuick to judge, quick to anger, slow to understand.\nIgnorance and prejudice and fear walk hand in hand.\n\n"},{"id":"346740","messageId":"39282590-576f-1ac1-6a16-80ad317ec7ed@gmail.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805052355190.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Igor Djordjevic","fromEmail":"igor.d.djordjevic@gmail.com","sentAt":"2018-05-06T01:05:06Z","receivedAt":"2018-05-06T01:05:22Z","isPatch":true,"sender":{"key":"igor.d.djordjevic@gmail.com","avatar":null},"body":"Hi Dscho,\n\nOn 05/05/2018 23:57, Johannes Schindelin wrote:\n> \n> > > This builtin does not do a whole lot so far, apart from showing a\n> > > usage that is oddly similar to that of `git tbdiff`. And for a\n> > > good reason: the next commits will turn `branch-diff` into a\n> > > full-blown replacement for `tbdiff`.\n> >\n> > One minor point about the name: will it become annoying as a tab\n> > completion conflict with git-branch?\n> \n> I did mention this in the commit message of 18/18:\n> \n>     Without this patch, we would only complete the `branch-diff` part but\n>     not the options and other arguments.\n> \n>     This of itself may already be slightly disruptive for well-trained\n>     fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to\n>     `git branch origin/master`, as we now no longer automatically append a\n>     space after completing `git branch`: this is now ambiguous.\n> \n> > It feels really petty complaining about the name, but I just want\n> > to raise the point, since it will never be easier to change than\n> > right now.\n> \n> I do hear you. Especially since I hate `git cherry` every single\n> time that I try to tab-complete `git cherry-pick`.\n> \n> > (And no, I don't really have another name in mind; I'm just\n> > wondering if \"subset\" names like this might be a mild annoyance in\n> > the long run).\n> \n> They totally are, and if you can come up with a better name, I am\n> really interested in changing it before this hits `next`, even.\n\nI gave this just a quick glance so might be I`m missing something \nobvious or otherwise well-known here, bur why not `diff-branch` instead?\n\nFrom user interface perspective, I would (personally) rather expect a \ncommand that does \"diff of branches\" to belong to \"diff family\" of \ncommands (just operating on branches, instead of \"branch\" command \nknowing to \"diff itself\"), and I see we already have `diff-files`, \n`diff-index` and `diff-tree`, for what that`s worth.\n\nHeck, I might even expect something like `git diff --branch ...` to work, \nbut I guess that is yet a different matter :)\n\nThanks, Buga\n"},{"id":"346741","messageId":"217c9c08-696f-5e96-d42f-d428ad1fe0a0@gmail.com","threadId":"48405","inReplyTo":"12d9c7977fdf9cc73c810d2ca31d86a4971cf7f4.1525448066.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 05/18] branch-diff: also show the diff between patches","fromName":"Igor Djordjevic","fromEmail":"igor.d.djordjevic@gmail.com","sentAt":"2018-05-06T01:14:06Z","receivedAt":"2018-05-06T01:14:20Z","isPatch":true,"sender":{"key":"igor.d.djordjevic@gmail.com","avatar":null},"body":"Hi Johannes,\n\nOn 04/05/2018 17:34, Johannes Schindelin wrote:\n> Just like tbdiff, we now show the diff between matching patches. This is\n> a \"diff of two diffs\", so it can be a bit daunting to read for the\n> beginner.\n> \n> And just like tbdiff, we now also accept the `--no-patches` option\n> (which is actually equivalent to the diff option `-s`).\n\nA quick nit - would `--no-patch` (singular form) option name be more \naligned with diff `-s` option it resembles?\n\nThanks, Buga\n"},{"id":"346742","messageId":"xmqqk1shsecd.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805052355190.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-05-06T02:33:06Z","receivedAt":"2018-05-06T02:33:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Hi Peff,\n>\n> On Sat, 5 May 2018, Jeff King wrote:\n>\n>> On Fri, May 04, 2018 at 05:34:32PM +0200, Johannes Schindelin wrote:\n>> \n>> > This builtin does not do a whole lot so far, apart from showing a usage\n>> > that is oddly similar to that of `git tbdiff`. And for a good reason:\n>> > the next commits will turn `branch-diff` into a full-blown replacement\n>> > for `tbdiff`.\n>> \n>> One minor point about the name: will it become annoying as a tab\n>> completion conflict with git-branch?\n\nIf tbdiff were \"Thomas's branch diff\", I would call this jbdiff ;-)\nbut I think the 't' in there stands for \"topic\", not \"Thomas's\".\n\nHow about \"git topic-diff\"?\n"},{"id":"346746","messageId":"CA+P7+xphRqZhwQAutph9RHAYxq=v0Zv9omdaPD3m8oV3KPdRhQ@mail.gmail.com","threadId":"48405","inReplyTo":"39282590-576f-1ac1-6a16-80ad317ec7ed@gmail.com","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2018-05-06T04:53:15Z","receivedAt":"2018-05-06T04:53:41Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Sat, May 5, 2018 at 6:05 PM, Igor Djordjevic\n<igor.d.djordjevic@gmail.com> wrote:\n> Hi Dscho,\n>\n> On 05/05/2018 23:57, Johannes Schindelin wrote:\n>>\n>> > > This builtin does not do a whole lot so far, apart from showing a\n>> > > usage that is oddly similar to that of `git tbdiff`. And for a\n>> > > good reason: the next commits will turn `branch-diff` into a\n>> > > full-blown replacement for `tbdiff`.\n>> >\n>> > One minor point about the name: will it become annoying as a tab\n>> > completion conflict with git-branch?\n>>\n>> I did mention this in the commit message of 18/18:\n>>\n>>     Without this patch, we would only complete the `branch-diff` part but\n>>     not the options and other arguments.\n>>\n>>     This of itself may already be slightly disruptive for well-trained\n>>     fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to\n>>     `git branch origin/master`, as we now no longer automatically append a\n>>     space after completing `git branch`: this is now ambiguous.\n>>\n>> > It feels really petty complaining about the name, but I just want\n>> > to raise the point, since it will never be easier to change than\n>> > right now.\n>>\n>> I do hear you. Especially since I hate `git cherry` every single\n>> time that I try to tab-complete `git cherry-pick`.\n>>\n>> > (And no, I don't really have another name in mind; I'm just\n>> > wondering if \"subset\" names like this might be a mild annoyance in\n>> > the long run).\n>>\n>> They totally are, and if you can come up with a better name, I am\n>> really interested in changing it before this hits `next`, even.\n>\n> I gave this just a quick glance so might be I`m missing something\n> obvious or otherwise well-known here, bur why not `diff-branch` instead?\n>\n> From user interface perspective, I would (personally) rather expect a\n> command that does \"diff of branches\" to belong to \"diff family\" of\n> commands (just operating on branches, instead of \"branch\" command\n> knowing to \"diff itself\"), and I see we already have `diff-files`,\n> `diff-index` and `diff-tree`, for what that`s worth.\n>\n> Heck, I might even expect something like `git diff --branch ...` to work,\n> but I guess that is yet a different matter :)\n>\n> Thanks, Buga\n\nI like diff-branch, though I suppose that also conflicts with diff too.\n\nThanks,\nJake\n"},{"id":"346748","messageId":"xmqqzi1dqrya.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-05-06T05:22:05Z","receivedAt":"2018-05-06T05:22:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> Johannes Schindelin (17):\n>   Add a function to solve least-cost assignment problems\n>   Add a new builtin: branch-diff\n\nPerhaps retitling these to\n\n    hungarian: a function to solve least-cost assignment problems\n    branch-diff: a new builtin to compare iterations of a topic\n\nmay serve as good precedents to changes other people may later make\nto these files.  Especially the second one is already consistent\nwith the several changes that are listed below ;-)\n\n>   branch-diff: first rudimentary implementation\n>   branch-diff: improve the order of the shown commits\n>   branch-diff: also show the diff between patches\n>...\n"},{"id":"346749","messageId":"20180506063543.GA3418@sigill.intra.peff.net","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805060001230.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2 13/18] color: provide inverted colors, too","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-06T06:35:44Z","receivedAt":"2018-05-06T06:35:50Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, May 06, 2018 at 12:03:50AM +0200, Johannes Schindelin wrote:\n\n> > There's a \"reverse\" attribute (which we already parse and support) that\n> > can do this without having to repeat the colors. AFAIK it's well\n> > supported everywhere, but I could be wrong.\n> \n> How would I use that here, though? I need to get the thing via\n> diff_get_color_opt() which takes a parameter of type `enum color_diff`.\n> There is no way I can specify `reverse` here, can I?\n\nMy thinking was that the code would know that coloring the initial \"+\"\nshould combine color.diff.new, along with a new tbdiff-specific config\noption. So the C equivalent of something like this:\n\n  new=$(git config --get-color color.diff.new green)\n  tbdiff=$(git config --get-color color.tbdiff.new reverse)\n  reset=$(git config --get-color color.diff.reset reset)\n\n  echo \"${new}${tbdiff}+${reset}${new}+actual diff content${reset}\"\n\nThen if you set color.diff.new to blue, you'll get a reverse-blue \"+\"\nwithout having to configure anything else.\n\nYou can still override the tbdiff coloring with a totally unrelated\ncolor, since it comes after ${new} (so you could set it to purple or\nsomething if you wanted, though obviously a background or attribute from\n${new} can still leak through if you have one set). The only downside in\nsuch a case is that the color sequence is slightly longer (\"green, no\nblue!\").\n\nYou could also have tbdiff.new and tbdiff.old to allow setting them\nindependently (but they'd both default to \"reverse\").\n\n> > I wonder if that would make configuring this slightly more pleasant,\n> > since it saves the user having to define \"oldinv\" whenever they change\n> > \"old\".\n> \n> I am all for making the configuration more pleasant. So I hope I can make\n> use of the `reverse` thing here, without having to introduce a new enum\n> value.\n\nI think the new enum (and matching config) has some value in case people\nwant to override it.  But if you don't want to, diff_get_color() is\nreally just checking want_color() as a convenience. You could do that,\ntoo:\n\n  const char *reverse = want_color(opt->use_color) ? GIT_COLOR_REVERSE : \"\";\n\nYou'd have to introduce GIT_COLOR_REVERSE. I don't think we have a\nconstant for it yet, but it's \\x[7m.\n\n-Peff\n"},{"id":"346750","messageId":"20180506064104.GB3418@sigill.intra.peff.net","threadId":"48405","inReplyTo":"20180506063543.GA3418@sigill.intra.peff.net","subject":"Re: [PATCH v2 13/18] color: provide inverted colors, too","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-06T06:41:05Z","receivedAt":"2018-05-06T06:41:10Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, May 06, 2018 at 02:35:44AM -0400, Jeff King wrote:\n\n> You'd have to introduce GIT_COLOR_REVERSE. I don't think we have a\n> constant for it yet, but it's \\x[7m.\n\nHeh, of course you knew that already, as I just noticed your patch is\nusing the reverse attribute internally (I had thought at first glance\nyou were just specifying the background independently).\n\nSo really, I guess all I am arguing for is having GIT_COLOR_INV (or\nREVERSE) as a constant, and then teaching the code to combine it with\nthe existing \"new\" color. It's perfectly OK to have:\n\n  \\x1b[7m\\x1b[36m\n\ninstead of:\n\n  \\x1b[7;36m\n\nIt's two extra bytes, but I doubt anybody cares.\n\n-Peff\n"},{"id":"346751","messageId":"20180506082440.GA26958@duynguyen.home","threadId":"48405","inReplyTo":"71698f11835311c103aae565a2a761d10f4676b9.1525448066.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 18/18] completion: support branch-diff","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2018-05-06T08:24:40Z","receivedAt":"2018-05-06T08:24:47Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Fri, May 04, 2018 at 05:35:11PM +0200, Johannes Schindelin wrote:\n> Tab completion of `branch-diff` is very convenient, especially given\n> that the revision arguments that need to be passed to `git branch-diff`\n> are typically more complex than, say, your grandfather's `git log`\n> arguments.\n> \n> Without this patch, we would only complete the `branch-diff` part but\n> not the options and other arguments.\n> \n> This of itself may already be slightly disruptive for well-trained\n> fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to\n> `git branch origin/master`, as we now no longer automatically append a\n> space after completing `git branch`: this is now ambiguous.\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  contrib/completion/git-completion.bash | 18 ++++++++++++++++++\n>  1 file changed, 18 insertions(+)\n> \n> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n> index 01dd9ff07a2..45addd525ac 100644\n> --- a/contrib/completion/git-completion.bash\n> +++ b/contrib/completion/git-completion.bash\n> @@ -1496,6 +1496,24 @@ _git_format_patch ()\n>  \t__git_complete_revlist\n>  }\n>  \n> +__git_branch_diff_options=\"\n> +\t--no-patches --creation-weight= --dual-color\n> +\"\n> +\n> +_git_branch_diff ()\n> +{\n> +\tcase \"$cur\" in\n> +\t--*)\n> +\t\t__gitcomp \"\n\nYou should use __gitcomp_builtin so you don't have to maintain\n$__git_branch_diff_options here. Something like this\n\n-- 8< --\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 45addd525a..4745631daf 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1496,18 +1496,11 @@ _git_format_patch ()\n \t__git_complete_revlist\n }\n \n-__git_branch_diff_options=\"\n-\t--no-patches --creation-weight= --dual-color\n-\"\n-\n _git_branch_diff ()\n {\n \tcase \"$cur\" in\n \t--*)\n-\t\t__gitcomp \"\n-\t\t\t$__git_branch_diff_options\n-\t\t\t$__git_diff_common_options\n-\t\t\t\"\n+\t\t__gitcomp_builtin branch-diff \"$__git_diff_common_options\"\n \t\treturn\n \t\t;;\n \tesac\n-- 8< --\n\n\n> +\t\t\t$__git_branch_diff_options\n> +\t\t\t$__git_diff_common_options\n> +\t\t\t\"\n> +\t\treturn\n> +\t\t;;\n> +\tesac\n> +\t__git_complete_revlist\n> +}\n> +\n>  _git_fsck ()\n>  {\n>  \tcase \"$cur\" in\n> -- \n> 2.17.0.409.g71698f11835\n"},{"id":"346752","messageId":"CACsJy8D0HYZFAWWn-8XEk63QohBKBE8Gx4=39Cp9_FMmbtJDew@mail.gmail.com","threadId":"48405","inReplyTo":"CA+P7+xphRqZhwQAutph9RHAYxq=v0Zv9omdaPD3m8oV3KPdRhQ@mail.gmail.com","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2018-05-06T08:32:46Z","receivedAt":"2018-05-06T08:33:27Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sun, May 6, 2018 at 6:53 AM, Jacob Keller <jacob.keller@gmail.com> wrote:\n> On Sat, May 5, 2018 at 6:05 PM, Igor Djordjevic\n> <igor.d.djordjevic@gmail.com> wrote:\n>> Hi Dscho,\n>>\n>> On 05/05/2018 23:57, Johannes Schindelin wrote:\n>>>\n>>> > > This builtin does not do a whole lot so far, apart from showing a\n>>> > > usage that is oddly similar to that of `git tbdiff`. And for a\n>>> > > good reason: the next commits will turn `branch-diff` into a\n>>> > > full-blown replacement for `tbdiff`.\n>>> >\n>>> > One minor point about the name: will it become annoying as a tab\n>>> > completion conflict with git-branch?\n>>>\n>>> I did mention this in the commit message of 18/18:\n>>>\n>>>     Without this patch, we would only complete the `branch-diff` part but\n>>>     not the options and other arguments.\n>>>\n>>>     This of itself may already be slightly disruptive for well-trained\n>>>     fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to\n>>>     `git branch origin/master`, as we now no longer automatically append a\n>>>     space after completing `git branch`: this is now ambiguous.\n>>>\n>>> > It feels really petty complaining about the name, but I just want\n>>> > to raise the point, since it will never be easier to change than\n>>> > right now.\n>>>\n>>> I do hear you. Especially since I hate `git cherry` every single\n>>> time that I try to tab-complete `git cherry-pick`.\n>>>\n>>> > (And no, I don't really have another name in mind; I'm just\n>>> > wondering if \"subset\" names like this might be a mild annoyance in\n>>> > the long run).\n>>>\n>>> They totally are, and if you can come up with a better name, I am\n>>> really interested in changing it before this hits `next`, even.\n>>\n>> I gave this just a quick glance so might be I`m missing something\n>> obvious or otherwise well-known here, bur why not `diff-branch` instead?\n>>\n>> From user interface perspective, I would (personally) rather expect a\n>> command that does \"diff of branches\" to belong to \"diff family\" of\n>> commands (just operating on branches, instead of \"branch\" command\n>> knowing to \"diff itself\"), and I see we already have `diff-files`,\n>> `diff-index` and `diff-tree`, for what that`s worth.\n>>\n>> Heck, I might even expect something like `git diff --branch ...` to work,\n>> but I guess that is yet a different matter :)\n>>\n>> Thanks, Buga\n>\n> I like diff-branch, though I suppose that also conflicts with diff too.\n\nHow about interdiff?\n\n-- \nDuy\n"},{"id":"346753","messageId":"nycvar.QRO.7.76.6.1805061401260.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180506003842.GT26695@zaya.teonanacatl.net","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-06T12:04:50Z","receivedAt":"2018-05-06T12:05:12Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Todd,\n\nOn Sat, 5 May 2018, Todd Zullinger wrote:\n\n> I wrote:\n> > Would it be possible and reasonable to teach 'git branch' to\n> > call this as a subcommand, i.e. as 'git branch diff'?  Then\n> > the completion wouldn't offer git branch-diff.\n> \n> Of course right after I sent this, it occurred to me that\n> 'git branch diff' would make mask the ability to create a\n> branch named diff.  Using 'git branch --diff ...' wouldn't\n> suffer that problem.\n\nYep, I immediately thought of --diff instead of diff when I read your\nprevious mail on that matter. And I like this idea!\n\nOf course, it will complicate the code to set up the pager a bit (for\n`branch-diff`, I could default to \"on\" all the time). But IIRC we recently\nchanged the --list cmdmode to set the pager to \"auto\", so I'll just copy\nthat.\n\n> It does add a bit more overhead to the 'git branch' command,\n> in terms of documentation and usage.  I'm not sure it's too\n> much though.  The git-branch summary wouldn't change much:\n> \n> -git-branch - List, create, or delete branches\n> +git-branch - List, create, delete, or diff branches\n\nIndeed.\n\nUnless I hear objections, I will work on moving to `git branch --diff` (it\nmight take a while, though, I will be traveling for work this week).\n\nCiao,\nJohannes\n"},{"id":"346754","messageId":"nycvar.QRO.7.76.6.1805061405350.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CACsJy8D0HYZFAWWn-8XEk63QohBKBE8Gx4=39Cp9_FMmbtJDew@mail.gmail.com","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-06T12:08:01Z","receivedAt":"2018-05-06T12:08:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Duy,\n\nOn Sun, 6 May 2018, Duy Nguyen wrote:\n\n> On Sun, May 6, 2018 at 6:53 AM, Jacob Keller <jacob.keller@gmail.com> wrote:\n> > On Sat, May 5, 2018 at 6:05 PM, Igor Djordjevic\n> > <igor.d.djordjevic@gmail.com> wrote:\n> >>\n> >> On 05/05/2018 23:57, Johannes Schindelin wrote:\n> >>>\n> >>> > > This builtin does not do a whole lot so far, apart from showing a\n> >>> > > usage that is oddly similar to that of `git tbdiff`. And for a\n> >>> > > good reason: the next commits will turn `branch-diff` into a\n> >>> > > full-blown replacement for `tbdiff`.\n> >>> >\n> >>> > One minor point about the name: will it become annoying as a tab\n> >>> > completion conflict with git-branch?\n> >>>\n> >>> I did mention this in the commit message of 18/18:\n> >>>\n> >>>     Without this patch, we would only complete the `branch-diff` part but\n> >>>     not the options and other arguments.\n> >>>\n> >>>     This of itself may already be slightly disruptive for well-trained\n> >>>     fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to\n> >>>     `git branch origin/master`, as we now no longer automatically append a\n> >>>     space after completing `git branch`: this is now ambiguous.\n> >>>\n> >>> > It feels really petty complaining about the name, but I just want\n> >>> > to raise the point, since it will never be easier to change than\n> >>> > right now.\n> >>>\n> >>> I do hear you. Especially since I hate `git cherry` every single\n> >>> time that I try to tab-complete `git cherry-pick`.\n> >>>\n> >>> > (And no, I don't really have another name in mind; I'm just\n> >>> > wondering if \"subset\" names like this might be a mild annoyance in\n> >>> > the long run).\n> >>>\n> >>> They totally are, and if you can come up with a better name, I am\n> >>> really interested in changing it before this hits `next`, even.\n> >>\n> >> I gave this just a quick glance so might be I`m missing something\n> >> obvious or otherwise well-known here, bur why not `diff-branch` instead?\n> >>\n> >> From user interface perspective, I would (personally) rather expect a\n> >> command that does \"diff of branches\" to belong to \"diff family\" of\n> >> commands (just operating on branches, instead of \"branch\" command\n> >> knowing to \"diff itself\"), and I see we already have `diff-files`,\n> >> `diff-index` and `diff-tree`, for what that`s worth.\n> >>\n> >> Heck, I might even expect something like `git diff --branch ...` to work,\n> >> but I guess that is yet a different matter :)\n> >>\n> >> Thanks, Buga\n> >\n> > I like diff-branch, though I suppose that also conflicts with diff too.\n> \n> How about interdiff?\n\nNo. An interdiff is well defined as the diff you would get by first\napplying the first of two patches in reverse and then the second patch\nforward. In other words, it turns two revisions of a patch into the diff\nbetween the result of applying both revisions.\n\nI tried very hard to avoid using that term in my patch series (tbdiff used\nthe term incorrectly: what it called an interdiff is a diff of two\npatches, where a patch is an author line followed by the commit message\nfollowed by the commit diff).\n\nCiao,\nDscho\n"},{"id":"346755","messageId":"nycvar.QRO.7.76.6.1805061408150.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"39282590-576f-1ac1-6a16-80ad317ec7ed@gmail.com","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-06T12:10:55Z","receivedAt":"2018-05-06T12:11:10Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Buga,\n\nOn Sun, 6 May 2018, Igor Djordjevic wrote:\n\n> On 05/05/2018 23:57, Johannes Schindelin wrote:\n> > \n> > > > This builtin does not do a whole lot so far, apart from showing a\n> > > > usage that is oddly similar to that of `git tbdiff`. And for a\n> > > > good reason: the next commits will turn `branch-diff` into a\n> > > > full-blown replacement for `tbdiff`.\n> > >\n> > > One minor point about the name: will it become annoying as a tab\n> > > completion conflict with git-branch?\n> > \n> > I did mention this in the commit message of 18/18:\n> > \n> >     Without this patch, we would only complete the `branch-diff` part but\n> >     not the options and other arguments.\n> > \n> >     This of itself may already be slightly disruptive for well-trained\n> >     fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to\n> >     `git branch origin/master`, as we now no longer automatically append a\n> >     space after completing `git branch`: this is now ambiguous.\n> > \n> > > It feels really petty complaining about the name, but I just want\n> > > to raise the point, since it will never be easier to change than\n> > > right now.\n> > \n> > I do hear you. Especially since I hate `git cherry` every single\n> > time that I try to tab-complete `git cherry-pick`.\n> > \n> > > (And no, I don't really have another name in mind; I'm just\n> > > wondering if \"subset\" names like this might be a mild annoyance in\n> > > the long run).\n> > \n> > They totally are, and if you can come up with a better name, I am\n> > really interested in changing it before this hits `next`, even.\n> \n> I gave this just a quick glance so might be I`m missing something \n> obvious or otherwise well-known here, bur why not `diff-branch` instead?\n\nI think that is just turning the problem from `branch` to `diff`.\n\nOf course, we have precedent with diff-index and diff-files. Except that\nthey don't auto-complete (because they are low-level commands) and I\n*would* like the subcommand discussed in this here patch series to\nauto-complete.\n\nI think Todd's idea to shift it from a full-blown builtin to a cmdmode\nof `branch` makes tons of sense.\n\nCiao,\nDscho\n"},{"id":"346756","messageId":"nycvar.QRO.7.76.6.1805061411260.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"217c9c08-696f-5e96-d42f-d428ad1fe0a0@gmail.com","subject":"Re: [PATCH v2 05/18] branch-diff: also show the diff between patches","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-06T12:18:47Z","receivedAt":"2018-05-06T12:18:59Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Buga,\n\nOn Sun, 6 May 2018, Igor Djordjevic wrote:\n\n> On 04/05/2018 17:34, Johannes Schindelin wrote:\n> > Just like tbdiff, we now show the diff between matching patches. This is\n> > a \"diff of two diffs\", so it can be a bit daunting to read for the\n> > beginner.\n> > \n> > And just like tbdiff, we now also accept the `--no-patches` option\n> > (which is actually equivalent to the diff option `-s`).\n> \n> A quick nit - would `--no-patch` (singular form) option name be more \n> aligned with diff `-s` option it resembles?\n\nThe reason I used `--no-patches` is that tbdiff called it that way.\n\nBut you're right, the functionality is already available via -s, and we\n*do* make this a distinct thing from tbdiff. So I'll simply drop support\nfor --no-patches.\n\nCiao,\nDscho\n"},{"id":"346757","messageId":"nycvar.QRO.7.76.6.1805061419530.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"xmqqk1shsecd.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-06T12:21:54Z","receivedAt":"2018-05-06T12:22:08Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Sun, 6 May 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > On Sat, 5 May 2018, Jeff King wrote:\n> >\n> >> On Fri, May 04, 2018 at 05:34:32PM +0200, Johannes Schindelin wrote:\n> >> \n> >> > This builtin does not do a whole lot so far, apart from showing a usage\n> >> > that is oddly similar to that of `git tbdiff`. And for a good reason:\n> >> > the next commits will turn `branch-diff` into a full-blown replacement\n> >> > for `tbdiff`.\n> >> \n> >> One minor point about the name: will it become annoying as a tab\n> >> completion conflict with git-branch?\n> \n> If tbdiff were \"Thomas's branch diff\", I would call this jbdiff ;-)\n> but I think the 't' in there stands for \"topic\", not \"Thomas's\".\n> \n> How about \"git topic-diff\"?\n\nOr `git topic-branch-diff`?\n\nBut then, we do not really use the term `topic branch` a lot in Git, *and*\nthe operation in question is not really about showing differences between\ntopic branches, but between revisions of topic branches.\n\nSo far, the solution I like best is to use `git branch --diff <...>`,\nwhich also neatly side-steps the problem of cluttering the top-level\ncommand list (because tab completion).\n\nCiao,\nDscho\n"},{"id":"346758","messageId":"nycvar.QRO.7.76.6.1805061423050.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"xmqqzi1dqrya.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-06T12:23:44Z","receivedAt":"2018-05-06T12:23:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Sun, 6 May 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> > Johannes Schindelin (17):\n> >   Add a function to solve least-cost assignment problems\n> >   Add a new builtin: branch-diff\n> \n> Perhaps retitling these to\n> \n>     hungarian: a function to solve least-cost assignment problems\n>     branch-diff: a new builtin to compare iterations of a topic\n> \n> may serve as good precedents to changes other people may later make\n> to these files.  Especially the second one is already consistent\n> with the several changes that are listed below ;-)\n\nI like it! They are retitled locally, in preparation for whenever I send\nout the next iteration.\n\nCiao,\nDscho\n"},{"id":"346761","messageId":"e0db15c5-e897-5b03-20ff-d83f38496e61@gmail.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805061408150.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Igor Djordjevic","fromEmail":"igor.d.djordjevic@gmail.com","sentAt":"2018-05-06T13:37:15Z","receivedAt":"2018-05-06T13:37:32Z","isPatch":true,"sender":{"key":"igor.d.djordjevic@gmail.com","avatar":null},"body":"Hi Dscho,\n\nOn 06/05/2018 14:10, Johannes Schindelin wrote:\n> \n> > > > > This builtin does not do a whole lot so far, apart from showing a\n> > > > > usage that is oddly similar to that of `git tbdiff`. And for a\n> > > > > good reason: the next commits will turn `branch-diff` into a\n> > > > > full-blown replacement for `tbdiff`.\n> > > >\n> > > > One minor point about the name: will it become annoying as a tab\n> > > > completion conflict with git-branch?\n> > >\n> > > I did mention this in the commit message of 18/18:\n> > >\n> > >     Without this patch, we would only complete the `branch-diff` part but\n> > >     not the options and other arguments.\n> > >\n> > >     This of itself may already be slightly disruptive for well-trained\n> > >     fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to\n> > >     `git branch origin/master`, as we now no longer automatically append a\n> > >     space after completing `git branch`: this is now ambiguous.\n> > >\n> > > > It feels really petty complaining about the name, but I just want\n> > > > to raise the point, since it will never be easier to change than\n> > > > right now.\n> > >\n> > > I do hear you. Especially since I hate `git cherry` every single\n> > > time that I try to tab-complete `git cherry-pick`.\n> > >\n> > > > (And no, I don't really have another name in mind; I'm just\n> > > > wondering if \"subset\" names like this might be a mild annoyance in\n> > > > the long run).\n> > >\n> > > They totally are, and if you can come up with a better name, I am\n> > > really interested in changing it before this hits `next`, even.\n> >\n> > I gave this just a quick glance so might be I`m missing something \n> > obvious or otherwise well-known here, bur why not `diff-branch` instead?\n> \n> I think that is just turning the problem from `branch` to `diff`.\n> \n> Of course, we have precedent with diff-index and diff-files. Except that\n> they don't auto-complete (because they are low-level commands) and I\n> *would* like the subcommand discussed in this here patch series to\n> auto-complete.\n\nYeah, I did suspect it might be something like this (those other ones \nnot auto-completing, where we do want it here), thanks for elaborating.\n\n> I think Todd's idea to shift it from a full-blown builtin to a cmdmode\n> of `branch` makes tons of sense.\n\nI don`t know, I still find it a bit strange that in order to \"diff \nsomething\", you go to \"something\" and tell it to \"diff itself\" - not \nbecause it`s a weird concept (OOP, anyone? :]), but because we \nalready have \"diff\" command that can accept different things, thus \njust teaching it to accept additional \"something\" (branch, in this \ncase), seems more natural (to me) - \"branch diff\" being just another \n\"diff\" mode of operation.\n\nWhat about that side thought you left out from my original message, \nmaking it `git diff --branch` instead?\n\nBut if \"branch diff\" is considered to be too special-cased mode of \n\"diff\" so that supporting it from `diff` itself would make it feel \nawkward in both usage and maintenance (in terms of many other regular \n`diff` specific options being unsupported), I guess I would understand \nhaving it outside `diff` altogether (and implemented as proposed `git \nbranch --diff`, or something)... for the time being, at least :)\n\nRegards, Buga\n"},{"id":"346768","messageId":"CAN0heSoLD0O9owCDEU5ZHje3zNDLAS_43atb75Te7KOFoS_dtA@mail.gmail.com","threadId":"48405","inReplyTo":"c856c460a47dbe885bbb82babc6be6848d31ed32.1525448066.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 07/18] branch-diff: indent the diffs just like tbdiff","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2018-05-06T14:15:23Z","receivedAt":"2018-05-06T14:15:27Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"On 4 May 2018 at 17:34, Johannes Schindelin <johannes.schindelin@gmx.de> wrote:\n> @@ -353,6 +358,7 @@ static void output(struct string_list *a, struct string_list *b,\n>  int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n>  {\n>         struct diff_options diffopt = { NULL };\n> +       struct strbuf four_spaces = STRBUF_INIT;\n>         double creation_weight = 0.6;\n>         struct option options[] = {\n>                 OPT_SET_INT(0, \"no-patches\", &diffopt.output_format,\n> @@ -371,6 +377,9 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n>\n>         diff_setup(&diffopt);\n>         diffopt.output_format = DIFF_FORMAT_PATCH;\n> +       diffopt.output_prefix = output_prefix_cb;\n> +       strbuf_addstr(&four_spaces, \"    \");\n> +       diffopt.output_prefix_data = &four_spaces;\n>\n>         argc = parse_options(argc, argv, NULL, options,\n>                         builtin_branch_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n\nYou end up leaking the buffer of `four_spaces`. Granted, that's not a\nbig memory leak, but still. ;-) This was the only leak that\nLeakSanitizer found in v2 when running the new test-script and playing\naround with this a bit. This looks really good!\n\nMartin\n"},{"id":"351587","messageId":"935cad180184787301975f18f56add409aa445f5.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 05/20] range-diff: also show the diff between patches","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-05-06T15:26:19Z","receivedAt":"2018-05-06T15:26:19Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nJust like tbdiff, we now show the diff between matching patches. This is\na \"diff of two diffs\", so it can be a bit daunting to read for the\nbeginner.\n\nAn alternative would be to display an interdiff, i.e. the hypothetical\ndiff which is the result of first reverting the old diff and then\napplying the new diff.\n\nEspecially when rebasing often, an interdiff is often not feasible,\nthough: if the old diff cannot be applied in reverse (due to a moving\nupstream), an interdiff can simply not be inferred.\n\nThis commit brings `range-diff` closer to feature parity with regard\nto tbdiff.\n\nTo make `git range-diff` respect e.g. color.diff.* settings, we have\nto adjust git_branch_config() accordingly.\n\nNote: while we now parse diff options such as --color, the effect is not\nyet the same as in tbdiff, where also the commit pairs would be colored.\nThis is left for a later commit.\n\nNote also: while tbdiff accepts the `--no-patches` option to suppress\nthese diffs between patches, we prefer the `-s` option that is\nautomatically supported via our use of diff_opt_parse().\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/range-diff.c | 25 +++++++++++++++++++++----\n range-diff.c         | 34 +++++++++++++++++++++++++++++++---\n range-diff.h         |  4 +++-\n 3 files changed, 55 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex c37a72100..5f12bbfa9 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -2,6 +2,7 @@\n #include \"builtin.h\"\n #include \"parse-options.h\"\n #include \"range-diff.h\"\n+#include \"config.h\"\n \n static const char * const builtin_range_diff_usage[] = {\n N_(\"git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n@@ -13,16 +14,31 @@ NULL\n int cmd_range_diff(int argc, const char **argv, const char *prefix)\n {\n \tint creation_factor = 60;\n+\tstruct diff_options diffopt = { NULL };\n \tstruct option options[] = {\n \t\tOPT_INTEGER(0, \"creation-factor\", &creation_factor,\n \t\t\t    N_(\"Percentage by which creation is weighted\")),\n \t\tOPT_END()\n \t};\n-\tint res = 0;\n+\tint i, j, res = 0;\n \tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n \n-\targc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,\n-\t\t\t     0);\n+\tgit_config(git_diff_ui_config, NULL);\n+\n+\tdiff_setup(&diffopt);\n+\tdiffopt.output_format = DIFF_FORMAT_PATCH;\n+\n+\targc = parse_options(argc, argv, NULL, options,\n+\t\t\tbuiltin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n+\n+\tfor (i = j = 0; i < argc; i++) {\n+\t\tint c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);\n+\n+\t\tif (!c)\n+\t\t\targv[j++] = argv[i];\n+\t}\n+\targc = j;\n+\tdiff_setup_done(&diffopt);\n \n \tif (argc == 2) {\n \t\tif (!strstr(argv[0], \"..\"))\n@@ -57,7 +73,8 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(builtin_range_diff_usage, options);\n \t}\n \n-\tres = show_range_diff(range1.buf, range2.buf, creation_factor);\n+\tres = show_range_diff(range1.buf, range2.buf, creation_factor,\n+\t\t\t      &diffopt);\n \n \tstrbuf_release(&range1);\n \tstrbuf_release(&range2);\ndiff --git a/range-diff.c b/range-diff.c\nindex e71cf0ba7..530f2fc32 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -6,6 +6,7 @@\n #include \"hashmap.h\"\n #include \"xdiff-interface.h\"\n #include \"linear-assignment.h\"\n+#include \"diffcore.h\"\n \n struct patch_util {\n \t/* For the search for an exact match */\n@@ -254,7 +255,31 @@ static const char *short_oid(struct patch_util *util)\n \treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n }\n \n-static void output(struct string_list *a, struct string_list *b)\n+static struct diff_filespec *get_filespec(const char *name, const char *p)\n+{\n+\tstruct diff_filespec *spec = alloc_filespec(name);\n+\n+\tfill_filespec(spec, &null_oid, 0, 0644);\n+\tspec->data = (char *)p;\n+\tspec->size = strlen(p);\n+\tspec->should_munmap = 0;\n+\tspec->is_stdin = 1;\n+\n+\treturn spec;\n+}\n+\n+static void patch_diff(const char *a, const char *b,\n+\t\t\t      struct diff_options *diffopt)\n+{\n+\tdiff_queue(&diff_queued_diff,\n+\t\t   get_filespec(\"a\", a), get_filespec(\"b\", b));\n+\n+\tdiffcore_std(diffopt);\n+\tdiff_flush(diffopt);\n+}\n+\n+static void output(struct string_list *a, struct string_list *b,\n+\t\t   struct diff_options *diffopt)\n {\n \tint i = 0, j = 0;\n \n@@ -296,6 +321,9 @@ static void output(struct string_list *a, struct string_list *b)\n \t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n \t\t\t       b_util->matching + 1, short_oid(a_util),\n \t\t\t       j + 1, short_oid(b_util));\n+\t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n+\t\t\t\tpatch_diff(a->items[b_util->matching].string,\n+\t\t\t\t\t   b->items[j].string, diffopt);\n \t\t\ta_util->shown = 1;\n \t\t\tj++;\n \t\t}\n@@ -303,7 +331,7 @@ static void output(struct string_list *a, struct string_list *b)\n }\n \n int show_range_diff(const char *range1, const char *range2,\n-\t\t    int creation_factor)\n+\t\t    int creation_factor, struct diff_options *diffopt)\n {\n \tint res = 0;\n \n@@ -318,7 +346,7 @@ int show_range_diff(const char *range1, const char *range2,\n \tif (!res) {\n \t\tfind_exact_matches(&branch1, &branch2);\n \t\tget_correspondences(&branch1, &branch2, creation_factor);\n-\t\toutput(&branch1, &branch2);\n+\t\toutput(&branch1, &branch2, diffopt);\n \t}\n \n \tstring_list_clear(&branch1, 1);\ndiff --git a/range-diff.h b/range-diff.h\nindex dd30449c4..aea9d43f3 100644\n--- a/range-diff.h\n+++ b/range-diff.h\n@@ -1,7 +1,9 @@\n #ifndef BRANCH_DIFF_H\n #define BRANCH_DIFF_H\n \n+#include \"diff.h\"\n+\n int show_range_diff(const char *range1, const char *range2,\n-\t\t    int creation_factor);\n+\t\t    int creation_factor, struct diff_options *diffopt);\n \n #endif\n-- \ngitgitgadget\n\n"},{"id":"351589","messageId":"ef997bb8b372c614a832bb84d456237e2b0c2294.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 10/20] range-diff: do not show \"function names\" in hunk headers","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-05-06T15:35:03Z","receivedAt":"2018-05-06T15:35:03Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWe are comparing complete, formatted commit messages with patches. There\nare no function names here, so stop looking for them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 0e0e77106..8df73da4e 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -9,6 +9,7 @@\n #include \"diffcore.h\"\n #include \"commit.h\"\n #include \"pretty.h\"\n+#include \"userdiff.h\"\n \n struct patch_util {\n \t/* For the search for an exact match */\n@@ -306,6 +307,10 @@ static void output_pair_header(struct strbuf *buf,\n \tfwrite(buf->buf, buf->len, 1, stdout);\n }\n \n+static struct userdiff_driver no_func_name = {\n+\t.funcname = { \"$^\", 0 }\n+};\n+\n static struct diff_filespec *get_filespec(const char *name, const char *p)\n {\n \tstruct diff_filespec *spec = alloc_filespec(name);\n@@ -315,6 +320,7 @@ static struct diff_filespec *get_filespec(const char *name, const char *p)\n \tspec->size = strlen(p);\n \tspec->should_munmap = 0;\n \tspec->is_stdin = 1;\n+\tspec->driver = &no_func_name;\n \n \treturn spec;\n }\n-- \ngitgitgadget\n\n"},{"id":"346786","messageId":"CAPig+cS0pvdg78fGUu8m2xspDDMHxi=uAMCkbLuthy7R4p3fQw@mail.gmail.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805061419530.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-05-06T20:51:19Z","receivedAt":"2018-05-06T20:51:23Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, May 6, 2018 at 8:21 AM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> On Sun, 6 May 2018, Junio C Hamano wrote:\n>> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>> > On Sat, 5 May 2018, Jeff King wrote:\n>> >> One minor point about the name: will it become annoying as a tab\n>> >> completion conflict with git-branch?\n>>\n>> If tbdiff were \"Thomas's branch diff\", I would call this jbdiff ;-)\n>> but I think the 't' in there stands for \"topic\", not \"Thomas's\".\n>> How about \"git topic-diff\"?\n>\n> Or `git topic-branch-diff`?\n>\n> But then, we do not really use the term `topic branch` a lot in Git, *and*\n> the operation in question is not really about showing differences between\n> topic branches, but between revisions of topic branches.\n>\n> So far, the solution I like best is to use `git branch --diff <...>`,\n> which also neatly side-steps the problem of cluttering the top-level\n> command list (because tab completion).\n\nLet's, please, not fall into the trap of polluting git-branch with\nutterly unrelated functionality, as has happened a few times with\nother Git commands. Let's especially not do so merely for the sake of\ntab-completion. git-branch is for branch management; it's not for\ndiff'ing.\n\nOf the suggestions thus far, Junio's git-topic-diff seems the least\nworse, and doesn't suffer from tab-completion problems.\n\nBuilding on Duy's suggestion: git-interdiff could be a superset of the\ncurrent git-branch-diff:\n\n    # standard interdiff\n    git interdiff womp-v1 womp-v2\n    # 'tbdiff'-like output\n    git interdiff --topic womp-v1 womp-v2\n\n(Substitute \"--topic\" by any other better name.)\n"},{"id":"346788","messageId":"20180506225627.GB953644@genre.crustytoothpaste.net","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2018-05-06T22:56:27Z","receivedAt":"2018-05-06T22:56:38Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On Fri, May 04, 2018 at 05:34:27PM +0200, Johannes Schindelin wrote:\n> The incredibly useful `git-tbdiff` tool to compare patch series (say, to see\n> what changed between two iterations sent to the Git mailing list) is slightly\n> less useful for this developer due to the fact that it requires the `hungarian`\n> and `numpy` Python packages which are for some reason really hard to build in\n> MSYS2. So hard that I even had to give up, because it was simply easier to\n> reimplement the whole shebang as a builtin command.\n\nI just want to say thanks for writing this.  I use tbdiff extensively at\nwork and having this built-in and much faster will really help.\n\nI did a once-over of v1 and I'll probably take a look at v2 or v3\n(whatever's the latest) later in the week.\n-- \nbrian m. carlson: Houston, Texas, US\nOpenPGP: https://keybase.io/bk2204\n"},{"id":"346826","messageId":"nycvar.QRO.7.76.6.1805062119051.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180506064104.GB3418@sigill.intra.peff.net","subject":"Re: [PATCH v2 13/18] color: provide inverted colors, too","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-07T01:20:46Z","receivedAt":"2018-05-07T01:21:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Sun, 6 May 2018, Jeff King wrote:\n\n> On Sun, May 06, 2018 at 02:35:44AM -0400, Jeff King wrote:\n> \n> > You'd have to introduce GIT_COLOR_REVERSE. I don't think we have a\n> > constant for it yet, but it's \\x[7m.\n> \n> Heh, of course you knew that already, as I just noticed your patch is\n> using the reverse attribute internally (I had thought at first glance\n> you were just specifying the background independently).\n> \n> So really, I guess all I am arguing for is having GIT_COLOR_INV (or\n> REVERSE) as a constant, and then teaching the code to combine it with\n> the existing \"new\" color. It's perfectly OK to have:\n> \n>   \\x1b[7m\\x1b[36m\n> \n> instead of:\n> \n>   \\x1b[7;36m\n> \n> It's two extra bytes, but I doubt anybody cares.\n\nYep, I agree that it is a small price to pay for the benefit of simply\nusing the reverse of diff.color.old (and .new).\n\nWhile at it, I also changed the hunk header colors: they are *also* simply\nthe same ones, with the outer one having background and foreground\nreversed.\n\nCiao,\nDscho\n"},{"id":"346827","messageId":"nycvar.QRO.7.76.6.1805062122150.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180506082440.GA26958@duynguyen.home","subject":"Re: [PATCH v2 18/18] completion: support branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-07T01:23:20Z","receivedAt":"2018-05-07T01:23:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Duy,\n\nOn Sun, 6 May 2018, Duy Nguyen wrote:\n\n> On Fri, May 04, 2018 at 05:35:11PM +0200, Johannes Schindelin wrote:\n> > Tab completion of `branch-diff` is very convenient, especially given\n> > that the revision arguments that need to be passed to `git branch-diff`\n> > are typically more complex than, say, your grandfather's `git log`\n> > arguments.\n> > \n> > Without this patch, we would only complete the `branch-diff` part but\n> > not the options and other arguments.\n> > \n> > This of itself may already be slightly disruptive for well-trained\n> > fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to\n> > `git branch origin/master`, as we now no longer automatically append a\n> > space after completing `git branch`: this is now ambiguous.\n> > \n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> >  contrib/completion/git-completion.bash | 18 ++++++++++++++++++\n> >  1 file changed, 18 insertions(+)\n> > \n> > diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n> > index 01dd9ff07a2..45addd525ac 100644\n> > --- a/contrib/completion/git-completion.bash\n> > +++ b/contrib/completion/git-completion.bash\n> > @@ -1496,6 +1496,24 @@ _git_format_patch ()\n> >  \t__git_complete_revlist\n> >  }\n> >  \n> > +__git_branch_diff_options=\"\n> > +\t--no-patches --creation-weight= --dual-color\n> > +\"\n> > +\n> > +_git_branch_diff ()\n> > +{\n> > +\tcase \"$cur\" in\n> > +\t--*)\n> > +\t\t__gitcomp \"\n> \n> You should use __gitcomp_builtin so you don't have to maintain\n> $__git_branch_diff_options here. Something like this\n> \n> -- 8< --\n> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n> index 45addd525a..4745631daf 100644\n> --- a/contrib/completion/git-completion.bash\n> +++ b/contrib/completion/git-completion.bash\n> @@ -1496,18 +1496,11 @@ _git_format_patch ()\n>  \t__git_complete_revlist\n>  }\n>  \n> -__git_branch_diff_options=\"\n> -\t--no-patches --creation-weight= --dual-color\n> -\"\n> -\n>  _git_branch_diff ()\n>  {\n>  \tcase \"$cur\" in\n>  \t--*)\n> -\t\t__gitcomp \"\n> -\t\t\t$__git_branch_diff_options\n> -\t\t\t$__git_diff_common_options\n> -\t\t\t\"\n> +\t\t__gitcomp_builtin branch-diff \"$__git_diff_common_options\"\n>  \t\treturn\n>  \t\t;;\n>  \tesac\n> -- 8< --\n\nDoes this really work? I have this instead, for now, and verified that it\nworks:\n\n-- snipsnap --\ndiff --git a/contrib/completion/git-completion.bash\nb/contrib/completion/git-completion.bash\nindex 01dd9ff07a2..c498c053881 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1205,13 +1205,14 @@ _git_bisect ()\n\n _git_branch ()\n {\n-       local i c=1 only_local_ref=\"n\" has_r=\"n\"\n+       local i c=1 only_local_ref=\"n\" has_r=\"n\" diff_mode=\"n\"\n\n        while [ $c -lt $cword ]; do\n                i=\"${words[c]}\"\n                case \"$i\" in\n                -d|--delete|-m|--move)  only_local_ref=\"y\" ;;\n                -r|--remotes)           has_r=\"y\" ;;\n+               --diff)                 diff_mode=\"y\" ;;\n                esac\n                ((c++))\n        done\n@@ -1221,11 +1222,22 @@ _git_branch ()\n                __git_complete_refs --cur=\"${cur##--set-upstream-to=}\"\n                ;;\n        --*)\n+               if [ $diff_mode = \"y\" ]; then\n+                       __gitcomp \"\n+                               --creation-factor= --dual-color\n+                               $__git_diff_common_options\n+                               \"\n+                       return\n+               fi\n                __gitcomp_builtin branch \"--no-color --no-abbrev\n                        --no-track --no-column\n                        \"\n                ;;\n        *)\n+               if [ $diff_mode = \"y\" ]; then\n+                       __git_complete_revlist\n+                       return\n+               fi\n                if [ $only_local_ref = \"y\" -a $has_r = \"n\" ]; then\n                        __gitcomp_direct \"$(__git_heads \"\" \"$cur\" \" \")\"\n                else\n\n"},{"id":"346828","messageId":"nycvar.QRO.7.76.6.1805062124470.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"e0db15c5-e897-5b03-20ff-d83f38496e61@gmail.com","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-07T01:34:56Z","receivedAt":"2018-05-07T01:35:21Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Buga,\n\nOn Sun, 6 May 2018, Igor Djordjevic wrote:\n\n> On 06/05/2018 14:10, Johannes Schindelin wrote:\n> \n> > I think Todd's idea to shift it from a full-blown builtin to a cmdmode\n> > of `branch` makes tons of sense.\n> \n> I don`t know, I still find it a bit strange that in order to \"diff\n> something\", you go to \"something\" and tell it to \"diff itself\" - not\n> because it`s a weird concept (OOP, anyone? :]), but because we already\n> have \"diff\" command that can accept different things, thus just teaching\n> it to accept additional \"something\" (branch, in this case), seems more\n> natural (to me) - \"branch diff\" being just another \"diff\" mode of\n> operation.\n\nYou also have to call `git branch` to list branches. And to rename\nbranches. And to delete them. So why not also compare them at the same\ntime?\n\n> What about that side thought you left out from my original message,\n> making it `git diff --branch` instead?\n\nI really did not like this, as all of the `git diff` options really are\nabout comparing two revisions, not two *sets* of revisions.\n\nFurther, if I put my unsuspecting user hat on, I would ask myself how you\ncan compare branches with one another? That is what I would expect `git\ndiff --branch` to do, not to compare two versions of *the same* branch.\n\nSo `git diff --branch` does not at all convey the same to me as `git\nbranch --diff`, and I find that the latter does match better what this\npatch series tries to achieve.\n\nI briefly considered `git branch --compare` instead, but then rejected it:\nit would again sound more like I try to compare two separate (and likely\nunrelated) branches with one another, and that simply does not make much\nsense, and tbdiff would not help with that, anyway.\n\n> But if \"branch diff\" is considered to be too special-cased mode of\n> \"diff\" so that supporting it from `diff` itself would make it feel\n> awkward in both usage and maintenance (in terms of many other regular\n> `diff` specific options being unsupported), I guess I would understand\n> having it outside `diff` altogether (and implemented as proposed `git\n> branch --diff`, or something)... for the time being, at least :)\n\nThe branch diff is not even a special-cased mode of diff. It is *way* more\ncomplicated than that. It tries to find 1:1 correspondences between *sets*\nof commits, and then only outputs a \"sort\" of a diff between the commits\nthat correspond with each other. I say \"sort\" of a diff because that diff\ndoes not look like `git diff <commit1> <commit2>` at all!\n\nSo I think it would just be confusing to add that mode to `git diff`.\n\nCiao,\nDscho\n"},{"id":"346829","messageId":"xmqqvac0qmbq.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"20180506064104.GB3418@sigill.intra.peff.net","subject":"Re: [PATCH v2 13/18] color: provide inverted colors, too","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-05-07T01:35:53Z","receivedAt":"2018-05-07T01:35:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Sun, May 06, 2018 at 02:35:44AM -0400, Jeff King wrote:\n>\n>> You'd have to introduce GIT_COLOR_REVERSE. I don't think we have a\n>> constant for it yet, but it's \\x[7m.\n>\n> Heh, of course you knew that already, as I just noticed your patch is\n> using the reverse attribute internally (I had thought at first glance\n> you were just specifying the background independently).\n\nI somehow suspected as such, but I also thought so and reacted \"what\nabout us whose terminal is black-on-white unlike most others?\",\nbefore looking up what 7 meant ;-)\n\n> So really, I guess all I am arguing for is having GIT_COLOR_INV (or\n> REVERSE) as a constant, and then teaching the code to combine it with\n> the existing \"new\" color. It's perfectly OK to have:\n>\n>   \\x1b[7m\\x1b[36m\n>\n> instead of:\n>\n>   \\x1b[7;36m\n>\n> It's two extra bytes, but I doubt anybody cares.\n\nI do not think two extra bytes will be missed, but it was not\nimmediately obvious to me how much flexibility or simplicity weu are\ngaining by combining values from multiple configuration variables.\nWith a \"letters on a new line is painted with ${new}, in addition,\nthe leading plus is further annotated with ${tbdiffNew}\" (similarly\nto \"old\") scheme, the user can take advantage of the fact that there\nis no ${reset} between ${new} and ${tbdiffNew} and set tbdiffNew and\ntbdiffOld to a same value (that does not change the color but\nchanges some other aspect of the appearance, like \"reverse\" or\n\"underline\").  Since only pre-designed combination can be used (your\nexample works only because you chose to allow combination by\nannotating the leading \"+\" with ${new}${tbdiffNew}), we'd need to\n(1) establish a convention to paint things with similar meanings in\nthe same color, modifyable by individual command (e.g. you could say\nanything new is by default green with \"color.new=green\", and then\n\"color.frotz.new=blink\" \"color.status.new=\" \"color.diff.new=blue\"\nwould make frotz, status and diff subcommands to show new things in\nblinking green, normal green, and blue), and (2) push the codebase\nto adopt such color combination as a preferred design pattern if we\nwant the resulting system to be useful.\n\nI guess you are getting simpler configuration, which is a big plus,\nbut to make a truly useful combining convention, we'd need to\nrethink and find a way to transition existing configurations to the\nnew world, which may not be feasible.\n\n"},{"id":"346830","messageId":"xmqqr2moqlw8.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805061419530.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-05-07T01:45:11Z","receivedAt":"2018-05-07T01:45:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n>> If tbdiff were \"Thomas's branch diff\", I would call this jbdiff ;-)\n>> but I think the 't' in there stands for \"topic\", not \"Thomas's\".\n>> \n>> How about \"git topic-diff\"?\n>\n> Or `git topic-branch-diff`?\n\nYeah something along that line, which is about comparing each step\nin two iterations of a single topic.  It would be wonderful if it\nalso supported a short-hand\n\n\t$ git tbdiff --reflog 1.day.ago js/branch-diff\n\nthat turned into:\n\n\t$ git tbdiff js/branch-diff..js/branch-diff@{1.day.ago} \\\n\t\t\tjs/branch-diff@{1.day.ago}..js/branch-diff\n\nThat compares \"what was on the topic a day ago\" with \"what is new on\nthe topic since that time\", which is exactly what an individual\ncontributor wants when reviewing how the topic was polished, I would\nsay.\n\n\n[Footnote]\n\nA variant I often use when accepting a rerolled series is\n\n\t$ git checkout js/branch-diff\n\t$ git checkout master...\n\t$ git am ./+js-branch-diff-v2\n\t$ git tbdiff ..@{-1} @{-1}..\n\nso this is not only for individual contributors but also helps\nintegrators.\n"},{"id":"346832","messageId":"nycvar.QRO.7.76.6.1805062146070.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180505234852.GR26695@zaya.teonanacatl.net","subject":"Re: [PATCH v2 12/18] branch-diff: use color for the commit pairs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-07T01:52:32Z","receivedAt":"2018-05-07T01:52:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Todd,\n\nOn Sat, 5 May 2018, Todd Zullinger wrote:\n\n> > @@ -430,6 +451,8 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n> >  \tstruct string_list branch1 = STRING_LIST_INIT_DUP;\n> >  \tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n> >  \n> > +\tgit_diff_basic_config(\"diff.color.frag\", \"magenta\", NULL);\n> > +\n> >  \tdiff_setup(&diffopt);\n> >  \tdiffopt.output_format = DIFF_FORMAT_PATCH;\n> >  \tdiffopt.flags.suppress_diff_headers = 1;\n> \n> Should this also (or only) check color.diff.frag?\n\nThis code is not querying diff.color.frag, it is setting it. Without\nany way to override it.\n\nHaving thought about it longer, and triggered by Peff's suggestion to\ndecouple the \"reverse\" part from the actual color, I fixed this by\n\n- *not* setting .frag to magenta,\n\n- using the reverse method also to mark outer *hunk headers* (not only the\n  outer -/+ markers).\n\n- actually calling git_diff_ui_config()...\n\n>  I thought that color.diff.* was preferred over diff.color.*, though\n>  that doesn't seem to be entirely true in all parts of the current\n>  codebase.\n> \n> In testing this series it seems that setting color.diff\n> options to change the various colors read earlier in this\n> patch via diff_get_color_opt, as well as the 'frag' slot,\n> are ignored.  Setting them via diff.color.<slot> does work.\n\nIn my tests, it did not even work via diff.color.<slot>. But I think I\nfixed this (at least my local testing confirms this) by calling\ngit_diff_ui_config().\n\n> The later patch adding a man page documents branch-diff as\n> using `diff.color.*` and points to git-config(1), but the\n> config docs only list color.diff.\n\nIn the current form (`git branch --diff`), I refrained from going into\n*so* much detail ;-) But the gist still holds, and now the code should\nsupport it, too.\n\nThe current work in progress can be pulled as `branch-diff` from\nhttps://github.com/dscho/git, if I could ask you to test?\n\nCiao,\nDscho\n"},{"id":"346833","messageId":"nycvar.QRO.7.76.6.1805062154030.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAN0heSoLD0O9owCDEU5ZHje3zNDLAS_43atb75Te7KOFoS_dtA@mail.gmail.com","subject":"Re: [PATCH v2 07/18] branch-diff: indent the diffs just like tbdiff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-07T01:54:36Z","receivedAt":"2018-05-07T01:54:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Martin,\n\nOn Sun, 6 May 2018, Martin Ågren wrote:\n\n> On 4 May 2018 at 17:34, Johannes Schindelin <johannes.schindelin@gmx.de> wrote:\n> > @@ -353,6 +358,7 @@ static void output(struct string_list *a, struct string_list *b,\n> >  int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n> >  {\n> >         struct diff_options diffopt = { NULL };\n> > +       struct strbuf four_spaces = STRBUF_INIT;\n> >         double creation_weight = 0.6;\n> >         struct option options[] = {\n> >                 OPT_SET_INT(0, \"no-patches\", &diffopt.output_format,\n> > @@ -371,6 +377,9 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n> >\n> >         diff_setup(&diffopt);\n> >         diffopt.output_format = DIFF_FORMAT_PATCH;\n> > +       diffopt.output_prefix = output_prefix_cb;\n> > +       strbuf_addstr(&four_spaces, \"    \");\n> > +       diffopt.output_prefix_data = &four_spaces;\n> >\n> >         argc = parse_options(argc, argv, NULL, options,\n> >                         builtin_branch_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n> \n> You end up leaking the buffer of `four_spaces`. Granted, that's not a\n> big memory leak, but still. ;-) This was the only leak that\n> LeakSanitizer found in v2 when running the new test-script and playing\n> around with this a bit. This looks really good!\n\nGood point. Fixed.\nDscho"},{"id":"346834","messageId":"nycvar.QRO.7.76.6.1805062155120.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAPig+cS0pvdg78fGUu8m2xspDDMHxi=uAMCkbLuthy7R4p3fQw@mail.gmail.com","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-07T02:04:31Z","receivedAt":"2018-05-07T02:04:53Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Sun, 6 May 2018, Eric Sunshine wrote:\n\n> On Sun, May 6, 2018 at 8:21 AM, Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> > On Sun, 6 May 2018, Junio C Hamano wrote:\n> >> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> >> > On Sat, 5 May 2018, Jeff King wrote:\n> >> >> One minor point about the name: will it become annoying as a tab\n> >> >> completion conflict with git-branch?\n> >>\n> >> If tbdiff were \"Thomas's branch diff\", I would call this jbdiff ;-)\n> >> but I think the 't' in there stands for \"topic\", not \"Thomas's\".\n> >> How about \"git topic-diff\"?\n> >\n> > Or `git topic-branch-diff`?\n> >\n> > But then, we do not really use the term `topic branch` a lot in Git, *and*\n> > the operation in question is not really about showing differences between\n> > topic branches, but between revisions of topic branches.\n> >\n> > So far, the solution I like best is to use `git branch --diff <...>`,\n> > which also neatly side-steps the problem of cluttering the top-level\n> > command list (because tab completion).\n> \n> Let's, please, not fall into the trap of polluting git-branch with\n> utterly unrelated functionality, as has happened a few times with\n> other Git commands. Let's especially not do so merely for the sake of\n> tab-completion. git-branch is for branch management; it's not for\n> diff'ing.\n\nI totally disagree. `git branch` is *the* command to work with branches.\nYes, you can manage branches. But you can also list them. And now you can\nalso compare them.\n\n> Of the suggestions thus far, Junio's git-topic-diff seems the least\n> worse, and doesn't suffer from tab-completion problems.\n\nExcept that this is too limited a view.\n\nHave you seen one of the more important tidbits in the cover letter, the\none about Git for Windows' *branch thicket*? In this case, it is not *one*\ntopic branch that we are talking about.\n\nAnd even worse: what this patch series introduces is not at all a feature\nto compare topic branches!\n\nInstead, it is a way to compare iterations of patch series, versions of\ntopic branches, changes introduced into a topic branch by rebasing it,\netc. And `git topic-diff` simply does not say this. It says something\ndifferent, something that my patches cannot fulfill.\n\n> Building on Duy's suggestion: git-interdiff could be a superset of the\n> current git-branch-diff:\n> \n>     # standard interdiff\n>     git interdiff womp-v1 womp-v2\n>     # 'tbdiff'-like output\n>     git interdiff --topic womp-v1 womp-v2\n\nNo, no, and no. An interdiff is an interdiff is an interdiff. See e.g.\nhttps://www.tutorialspoint.com/unix_commands/interdiff.htm for details.\n\nThe operation introduced by this patch series, or for that matter tbdiff,\n*never ever* produced an interdiff. Get this \"interdiff\" label out of your\nmind immediately when you think about this here operation.\n\nOne of my commit messages even talks about this, and says *why* we do not\ngenerate interdiffs: they are in general not even well-defined.\n\nTake my --rebase-merges patch series, for example. It is so long-running\nthat at some stages, all I did was to resolve merge conflicts incurred\nfrom rebasing to `master`. That was literally all. Now, if you tried to\nproduce an interdiff, you would *already fail in the first step*, as the\nprevious overall diff does not apply in reverse on current `master`.\n\nOut of all the options so far, the one that I liked was `git branch\n--diff`. Seriously. I do not understand why you think that this is abusing\nthe `git branch` command. It is no less abusing it than `git branch\n--edit-description`! And that is a *very good* command, and it is *very\ngood* that it is an option to `git branch`. It makes a total lot of sense,\nI have never had to think \"wait, in which Git command is this implemented\nalready?\" And I would expect the exact same thing to happen with `git\nbranch --diff`.\n\nCiao,\nJohannes\n"},{"id":"346835","messageId":"nycvar.QRO.7.76.6.1805062204580.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180506225627.GB953644@genre.crustytoothpaste.net","subject":"Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-07T02:05:16Z","receivedAt":"2018-05-07T02:05:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Brian,\n\nOn Sun, 6 May 2018, brian m. carlson wrote:\n\n> On Fri, May 04, 2018 at 05:34:27PM +0200, Johannes Schindelin wrote:\n> > The incredibly useful `git-tbdiff` tool to compare patch series (say,\n> > to see what changed between two iterations sent to the Git mailing\n> > list) is slightly less useful for this developer due to the fact that\n> > it requires the `hungarian` and `numpy` Python packages which are for\n> > some reason really hard to build in MSYS2. So hard that I even had to\n> > give up, because it was simply easier to reimplement the whole shebang\n> > as a builtin command.\n> \n> I just want to say thanks for writing this.  I use tbdiff extensively at\n> work and having this built-in and much faster will really help.\n> \n> I did a once-over of v1 and I'll probably take a look at v2 or v3\n> (whatever's the latest) later in the week.\n\nThank you so much!\nDscho\n"},{"id":"346845","messageId":"nycvar.QRO.7.76.6.1805062349450.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"xmqqvac0qmbq.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 13/18] color: provide inverted colors, too","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-07T05:38:48Z","receivedAt":"2018-05-07T05:39:28Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Mon, 7 May 2018, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > So really, I guess all I am arguing for is having GIT_COLOR_INV (or\n> > REVERSE) as a constant, and then teaching the code to combine it with\n> > the existing \"new\" color. It's perfectly OK to have:\n> >\n> >   \\x1b[7m\\x1b[36m\n> >\n> > instead of:\n> >\n> >   \\x1b[7;36m\n> >\n> > It's two extra bytes, but I doubt anybody cares.\n> \n> I do not think two extra bytes will be missed, but it was not\n> immediately obvious to me how much flexibility or simplicity weu are\n> gaining by combining values from multiple configuration variables.\n> With a \"letters on a new line is painted with ${new}, in addition,\n> the leading plus is further annotated with ${tbdiffNew}\" (similarly\n> to \"old\") scheme, the user can take advantage of the fact that there\n> is no ${reset} between ${new} and ${tbdiffNew} and set tbdiffNew and\n> tbdiffOld to a same value (that does not change the color but\n> changes some other aspect of the appearance, like \"reverse\" or\n> \"underline\").  Since only pre-designed combination can be used (your\n> example works only because you chose to allow combination by\n> annotating the leading \"+\" with ${new}${tbdiffNew}), we'd need to\n> (1) establish a convention to paint things with similar meanings in\n> the same color, modifyable by individual command (e.g. you could say\n> anything new is by default green with \"color.new=green\", and then\n> \"color.frotz.new=blink\" \"color.status.new=\" \"color.diff.new=blue\"\n> would make frotz, status and diff subcommands to show new things in\n> blinking green, normal green, and blue), and (2) push the codebase\n> to adopt such color combination as a preferred design pattern if we\n> want the resulting system to be useful.\n> \n> I guess you are getting simpler configuration, which is a big plus,\n> but to make a truly useful combining convention, we'd need to\n> rethink and find a way to transition existing configurations to the\n> new world, which may not be feasible.\n\nI really do not like the sound of that much complexity. It strikes me as\nyet another instance of Yer Ain't Gonna Need It. In *particular* because\nnested diffs are a special thing: you *already* get overwhelmed with\ntoo much information, and adding colors to the fray won't help.\n\nWhat does help is to keep the colors, so that they can mean the same thing\nin inner vs outer diffs, but reverse foreground and background to make the\nouter diff \"stick out more\".\n\nShould my assessment be wrong, I think it'll still be relatively easy to\nadd support for config settings, *then*, not before we know it is needed.\n\nCiao,\nDscho\n"},{"id":"346846","messageId":"nycvar.QRO.7.76.6.1805062355190.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"xmqqr2moqlw8.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-07T05:39:46Z","receivedAt":"2018-05-07T05:40:19Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Mon, 7 May 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> >> If tbdiff were \"Thomas's branch diff\", I would call this jbdiff ;-)\n> >> but I think the 't' in there stands for \"topic\", not \"Thomas's\".\n> >> \n> >> How about \"git topic-diff\"?\n> >\n> > Or `git topic-branch-diff`?\n> \n> Yeah something along that line, which is about comparing each step\n> in two iterations of a single topic.  It would be wonderful if it\n> also supported a short-hand\n> \n> \t$ git tbdiff --reflog 1.day.ago js/branch-diff\n> \n> that turned into:\n> \n> \t$ git tbdiff js/branch-diff..js/branch-diff@{1.day.ago} \\\n> \t\t\tjs/branch-diff@{1.day.ago}..js/branch-diff\n\nOr even easier: `git tbdiff js/branch-diff@{1.day.ago}...js/branch-diff`.\n\n> That compares \"what was on the topic a day ago\" with \"what is new on\n> the topic since that time\", which is exactly what an individual\n> contributor wants when reviewing how the topic was polished, I would\n> say.\n\nIt would be easy to introduce, but I am wary about its usefulness.\nUnless you re-generate the branch from patches (which I guess you do a\nlot, but I don't), you are likely to compare incomplete patch series: say,\nwhen you call `git rebase -i` to reword 05/18's commit message, your\ncommand will only compare 05--18 of the patch series.\n\nWorse, if js/branch-diff needs to be uprooted (e.g. because it now depends\non some different patch, or because it already depended on a separate\npatch series that was now updated), your `git branch --diff` call will\ncompare more than just my patches: it will assume that those dependencies\nare part of the patch series, because they changed, too.\n\n> [Footnote]\n> \n> A variant I often use when accepting a rerolled series is\n> \n> \t$ git checkout js/branch-diff\n> \t$ git checkout master...\n> \t$ git am ./+js-branch-diff-v2\n> \t$ git tbdiff ..@{-1} @{-1}..\n> \n> so this is not only for individual contributors but also helps\n> integrators.\n\nYes, and I also pointed out (twice) that it will help interested parties\nfollow what I do with my merging-rebases in Git for Windows.\n\nCiao,\nDscho\n"},{"id":"346848","messageId":"20180507073736.GA31170@sigill.intra.peff.net","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805062119051.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2 13/18] color: provide inverted colors, too","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-07T07:37:37Z","receivedAt":"2018-05-07T07:37:41Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, May 06, 2018 at 09:20:46PM -0400, Johannes Schindelin wrote:\n\n> > Heh, of course you knew that already, as I just noticed your patch is\n> > using the reverse attribute internally (I had thought at first glance\n> > you were just specifying the background independently).\n> > \n> > So really, I guess all I am arguing for is having GIT_COLOR_INV (or\n> > REVERSE) as a constant, and then teaching the code to combine it with\n> > the existing \"new\" color. It's perfectly OK to have:\n> > \n> >   \\x1b[7m\\x1b[36m\n> > \n> > instead of:\n> > \n> >   \\x1b[7;36m\n> > \n> > It's two extra bytes, but I doubt anybody cares.\n> \n> Yep, I agree that it is a small price to pay for the benefit of simply\n> using the reverse of diff.color.old (and .new).\n> \n> While at it, I also changed the hunk header colors: they are *also* simply\n> the same ones, with the outer one having background and foreground\n> reversed.\n\nThat sound sane.\n\nIf we ever did want to care about the number of bytes we output, I\nsuspect we could \"compress\" our ANSI terminal outputs by collapsing\nadjacent colors into a single one. But IMHO it's not even worth worrying\nabout that optimization at this point.\n\n-Peff\n"},{"id":"346850","messageId":"20180507074024.GB31170@sigill.intra.peff.net","threadId":"48405","inReplyTo":"xmqqvac0qmbq.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 13/18] color: provide inverted colors, too","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-07T07:40:24Z","receivedAt":"2018-05-07T07:40:29Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, May 07, 2018 at 10:35:53AM +0900, Junio C Hamano wrote:\n\n> > So really, I guess all I am arguing for is having GIT_COLOR_INV (or\n> > REVERSE) as a constant, and then teaching the code to combine it with\n> > the existing \"new\" color. It's perfectly OK to have:\n> >\n> >   \\x1b[7m\\x1b[36m\n> >\n> > instead of:\n> >\n> >   \\x1b[7;36m\n> >\n> > It's two extra bytes, but I doubt anybody cares.\n> \n> I do not think two extra bytes will be missed, but it was not\n> immediately obvious to me how much flexibility or simplicity weu are\n> gaining by combining values from multiple configuration variables.\n\nMy goal was just to let you set color.diff.new to something besides\ngreen without having to also manually set color.tbdiff.new (or whatever\nit's called) to match.\n\n> With a \"letters on a new line is painted with ${new}, in addition,\n> the leading plus is further annotated with ${tbdiffNew}\" (similarly\n> to \"old\") scheme, the user can take advantage of the fact that there\n> is no ${reset} between ${new} and ${tbdiffNew} and set tbdiffNew and\n> tbdiffOld to a same value (that does not change the color but\n> changes some other aspect of the appearance, like \"reverse\" or\n> \"underline\").  Since only pre-designed combination can be used (your\n> example works only because you chose to allow combination by\n> annotating the leading \"+\" with ${new}${tbdiffNew}), we'd need to\n> (1) establish a convention to paint things with similar meanings in\n> the same color, modifyable by individual command (e.g. you could say\n> anything new is by default green with \"color.new=green\", and then\n> \"color.frotz.new=blink\" \"color.status.new=\" \"color.diff.new=blue\"\n> would make frotz, status and diff subcommands to show new things in\n> blinking green, normal green, and blue), and (2) push the codebase\n> to adopt such color combination as a preferred design pattern if we\n> want the resulting system to be useful.\n\nRight, this is basically making that \"new\" piggy-backing explicit, but\nonly for this one case.\n\n> I guess you are getting simpler configuration, which is a big plus,\n> but to make a truly useful combining convention, we'd need to\n> rethink and find a way to transition existing configurations to the\n> new world, which may not be feasible.\n\nYes, one could probably develop a whole theming system for Git. We've\nresisted it so far. :)\n\n-Peff\n"},{"id":"346851","messageId":"20180507074843.GC31170@sigill.intra.peff.net","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805062155120.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-07T07:48:43Z","receivedAt":"2018-05-07T07:48:48Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, May 06, 2018 at 10:04:31PM -0400, Johannes Schindelin wrote:\n\n> > Let's, please, not fall into the trap of polluting git-branch with\n> > utterly unrelated functionality, as has happened a few times with\n> > other Git commands. Let's especially not do so merely for the sake of\n> > tab-completion. git-branch is for branch management; it's not for\n> > diff'ing.\n> \n> I totally disagree. `git branch` is *the* command to work with branches.\n> Yes, you can manage branches. But you can also list them. And now you can\n> also compare them.\n\nOne of the things I don't like about \"git branch --diff\" is that this\nfeature is not _just_ about branches at all. E.g., I could do:\n\n  git tbdiff HEAD~10 HEAD~5 foo\n\nOr even:\n\n  git tbdiff v2.16.0 v2.17.0 my-rewritten-v2.17.0\n\nThose arguments really are just commitishes, not necessarily branches.\nOne of the current interface rules for \"git branch\" is that the branch\nnames we hand it are interpreted _exactly_ as branch names. You cannot\n\"git branch -m v2.16.0\", and there is no ambiguity in \"git branch -d\nfoo\" if \"foo\" is both a tag and a branch.\n\nBut this new mode does not fit the pattern at all.\n\nIf we were to attach this to an existing command, I think it has more to\ndo with \"diff\" than \"branch\". But I'm not sure we want to overload\n\"diff\" either (which has traditionally been about two endpoints, and\ndoes not really traverse at all, though arguably \"foo...bar\" is a bit of\na cheat :) ).\n\n> > Of the suggestions thus far, Junio's git-topic-diff seems the least\n> > worse, and doesn't suffer from tab-completion problems.\n> \n> Except that this is too limited a view.\n\nRight, I agree with you. Topic branches are the intended use, but that's\nnot what it _does_, and obviously it can be applied in other cases. So\nsince \"branch\" is too specific, I think \"topic branch\" is even more so.\n\nIt's really \"diff-history\" or something, I think. That's not very\ncatchy, but I think the best name would imply that it was diffing a set\nof commits (so even \"diff-commit\" would not be right, because that again\nsounds like endpoints).\n\n-Peff\n"},{"id":"346852","messageId":"20180507075006.GD31170@sigill.intra.peff.net","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805052355190.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-07T07:50:06Z","receivedAt":"2018-05-07T07:50:11Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, May 05, 2018 at 11:57:26PM +0200, Johannes Schindelin wrote:\n\n> > It feels really petty complaining about the name, but I just want to\n> > raise the point, since it will never be easier to change than right now.\n> \n> I do hear you. Especially since I hate `git cherry` every single time that\n> I try to tab-complete `git cherry-pick`.\n\nMe too. :)\n\nI've wondered if \"git pick\" would be a good alias for cherry-pick (the\n\"cherry\" metaphor is probably not well understood by most users). And\n\"revert\" should just be \"pick -R\", but that is a whole other discussion.\n\n-Peff\n"},{"id":"346874","messageId":"xmqq603zpkih.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805062355190.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-05-07T15:12:38Z","receivedAt":"2018-05-07T15:13:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> It would be easy to introduce, but I am wary about its usefulness.\n> Unless you re-generate the branch from patches (which I guess you do a\n> lot, but I don't), you are likely to compare incomplete patch series: say,\n> when you call `git rebase -i` to reword 05/18's commit message, your\n> command will only compare 05--18 of the patch series.\n\nWell that is exactly the point of that \"..@{1} @{1}..\", which turned\nout to be very useful in practice at least for me when I am updating\na topic with \"rebase -i\", and then reviewing what I did with tbdiff.\n\nI do not want 01-04 in the above case as I already know I did not\ntouch them.\n"},{"id":"346879","messageId":"CACsJy8DabOE_6BKorQmO=E9m5QBbXW2hKLswiZ21Qg9z7H++cg@mail.gmail.com","threadId":"48405","inReplyTo":"20180507075006.GD31170@sigill.intra.peff.net","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2018-05-07T15:28:54Z","receivedAt":"2018-05-07T15:29:29Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Mon, May 7, 2018 at 9:50 AM, Jeff King <peff@peff.net> wrote:\n> On Sat, May 05, 2018 at 11:57:26PM +0200, Johannes Schindelin wrote:\n>\n>> > It feels really petty complaining about the name, but I just want to\n>> > raise the point, since it will never be easier to change than right now.\n>>\n>> I do hear you. Especially since I hate `git cherry` every single time that\n>> I try to tab-complete `git cherry-pick`.\n>\n> Me too. :)\n\nJust so you know I'm also not happy with that \"git cherry\". Since I'm\nupdating git-completion.bash in this area and we got 3 \"me too\" votes\n(four if we count Szeder in another thread), I'm going to implementing\nsomething to at least let you exclude \"cherry\" from the completion\nlist if you want.\n-- \nDuy\n"},{"id":"346884","messageId":"CABPp-BFQ2y1-FuA1wwnvFefjTFxunM4qeFke6icc5vAPs7k8GQ@mail.gmail.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805052141550.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-05-07T17:07:47Z","receivedAt":"2018-05-07T17:07:52Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Dscho,\n\nOn Sat, May 5, 2018 at 1:03 PM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> Hi Elijah,\n>\n> On Fri, 4 May 2018, Elijah Newren wrote:\n>\n<snip>\n>> - tbdiff aligned output columns better when there were more than 9\n>> patches (I'll comment more on patch 09/18)\n>\n> I added a new patch to align the patch numbers specifically. I considered\n> squashing it into 9/18, but decided against it: it will make it easier to\n> read through the rationale when calling `git annotate` on those lines.\n\nAwesome, thanks.\n\n<snip>\n>> Also, I don't have bash-completion for either tbdiff or branch-diff.\n>> :-(  But I saw some discussion on the v1 patches about how this gets\n>> handled...  :-)\n>\n> Oh? Does 18/18 not work for you?\n> https://public-inbox.org/git/71698f11835311c103aae565a2a761d10f4676b9.1525448066.git.johannes.schindelin@gmx.de/\n\n\nIt looks like it does work, in part, there were just two issues:\n\n1) I apparently wasn't using all the nice improvements from the\ncompletion script in my locally built git, but was instead still using\nthe one associated with my system-installed (and much older) git.\n(Oops, my bad.)\n\n\n2) Your completion commands for branch-diff will only complete one\nrevision range, not two.  e.g.\n    git branch-diff origin/master..my-topic@{2} origin/master..my-top<tab>\nwon't complete \"my-topic\" as I'd expect.\n\n\nElijah\n"},{"id":"346885","messageId":"20180507175007.30381-1-szeder.dev@gmail.com","threadId":"48405","inReplyTo":"CABPp-BFQ2y1-FuA1wwnvFefjTFxunM4qeFke6icc5vAPs7k8GQ@mail.gmail.com","subject":"Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2018-05-07T17:50:07Z","receivedAt":"2018-05-07T17:50:26Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"> 2) Your completion commands for branch-diff will only complete one\n> revision range, not two.  e.g.\n>     git branch-diff origin/master..my-topic@{2} origin/master..my-top<tab>\n> won't complete \"my-topic\" as I'd expect.\n\nIt does complete two revision ranges, but if you want to look at\nreflogs, then you must escape the opening curly brace.  I'm not sure\nwhy, but apparently after the unescaped '{' Bash thinks that it's a\nnew command, and doesn't even call our completion functions anymore.\nIt's not specific to the completion of 'branch-diff', or even to our\ncompletion script.  I don't think we can do anything about it.\n\n"},{"id":"346903","messageId":"CABPp-BGGmQsy-MD=0qX1FTj0d5-j-cocz6HCUXWzC4gnW1k9NA@mail.gmail.com","threadId":"48405","inReplyTo":"20180507175007.30381-1-szeder.dev@gmail.com","subject":"Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-05-07T18:38:44Z","receivedAt":"2018-05-07T18:38:49Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, May 7, 2018 at 10:50 AM, SZEDER Gábor <szeder.dev@gmail.com> wrote:\n>> 2) Your completion commands for branch-diff will only complete one\n>> revision range, not two.  e.g.\n>>     git branch-diff origin/master..my-topic@{2} origin/master..my-top<tab>\n>> won't complete \"my-topic\" as I'd expect.\n>\n> It does complete two revision ranges, but if you want to look at\n> reflogs, then you must escape the opening curly brace.  I'm not sure\n> why, but apparently after the unescaped '{' Bash thinks that it's a\n> new command, and doesn't even call our completion functions anymore.\n> It's not specific to the completion of 'branch-diff', or even to our\n> completion script.  I don't think we can do anything about it.\n\nAh, indeed.  Thanks for the pointer.\n"},{"id":"346907","messageId":"CAGZ79kZ_M3AMO0ieaH1u3mtBCEtnHNWV8DtE219p+WM3P5L2tA@mail.gmail.com","threadId":"48405","inReplyTo":"CACsJy8DabOE_6BKorQmO=E9m5QBbXW2hKLswiZ21Qg9z7H++cg@mail.gmail.com","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-05-07T19:58:10Z","receivedAt":"2018-05-07T19:58:15Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, May 7, 2018 at 8:28 AM, Duy Nguyen <pclouds@gmail.com> wrote:\n\n>>> I do hear you. Especially since I hate `git cherry` every single time that\n>>> I try to tab-complete `git cherry-pick`.\n>>\n>> Me too. :)\n>\n> Just so you know I'm also not happy with that \"git cherry\". Since I'm\n> updating git-completion.bash in this area and we got 3 \"me too\" votes\n> (four if we count Szeder in another thread), I'm going to implementing\n> something to at least let you exclude \"cherry\" from the completion\n> list if you want.\n\nAnd another \"me too\" here.\n"},{"id":"346910","messageId":"3cefc6b3-3dbd-9cb1-20d0-193116191726@gmail.com","threadId":"48405","inReplyTo":"20180507074843.GC31170@sigill.intra.peff.net","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Igor Djordjevic","fromEmail":"igor.d.djordjevic@gmail.com","sentAt":"2018-05-07T21:33:20Z","receivedAt":"2018-05-07T21:33:28Z","isPatch":true,"sender":{"key":"igor.d.djordjevic@gmail.com","avatar":null},"body":"On 07/05/2018 09:48, Jeff King wrote:\n> \n> > > Let's, please, not fall into the trap of polluting git-branch with\n> > > utterly unrelated functionality, as has happened a few times with\n> > > other Git commands. Let's especially not do so merely for the sake of\n> > > tab-completion. git-branch is for branch management; it's not for\n> > > diff'ing.\n> >\n> > I totally disagree. `git branch` is *the* command to work with branches.\n> > Yes, you can manage branches. But you can also list them. And now you can\n> > also compare them.\n> \n> One of the things I don't like about \"git branch --diff\" is that this\n> feature is not _just_ about branches at all. E.g., I could do:\n> \n>   git tbdiff HEAD~10 HEAD~5 foo\n> \n> Or even:\n> \n>   git tbdiff v2.16.0 v2.17.0 my-rewritten-v2.17.0\n> \n> Those arguments really are just commitishes, not necessarily branches.\n> One of the current interface rules for \"git branch\" is that the branch\n> names we hand it are interpreted _exactly_ as branch names. You cannot\n> \"git branch -m v2.16.0\", and there is no ambiguity in \"git branch -d\n> foo\" if \"foo\" is both a tag and a branch.\n> \n> But this new mode does not fit the pattern at all.\n> \n> If we were to attach this to an existing command, I think it has more to\n> do with \"diff\" than \"branch\". But I'm not sure we want to overload\n> \"diff\" either (which has traditionally been about two endpoints, and\n> does not really traverse at all, though arguably \"foo...bar\" is a bit of\n> a cheat :) ).\n> \n> > > Of the suggestions thus far, Junio's git-topic-diff seems the least\n> > > worse, and doesn't suffer from tab-completion problems.\n> >\n> > Except that this is too limited a view.\n> \n> Right, I agree with you. Topic branches are the intended use, but that's\n> not what it _does_, and obviously it can be applied in other cases. So\n> since \"branch\" is too specific, I think \"topic branch\" is even more so.\n> \n> It's really \"diff-history\" or something, I think. That's not very\n> catchy, but I think the best name would imply that it was diffing a set\n> of commits (so even \"diff-commit\" would not be right, because that again\n> sounds like endpoints).\n\nThis is exactly what I feel as well, thanks for concise and \nto-the-point spelling out.\n\nFrom user interface perspective, I would expect something like this \nto be possible (and natural):\n\n(1) git diff topic-v1...topic-v2\n(2) git diff --branch topic-v1...topic-v2\n\n(1) is what we are all familiar with, providing a diff between two \nrevisions with focus on file changes, where (2) shifts focus to \nhistory changes.\n\nIt`s all still a comparison between two revisions (pointed to by \n\"topic-v1\" and \"topic-v2\" branch heads in this specific example), but \nit differs in what we are comparing - (1) set of files contained in \nendpoints, or (2) set of revisions contained in (or \"leading to\") \nendpoints.\n\nHmm... what about `git diff --history`? :/ It does seem more \"true\" \nto what it does, though I still like `git diff --branch` more \n(catchier, indeed).\n\nRegards, Buga\n"},{"id":"346911","messageId":"3b4591cd-6dde-31ee-f0b1-42b5353086e5@gmail.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805062124470.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Igor Djordjevic","fromEmail":"igor.d.djordjevic@gmail.com","sentAt":"2018-05-07T22:05:24Z","receivedAt":"2018-05-07T22:05:31Z","isPatch":true,"sender":{"key":"igor.d.djordjevic@gmail.com","avatar":null},"body":"Hi Dscho,\n\nOn 07/05/2018 03:34, Johannes Schindelin wrote:\n> \n> > > I think Todd's idea to shift it from a full-blown builtin to a cmdmode\n> > > of `branch` makes tons of sense.\n> >\n> > I don`t know, I still find it a bit strange that in order to \"diff\n> > something\", you go to \"something\" and tell it to \"diff itself\" - not\n> > because it`s a weird concept (OOP, anyone? :]), but because we already\n> > have \"diff\" command that can accept different things, thus just teaching\n> > it to accept additional \"something\" (branch, in this case), seems more\n> > natural (to me) - \"branch diff\" being just another \"diff\" mode of\n> > operation.\n> \n> You also have to call `git branch` to list branches. And to rename\n> branches. And to delete them. So why not also compare them at the same\n> time?\n\nMaybe because we already have a command that specifically does \ncomparison? :)\n\nList, rename, delete -- all these seem more as basic CRUD operations, \nwhere comparison is a more complex one. And not to get me wrong - I \ncould see \"branch diff\" being part of \"branch\", but not really when \n\"diff\" already exists as a separate thing, already doing quite some \n(but still diff related, and configurable) stuff.\n\n> > What about that side thought you left out from my original message,\n> > making it `git diff --branch` instead?\n> \n> I really did not like this, as all of the `git diff` options really are\n> about comparing two revisions, not two *sets* of revisions.\n\nI see what you mean, but I would argue this being a deliberate user \nchoice here, like picking a diff \"strategy\" - I`d say it still utterly \ndoes compare two revisions (branch tips, in this case), just putting \nfocus on comparing revisions that lead to them (branch history), \ninstead of just files found in them (branch files).\n\n> Further, if I put my unsuspecting user hat on, I would ask myself how you\n> can compare branches with one another? That is what I would expect `git\n> diff --branch` to do, not to compare two versions of *the same* branch.\n\nI totally agree with you here, and thus I have a question - what \ndetermines \"two versions of *the same* branch\"? :) Do you still \nexplicitly provide both \"old\" and \"new\" version branch tips?\n\nI see \"multiple versions of the same branch\" more as a conceptual \nmodel, and not something Git is aware of (I think?) - BUT, even if it \nwas, I don`t see why this should be a(n artificial) restriction?\n\nBasically, what you (conceptually) call \"two versions of the same \nbranch\", I simply call \"two branches\" (from usage standpoint).\n\nAnd you may have a branch that got split, or more of them that got \nunified, so defining \"previous branch version\" may not be that \nstraightforward - it`s really just \"two commit ranges\" (as man page \ndefines it in general), with \"two versions of a patch series\" only \nbeing the most common/expected use case of the former.\n\nFinally, if user picks two totally unrelated \"branches\" to compare, \nhe won`t get a really useful diff - but it`s the same as if he would \ncompare two totally unrelated commits (where tree state massively \nchanged in between, or having unrelated histories, even).\n\nBesides, while I might still not be much into the matter, but isn`t \n\"branch\" in Git just a pointer to revision? Being so, there is really \nno such thing as \"branch\" in terms of being a specific (sub)set of \nrevisions (commits), other then \"everything from branch head/pointer \nto root commit\" (in general).\n\nYes, we do perceive \"a branch\" being a specific set of topic related \ncommits, but which *exact* commits we are interested in (\"branch\" lower \nbounds) may differ in regards to what we aim for - how far do we consider \none branch to reach in the past depends solely on the use case.\n\n> So `git diff --branch` does not at all convey the same to me as `git\n> branch --diff`, and I find that the latter does match better what this\n> patch series tries to achieve.\n\nI agree with the first part, but it seems to me your finding is \nbiased due to your (expected) use case.\n\n> > But if \"branch diff\" is considered to be too special-cased mode of\n> > \"diff\" so that supporting it from `diff` itself would make it feel\n> > awkward in both usage and maintenance (in terms of many other regular\n> > `diff` specific options being unsupported), I guess I would understand\n> > having it outside `diff` altogether (and implemented as proposed `git\n> > branch --diff`, or something)... for the time being, at least :)\n> \n> The branch diff is not even a special-cased mode of diff. It is *way* more\n> complicated than that. It tries to find 1:1 correspondences between *sets*\n> of commits, and then only outputs a \"sort\" of a diff between the commits\n> that correspond with each other. I say \"sort\" of a diff because that diff\n> does not look like `git diff <commit1> <commit2>` at all!\n\nBut there is not only one `git diff <commit1> <commit2>` looks, it \ndepends on other options (like --name-status, for example), which is \nmy point exactly :)\n\nWith something like `git diff --branch <commit1>...<commit2>` you \nwould get yet another \"diff look\", useful for use case in question \nhere.\n\nRegards, Buga\n"},{"id":"346912","messageId":"CAGZ79kZbRCH2OiTW1Ge31R9JN+vWD6tcjNWVGSzkSBcYZvwDjw@mail.gmail.com","threadId":"48405","inReplyTo":"3b4591cd-6dde-31ee-f0b1-42b5353086e5@gmail.com","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-05-07T22:24:59Z","receivedAt":"2018-05-07T22:25:04Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, May 7, 2018 at 3:05 PM, Igor Djordjevic\n<igor.d.djordjevic@gmail.com> wrote:\n\n> List, rename, delete -- all these seem more as basic CRUD operations,\n> where comparison is a more complex one. And not to get me wrong - I\n> could see \"branch diff\" being part of \"branch\", but not really when\n> \"diff\" already exists as a separate thing, already doing quite some\n> (but still diff related, and configurable) stuff.\n\nIf we go with \"branch --diff\", because it has the CRUD operations already\nthere for branches, I might ask for \"remote --diff\" to diff two remotes. ;)\n(That command \"remote --diff\" would not make any sense, would it?)\n\n> Basically, what you (conceptually) call \"two versions of the same\n> branch\", I simply call \"two branches\" (from usage standpoint).\n\nIf I diff 2 (topic) branches, which are based on a different version\nfrom upstream, then I see changes from commits that I don't care\nabout, but this tool explicitly excludes them. Instead it includes\nthe ordering of the commits as well as its commit messages to\nthe diff.\n\nSo I would not say this tool \"diffs two branches\", as that is understood\nas \"diffing the trees, where each of the two branches points two\",\nwhereas this tool diffs a patch series, or if you give Git-ranges,\nthen it would produce such a patch series in memory.\n\n\n> And you may have a branch that got split, or more of them that got\n> unified, so defining \"previous branch version\" may not be that\n> straightforward - it`s really just \"two commit ranges\" (as man page\n> defines it in general), with \"two versions of a patch series\" only\n> being the most common/expected use case of the former.\n>\n> Finally, if user picks two totally unrelated \"branches\" to compare,\n> he won`t get a really useful diff - but it`s the same as if he would\n> compare two totally unrelated commits (where tree state massively\n> changed in between, or having unrelated histories, even).\n\nI used just that, but narrowed down the comparison to one file\ninstead of the whole tree.\n\n> With something like `git diff --branch <commit1>...<commit2>` you\n> would get yet another \"diff look\", useful for use case in question\n> here.\n\nPersonally I think this patch series should neither extend git-diff\nnor git-branch.\n\nIt should not extend git-diff, because currently git-diff can diff\ntree-ishs (and does that very well) and comparing to\nworktree/index.\n\nIt should also not extend git-branch, as that command is for\nCRUD operations that you hinted at earlier (Earlier I proposed\ngit-remote --diff for diffing two remote, which makes no sense,\nanother one might be git-worktree, which also just does CRUD\nfor worktrees. It would be a bad idea to have \"git worktree --diff\")\n\nHence I propose \"git range-diff\", similar to topic-diff, that\nwas proposed earlier.\n\n* it \"diffs ranges\" of commits.\n* it can also deal with out-of-git things like patch series,\n  but that is a mere by product and may not be desired.\n  Just like git-diff can also compare two files outside a git\n  repo, that would not be a good use case.\n  Keep the name Git-centric!\n* it autocompletes well.\n\nStefan\n"},{"id":"346931","messageId":"bc9fa17b-d133-6679-42cf-2374e4513c35@gmail.com","threadId":"48405","inReplyTo":"CAGZ79kZbRCH2OiTW1Ge31R9JN+vWD6tcjNWVGSzkSBcYZvwDjw@mail.gmail.com","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Igor Djordjevic","fromEmail":"igor.d.djordjevic@gmail.com","sentAt":"2018-05-07T23:39:26Z","receivedAt":"2018-05-07T23:39:32Z","isPatch":true,"sender":{"key":"igor.d.djordjevic@gmail.com","avatar":null},"body":"Hi Stefan,\n\nOn 08/05/2018 00:24, Stefan Beller wrote:\n> \n> > List, rename, delete -- all these seem more as basic CRUD operations,\n> > where comparison is a more complex one. And not to get me wrong - I\n> > could see \"branch diff\" being part of \"branch\", but not really when\n> > \"diff\" already exists as a separate thing, already doing quite some\n> > (but still diff related, and configurable) stuff.\n> \n> If we go with \"branch --diff\", because it has the CRUD operations already\n> there for branches, I might ask for \"remote --diff\" to diff two remotes. ;)\n> (That command \"remote --diff\" would not make any sense, would it?)\n\nI`m not sure if this is a reply to me or in general, and whether you \nsupport what I sad, or argue against it...? Because what you`re \nsaying was (or at least should have been) my exact point there :)\n\n> > Basically, what you (conceptually) call \"two versions of the same\n> > branch\", I simply call \"two branches\" (from usage standpoint).\n> \n> If I diff 2 (topic) branches, which are based on a different version\n> from upstream, then I see changes from commits that I don't care\n> about, but this tool explicitly excludes them. Instead it includes\n> the ordering of the commits as well as its commit messages to\n> the diff.\n\nHere, I was merely pointing out that you still need to provide two \nbranch heads - which might be expected to resemble \"two versions of \nthe same topic\", but they are still (just) \"two branches\" in Git world.\n\n> > And you may have a branch that got split, or more of them that got\n> > unified, so defining \"previous branch version\" may not be that\n> > straightforward - it`s really just \"two commit ranges\" (as man page\n> > defines it in general), with \"two versions of a patch series\" only\n> > being the most common/expected use case of the former.\n> >\n> > Finally, if user picks two totally unrelated \"branches\" to compare,\n> > he won`t get a really useful diff - but it`s the same as if he would\n> > compare two totally unrelated commits (where tree state massively\n> > changed in between, or having unrelated histories, even).\n> \n> I used just that, but narrowed down the comparison to one file\n> instead of the whole tree.\n\nAgain, not sure if this should support the argument, or argue against \nit? :) My point was that there might be other use cases (as you seem \nto have supported now), and as \"diff\" is pretty forgiving, might be \n\"diff branch\" should be as well.\n\n> > With something like `git diff --branch <commit1>...<commit2>` you\n> > would get yet another \"diff look\", useful for use case in question\n> > here.\n> \n> Personally I think this patch series should neither extend git-diff\n> nor git-branch.\n> \n> It should not extend git-diff, because currently git-diff can diff\n> tree-ishs (and does that very well) and comparing to\n> worktree/index.\n\nHmm, are you saying that `git diff` actually has a too generic name \nfor its (more specific) purpose?\n\n> It should also not extend git-branch, as that command is for\n> CRUD operations that you hinted at earlier (Earlier I proposed\n> git-remote --diff for diffing two remote, which makes no sense,\n> another one might be git-worktree, which also just does CRUD\n> for worktrees. It would be a bad idea to have \"git worktree --diff\")\n\nAgreed here.\n\n> Hence I propose \"git range-diff\", similar to topic-diff, that\n> was proposed earlier.\n\nI find it strange that we already have both \"diff\" and \"diff-something\" \ncommands, and yet you still propose \"something-diff\" naming pattern \ninstead (but I guess it`s mainly because of auto-complete concerns).\n\nPlease forgive my lack of code base familiarity, but from what I`ve \nseen so far, and at least from end-user perspective, I may rather expect \n`git diff-range` as low level implementation, and possibly exposed \nthrough `git diff --range` (with a nice single letter abbreviation?).\n\n> * it \"diffs ranges\" of commits.\n\nThus \"diff-range\", as your description says itself :) (\"range-diff\" \nmight sound like it \"ranges diffs\"...?)\n\n> * it can also deal with out-of-git things like patch series,\n>   but that is a mere by product and may not be desired.\n>   Just like git-diff can also compare two files outside a git\n>   repo, that would not be a good use case.\n\nHmm, so still follows `git diff` in general... `git diff --range`? :D\n\n> * it autocompletes well.\n\nOnly here I`m not sure if something like `git diff --range` (with \naccompanying single letter option) would be considered \"auto-complete \nfriendly\", or not?\n\nRegards, Buga\n"},{"id":"346939","messageId":"xmqqwowfng47.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"20180507074843.GC31170@sigill.intra.peff.net","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-05-08T00:30:32Z","receivedAt":"2018-05-08T00:30:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> One of the things I don't like about \"git branch --diff\" is that this\n> feature is not _just_ about branches at all.\n\nI actually wouldn't be that much against the word \"branch\" in\n\"branch-diff\" on the ground that we are typically not feeding\nbranches to the command (we are feeding two ranges, and one endpoint\nof each range typically gets expressed using branch name), as we\nhave a precedent in \"show-branch\", for example, that often takes\nbranches but does not have to.\n\n> It's really \"diff-history\" or something, I think. That's not very\n> catchy, but I think the best name would imply that it was diffing a set\n> of commits (so even \"diff-commit\" would not be right, because that again\n> sounds like endpoints).\n\nSure.  This should't be a submode \"--diff\" of \"git branch\" just like\nit shouldn't be a submode of \"git commit\" only because it is about\ncomparing two sets of commits.  \"diff\" is about comparing two\nendpoints, and not about comparing two sets.  \"log\" is the closest\nthing, if we really want to coerce it into an existing set of\ncommands, as it is about a set of commits, but it does not do\nmultiple sets, let alone comparing them.\n\n\"branch-diff\" was just a good as \"diff-history\", except that both of\nthem may irritate command line completion users.  I do not think I\ncare too much about which exact command name it gets, but I think it\nis a bad idea to tacked it to an existing command as a submode that\ndoes unrelated thing to what the main command does.  So from that\npoint of view, \"branch-diff\" and \"diff-history\" are equally good\nbeing a distinct command, and equally bad sharing prefix with common\nexisting command.\n\n"},{"id":"346948","messageId":"20180508021029.GC26695@zaya.teonanacatl.net","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805062146070.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2 12/18] branch-diff: use color for the commit pairs","fromName":"Todd Zullinger","fromEmail":"tmz@pobox.com","sentAt":"2018-05-08T02:10:29Z","receivedAt":"2018-05-08T02:10:35Z","isPatch":true,"sender":{"key":"tmz@pobox.com","avatar":"https://avatars.githubusercontent.com/u/806319?v=4"},"body":"Hi Johannes,\n\nJohannes Schindelin wrote:\n> Hi Todd,\n> \n> On Sat, 5 May 2018, Todd Zullinger wrote:\n> \n>>> @@ -430,6 +451,8 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n>>>  \tstruct string_list branch1 = STRING_LIST_INIT_DUP;\n>>>  \tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n>>>  \n>>> +\tgit_diff_basic_config(\"diff.color.frag\", \"magenta\", NULL);\n>>> +\n>>>  \tdiff_setup(&diffopt);\n>>>  \tdiffopt.output_format = DIFF_FORMAT_PATCH;\n>>>  \tdiffopt.flags.suppress_diff_headers = 1;\n>> \n>> Should this also (or only) check color.diff.frag?\n> \n> This code is not querying diff.color.frag, it is setting it. Without\n> any way to override it.\n> \n> Having thought about it longer, and triggered by Peff's suggestion to\n> decouple the \"reverse\" part from the actual color, I fixed this by\n> \n> - *not* setting .frag to magenta,\n> \n> - using the reverse method also to mark outer *hunk headers* (not only the\n>   outer -/+ markers).\n> \n> - actually calling git_diff_ui_config()...\n\nExcellent.  That seems to work nicely now, respecting the\ncolor.diff.<slot> config.\n\n> The current work in progress can be pulled as `branch-diff` from\n> https://github.com/dscho/git, if I could ask you to test?\n\nWhile the colors and 'branch --diff' usage seem to work\nnicely, I found that with 4ac3413cc8 (\"branch-diff: left-pad\npatch numbers\", 2018-05-05), 'git branch' itself is broken.\n\nRunning 'git branch' creates a branch named 'branch'.\nCalling 'git branch --list' shows only 'branch' as the only\nbranch.\n\nI didn't look too closely, but I'm guessing that the argv\nhandling is leaving the 'branch' argument in place where it\nshould be stripped?\n\nThis unsurprisingly breaks a large number of tests. :)\n\nThanks,\n\n-- \nTodd\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nA common mistake people make when trying to design something\ncompletely foolproof is to underestimate the ingenuity of complete\nfools.\n    -- Douglas Adams\n\n"},{"id":"346957","messageId":"20180508034429.GA7242@sigill.intra.peff.net","threadId":"48405","inReplyTo":"CAGZ79kZbRCH2OiTW1Ge31R9JN+vWD6tcjNWVGSzkSBcYZvwDjw@mail.gmail.com","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-08T03:44:29Z","receivedAt":"2018-05-08T03:44:34Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, May 07, 2018 at 03:24:59PM -0700, Stefan Beller wrote:\n\n> Hence I propose \"git range-diff\", similar to topic-diff, that\n> was proposed earlier.\n> \n> * it \"diffs ranges\" of commits.\n> * it can also deal with out-of-git things like patch series,\n>   but that is a mere by product and may not be desired.\n>   Just like git-diff can also compare two files outside a git\n>   repo, that would not be a good use case.\n>   Keep the name Git-centric!\n> * it autocompletes well.\n\nFWIW, I like this by far of all of the suggested names.\n\n-Peff\n"},{"id":"346959","messageId":"20180508034815.GB7242@sigill.intra.peff.net","threadId":"48405","inReplyTo":"20180508034429.GA7242@sigill.intra.peff.net","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-08T03:48:15Z","receivedAt":"2018-05-08T03:48:20Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, May 07, 2018 at 11:44:29PM -0400, Jeff King wrote:\n\n> On Mon, May 07, 2018 at 03:24:59PM -0700, Stefan Beller wrote:\n> \n> > Hence I propose \"git range-diff\", similar to topic-diff, that\n> > was proposed earlier.\n> > \n> > * it \"diffs ranges\" of commits.\n> > * it can also deal with out-of-git things like patch series,\n> >   but that is a mere by product and may not be desired.\n> >   Just like git-diff can also compare two files outside a git\n> >   repo, that would not be a good use case.\n> >   Keep the name Git-centric!\n> > * it autocompletes well.\n> \n> FWIW, I like this by far of all of the suggested names.\n\nI hit \"send\" before I had a chance to expound. ;)\n\nThe thing that I really like about it is that it names the _concept_.\nIf I were writing a manual page describing what this output is, I would\ncall it a \"range diff\". And naturally, the command to generate range\ndiffs is \"git range-diff\".\n\nI think \"git diff --range\" would also be OK, but IMHO it's useful to\nkeep the \"git diff\" family as always comparing end-points.\n\n-Peff\n"},{"id":"347096","messageId":"8876f376-db86-c3e1-b97d-9e33506f2df2@ramsayjones.plus.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805052130360.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Ramsay Jones","fromEmail":"ramsay@ramsayjones.plus.com","sentAt":"2018-05-09T16:24:36Z","receivedAt":"2018-05-09T16:24:42Z","isPatch":true,"sender":{"key":"ramsay@ramsayjones.plus.com","avatar":"https://avatars.githubusercontent.com/u/33702710?v=4"},"body":"\n\nOn 05/05/18 20:41, Johannes Schindelin wrote:\n[snip]\n\n[Sorry for the late reply - still catching up after (long weekend)\nUK public holiday]\n\n> Well, what I would want to do is let the cloud work for me. By adding an\n> automated build to my Visual Studio Team Services (VSTS) account, of\n> course, as I have \"cloud privilege\" (i.e. I work in the organization\n> providing the service, so I get to play with all of it for free).\n> \n> So I really don't want to build sparse every time a new revision needs to\n> be tested (whether that be from one of my branches, an internal PR for\n> pre-review of patches to be sent to the mailing list, or maybe even `pu`\n> or the personalized branches on https://github.com/gitster/git).\n> \n> I really would need a ready-to-install sparse, preferably as light-weight\n> as possible (by not requiring any dependencies outside what is available\n> in VSTS' hosted Linux build agents.\n> \n> Maybe there is a specific apt source for sparse?\n\nNot that I'm aware of, sorry! :(\n\n[release _source_ tar-balls are available, but that's not\nwhat you are after, right?]\n\nI don't know what is involved in setting up a 'ppa repo' for\nUbuntu, which I suspect is the kind of thing you want, but it\nwould have helped me several times in the past (so that I could\nhave something to point people to) ... ;-)\n\nATB,\nRamsay Jones\n\n"},{"id":"347551","messageId":"CACsJy8ApDb_KTjoJgMuV_AJ+dQdGC0kgCnMcJXj5pMGdqMQCyA@mail.gmail.com","threadId":"48405","inReplyTo":"3f51970cbc44bfe34133c48c0844ed3723e83808.1525361419.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 01/18] Add a function to solve least-cost assignment problems","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2018-05-13T18:19:44Z","receivedAt":"2018-05-13T18:20:17Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, May 3, 2018 at 5:30 PM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> +       /* reduction transfer */\n> +       free_row = xmalloc(sizeof(int) * row_count);\n> +       for (int i = 0; i < row_count; i++) {\n\ntravis complains about this\n\n\nhungarian.c: In function ‘compute_assignment’:\nhungarian.c:47:11: error: redeclaration of ‘i’ with no linkage\n  for (int i = 0; i < row_count; i++) {\n           ^\nhungarian.c:21:6: note: previous declaration of ‘i’ was here\n  int i, j, phase;\n      ^\nhungarian.c:47:2: error: ‘for’ loop initial declarations are only\nallowed in C99 mode\n  for (int i = 0; i < row_count; i++) {\n  ^\nhungarian.c:47:2: note: use option -std=c99 or -std=gnu99 to compile your code\n-- \nDuy\n"},{"id":"348178","messageId":"xmqqvabh1ung.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"cover.1525361419.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-05-21T04:48:19Z","receivedAt":"2018-05-21T04:48:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"I've been using both branch-diff and tbdiff while comparing rerolled\ntopics before accepting them.  One obvious difference between the\ntwo is that the speed to compute pairing is quite different but that\nis expected ;-)\n\nAnother difference that is somewhat irritating is that a SP that\nleads a context line in a diff hunk that is introduced in the newer\niteration is often painted in reverse, and I do not know what aspect\nof that single SP on these lines the branch-diff wants to pull my\nattention to.\n\n    https://pasteboard.co/Hm9ujI7F.png\n\nIn the picture, the three pre-context lines that are all indented by\na HT are prefixed by a SP, and that is prefixed by a '+' sign of the\nouter diff.\n\nWe can use --dual-color mode to unsee these but it is somewhat\nfrustrating not to know what the branch-diff program wants to tell\nme by highlighting the single SPs on these lines.\n\nThanks.\n"},{"id":"348185","messageId":"nycvar.QRO.7.76.6.1805211144340.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"xmqqvabh1ung.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-21T09:51:48Z","receivedAt":"2018-05-21T09:51:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Mon, 21 May 2018, Junio C Hamano wrote:\n\n> I've been using both branch-diff and tbdiff while comparing rerolled\n> topics before accepting them.  One obvious difference between the two is\n> that the speed to compute pairing is quite different but that is\n> expected ;-)\n\nYep.\n\nIt is also expected that the branch --diff actually works without you\nhaving to make sure that the Python modules numpy and hungarian are built\ncorrectly (which you Linux folks couldn't care less about because it is\ndone for ya).\n\nI spent such a lot on time trying to get it to work on Windows that it\nprobably only now reaches parity with the time I spent on implementing\nbranch --diff.\n\n> Another difference that is somewhat irritating is that a SP that\n> leads a context line in a diff hunk that is introduced in the newer\n> iteration is often painted in reverse, and I do not know what aspect\n> of that single SP on these lines the branch-diff wants to pull my\n> attention to.\n\nRight. This is due to the fact that the diff does not realize that it\nlooks at pre/post images being diffs. And hence it does not understand\nthat the single space indicates a context line and is therefore not a\nwhitespace error before the tab.\n\n>     https://pasteboard.co/Hm9ujI7F.png\n> \n> In the picture, the three pre-context lines that are all indented by\n> a HT are prefixed by a SP, and that is prefixed by a '+' sign of the\n> outer diff.\n\nYep, that's exactly it.\n\nThe way tbdiff did it was to parse the diff and re-roll the coloring on\nits own. I am not really keen on doing that in `branch --diff`, too.\n\n> We can use --dual-color mode to unsee these but it is somewhat\n> frustrating not to know what the branch-diff program wants to tell\n> me by highlighting the single SPs on these lines.\n\nI was wondering from the get-go whether it would make sense to make\n--dual-color the default.\n\nBut now I wonder whether there is actually *any* use in `--color` without\n`--dual-color`.\n\nWhat do you think? Should I make the dual color mode the *only* color\nmode?\n\nCiao,\nDscho\n"},{"id":"348186","messageId":"nycvar.QRO.7.76.6.1805211152140.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CACsJy8ApDb_KTjoJgMuV_AJ+dQdGC0kgCnMcJXj5pMGdqMQCyA@mail.gmail.com","subject":"Re: [PATCH 01/18] Add a function to solve least-cost assignment problems","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-21T09:52:40Z","receivedAt":"2018-05-21T09:52:45Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Duy,\n\nOn Sun, 13 May 2018, Duy Nguyen wrote:\n\n> On Thu, May 3, 2018 at 5:30 PM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > +       /* reduction transfer */\n> > +       free_row = xmalloc(sizeof(int) * row_count);\n> > +       for (int i = 0; i < row_count; i++) {\n> \n> travis complains about this\n> \n> \n> hungarian.c: In function ‘compute_assignment’:\n> hungarian.c:47:11: error: redeclaration of ‘i’ with no linkage\n>   for (int i = 0; i < row_count; i++) {\n>            ^\n> hungarian.c:21:6: note: previous declaration of ‘i’ was here\n>   int i, j, phase;\n>       ^\n> hungarian.c:47:2: error: ‘for’ loop initial declarations are only\n> allowed in C99 mode\n>   for (int i = 0; i < row_count; i++) {\n>   ^\n> hungarian.c:47:2: note: use option -std=c99 or -std=gnu99 to compile your code\n\nYep, I fixed it locally already before seeing your mail. It is good to\nknow that some people pay attention, though!\n\nCiao,\nDscho"},{"id":"348189","messageId":"nycvar.QRO.7.76.6.1805211153370.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"3cefc6b3-3dbd-9cb1-20d0-193116191726@gmail.com","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-21T10:33:59Z","receivedAt":"2018-05-21T10:34:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Buga,\n\nOn Mon, 7 May 2018, Igor Djordjevic wrote:\n\n> On 07/05/2018 09:48, Jeff King wrote:\n> > \n> > > > Let's, please, not fall into the trap of polluting git-branch with\n> > > > utterly unrelated functionality, as has happened a few times with\n> > > > other Git commands. Let's especially not do so merely for the sake of\n> > > > tab-completion. git-branch is for branch management; it's not for\n> > > > diff'ing.\n> > >\n> > > I totally disagree. `git branch` is *the* command to work with branches.\n> > > Yes, you can manage branches. But you can also list them. And now you can\n> > > also compare them.\n> > \n> > One of the things I don't like about \"git branch --diff\" is that this\n> > feature is not _just_ about branches at all. E.g., I could do:\n> > \n> >   git tbdiff HEAD~10 HEAD~5 foo\n> > \n> > Or even:\n> > \n> >   git tbdiff v2.16.0 v2.17.0 my-rewritten-v2.17.0\n> > \n> > Those arguments really are just commitishes, not necessarily branches.\n> > One of the current interface rules for \"git branch\" is that the branch\n> > names we hand it are interpreted _exactly_ as branch names. You cannot\n> > \"git branch -m v2.16.0\", and there is no ambiguity in \"git branch -d\n> > foo\" if \"foo\" is both a tag and a branch.\n> > \n> > But this new mode does not fit the pattern at all.\n> > \n> > If we were to attach this to an existing command, I think it has more to\n> > do with \"diff\" than \"branch\". But I'm not sure we want to overload\n> > \"diff\" either (which has traditionally been about two endpoints, and\n> > does not really traverse at all, though arguably \"foo...bar\" is a bit of\n> > a cheat :) ).\n> > \n> > > > Of the suggestions thus far, Junio's git-topic-diff seems the least\n> > > > worse, and doesn't suffer from tab-completion problems.\n> > >\n> > > Except that this is too limited a view.\n> > \n> > Right, I agree with you. Topic branches are the intended use, but that's\n> > not what it _does_, and obviously it can be applied in other cases. So\n> > since \"branch\" is too specific, I think \"topic branch\" is even more so.\n> > \n> > It's really \"diff-history\" or something, I think. That's not very\n> > catchy, but I think the best name would imply that it was diffing a set\n> > of commits (so even \"diff-commit\" would not be right, because that again\n> > sounds like endpoints).\n> \n> This is exactly what I feel as well, thanks for concise and \n> to-the-point spelling out.\n> \n> From user interface perspective, I would expect something like this \n> to be possible (and natural):\n> \n> (1) git diff topic-v1...topic-v2\n\nNo, we cannot. The `git diff topic-v1...topic-v2` invocation has worked\nfor a looooong time, and does something very different.\n\nWe should not even allow ourselves to think of such a breakage.\n\n> (2) git diff --branch topic-v1...topic-v2\n\nFrom my point of view, `git diff --branch` indicates that I diff\n*branches*. Which is not really something that makes sense, and definitely\nnot what this command is about.\n\nWe are not comparing branches.\n\nWe are comparing versions of the same branch.\n\n> (1) is what we are all familiar with, providing a diff between two \n> revisions with focus on file changes, where (2) shifts focus to \n> history changes.\n> \n> It`s all still a comparison between two revisions (pointed to by \n> \"topic-v1\" and \"topic-v2\" branch heads in this specific example), but \n> it differs in what we are comparing - (1) set of files contained in \n> endpoints, or (2) set of revisions contained in (or \"leading to\") \n> endpoints.\n\nIt is very much not about comparing *two* revisions. It is very much about\ncomparing two *ranges of* revisions, and not just any ranges, no. Those\nranges need to be so related as to contain mostly identical changes.\nOtherwise, `git branch --diff` will spend a ton of time, just to come back\nwith a series of `-` lines followed by a series of `+` lines\n(figuratively, not literally). Which would be stupid, to spend that much\ntime on something that `git rev-list --left-right topic1...topic2` would\nhave computed a lot faster.\n\n> Hmm... what about `git diff --history`? :/ It does seem more \"true\" \n> to what it does, though I still like `git diff --branch` more \n> (catchier, indeed).\n\nIt certainly is catchier. But also a ton more puzzling.\n\nI do not want to compare histories, after all. That would be like saying:\nokay, topic1 and topic2 ended up at the same stage, but *how* did they\nget there?\n\nWhat I *want* to ask via the command implemented by this patch series is\nthe question: there was a set of patches previously, and now I have a set\nof revised patches, what changed?\n\nMost fellow German software engineers (who seem to have a knack for\nidiotically long variable/function names) would now probably suggest:\n\n\tgit compare-patch-series-with-revised-patch-series\n\nI hope you agree that that is better *and* worse than your suggestions,\ndepending from what angle you look at it: it is better because it\ndescribes what the command is *actually* doing. But it is much worse at\nthe same time because it is too long.\n\nCiao,\nDscho\n"},{"id":"348190","messageId":"nycvar.QRO.7.76.6.1805211238520.77@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"xmqq603zpkih.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-21T10:41:00Z","receivedAt":"2018-05-21T10:41:25Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Tue, 8 May 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > It would be easy to introduce, but I am wary about its usefulness.\n> > Unless you re-generate the branch from patches (which I guess you do a\n> > lot, but I don't), you are likely to compare incomplete patch series: say,\n> > when you call `git rebase -i` to reword 05/18's commit message, your\n> > command will only compare 05--18 of the patch series.\n> \n> Well that is exactly the point of that \"..@{1} @{1}..\", which turned\n> out to be very useful in practice at least for me when I am updating\n> a topic with \"rebase -i\", and then reviewing what I did with tbdiff.\n> \n> I do not want 01-04 in the above case as I already know I did not\n> touch them.\n\nAnd you are a seasoned veteran maintainer.\n\nTo the occasional contributor, this information is not obvious, and it is\nnot stored in their brain. It needs to be made explicit, which is why this\nhere command outputs those `abcdef = 012345` lines: it lists all the\ncommits, stating which ones are unchanged. In your 01-04 example, those\nlines would be of the form `abcdef = abcdef`, of course.\n\nCiao,\nDscho\n"},{"id":"348227","messageId":"CAGZ79kYcWuVorfk7eYjUuLi2XeMS8sPrJYE0OQmgiQi2NkuDZA@mail.gmail.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805211153370.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-05-21T17:56:47Z","receivedAt":"2018-05-21T17:56:56Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Hi Johannes,\n\n>> (2) git diff --branch topic-v1...topic-v2\n>\n> From my point of view, `git diff --branch` indicates that I diff\n> *branches*. Which is not really something that makes sense, and definitely\n> not what this command is about.\n>\n> We are not comparing branches.\n>\n> We are comparing versions of the same branch.\n\nI happen to have a messier workflow than you have, as I\ndevelop a \"resend\" of a topic in a new branch (or I have to\nrestore the old sent topic from the reflog).\n\nNow that I have the tool I also compare two branches,\nnamely, the branch that Junio queued\n(origin/base..origin/sb/intelligent-name) vs the resend\nthat I had locally (origin/base..foo).\n\nNext time I might compare Junios queued topic to the\nlocal format-patch'es that I already annotated.\n\nSo in a way this diffs different versions of a topic, \"diff-topic-versions\".\n\n>> It`s all still a comparison between two revisions (pointed to by\n>> \"topic-v1\" and \"topic-v2\" branch heads in this specific example), but\n>> it differs in what we are comparing - (1) set of files contained in\n>> endpoints, or (2) set of revisions contained in (or \"leading to\")\n>> endpoints.\n>\n> It is very much not about comparing *two* revisions.\n\nI wonder if we can make the tool more intelligent to take two revisions\nand it figures out the range by finding the base branch itself.\nProbably as a follow up.\n\n\n> It is very much about\n> comparing two *ranges of* revisions, and not just any ranges, no. Those\n> ranges need to be so related as to contain mostly identical changes.\n\nrange-diff, eh?\n\n> Most fellow German software engineers (who seem to have a knack for\n> idiotically long variable/function names) would now probably suggest:\n>\n>         git compare-patch-series-with-revised-patch-series\n\nor short:\n\n  revision-compare\n  compare-revs\n  com-revs\n\n  revised-diff\n  revise-diff\n  revised-compare\n\n  diff-revise\n\n> I hope you agree that that is better *and* worse than your suggestions,\n> depending from what angle you look at it: it is better because it\n> describes what the command is *actually* doing. But it is much worse at\n> the same time because it is too long.\n\nbtw, you think very much in terms of *patch series*, but there are workflows\nwithout patches (pull requests at Github et Al., changes in Gerrit),\nand I would think the output of the tool under discussion would still be\nuseful.\n\nIn [1] Junio gives his use case, it is \"before accepting them\", which could\nbe comparing an mbox or patch files against a branch, or first building\nup a local history on a detached head (and then wondering if to reset\nthe branch to the new history), which would be all in Git.\n\nThat use case still has 'patches' involved, but these are not the main\nselling point for the tool, as you could turn patches into commits before\nusing this tool.\n\n[1] https://public-inbox.org/git/xmqqvabh1ung.fsf@gitster-ct.c.googlers.com/\n\nThanks,\nStefan\n"},{"id":"348245","messageId":"20180521202414.GA14250@sigill.intra.peff.net","threadId":"48405","inReplyTo":"CAGZ79kYcWuVorfk7eYjUuLi2XeMS8sPrJYE0OQmgiQi2NkuDZA@mail.gmail.com","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-21T20:24:14Z","receivedAt":"2018-05-21T20:24:19Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, May 21, 2018 at 10:56:47AM -0700, Stefan Beller wrote:\n\n> > It is very much about\n> > comparing two *ranges of* revisions, and not just any ranges, no. Those\n> > ranges need to be so related as to contain mostly identical changes.\n> \n> range-diff, eh?\n> \n> > Most fellow German software engineers (who seem to have a knack for\n> > idiotically long variable/function names) would now probably suggest:\n> >\n> >         git compare-patch-series-with-revised-patch-series\n> \n> or short:\n> \n>   revision-compare\n>   compare-revs\n>   com-revs\n> \n>   revised-diff\n>   revise-diff\n>   revised-compare\n> \n>   diff-revise\n\nI still like \"range diff\", but I think something around \"revise\" is a\ngood line of thought, too. Because it implies that we expect the two\nranges to be composed of almost-the-same commits.\n\nThat leads to another use case where I think focusing on topic branches\n(or even branches at all) would be a misnomer. Imagine I cherry-pick a\nbunch of commits with:\n\n  git cherry-pick -10 $old_commit\n\nI might then want to see how the result differs with something like:\n\n  git range-diff $old_commit~10..$old_commit HEAD~10..HEAD\n\nI wouldn't think of this as a topic-branch operation, but just as\ncomparing two sequences of commits. I guess \"revise\" isn't strictly\naccurate here either, as I'm not revising. But I do assume the two\nranges share some kind of mapping of patches.\n\n-Peff\n\nPS I wish there were a nicer syntax to do that. Perhaps\n   \"git range-diff -10 $old_commit HEAD\" could work, though occasionally\n   the two ranges are not the same length (e.g., if you ended up\n   skipping one of the cherry-picked commits). Anyway, those kind of\n   niceties can easily come later on top. :)\n"},{"id":"348250","messageId":"20180521214057.GB125693@google.com","threadId":"48405","inReplyTo":"20180521202414.GA14250@sigill.intra.peff.net","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Brandon Williams","fromEmail":"bmwill@google.com","sentAt":"2018-05-21T21:40:57Z","receivedAt":"2018-05-21T21:41:27Z","isPatch":true,"sender":{"key":"bwilliams.eng@gmail.com","avatar":null},"body":"On 05/21, Jeff King wrote:\n> On Mon, May 21, 2018 at 10:56:47AM -0700, Stefan Beller wrote:\n> \n> > > It is very much about\n> > > comparing two *ranges of* revisions, and not just any ranges, no. Those\n> > > ranges need to be so related as to contain mostly identical changes.\n> > \n> > range-diff, eh?\n> > \n> > > Most fellow German software engineers (who seem to have a knack for\n> > > idiotically long variable/function names) would now probably suggest:\n> > >\n> > >         git compare-patch-series-with-revised-patch-series\n> > \n> > or short:\n> > \n> >   revision-compare\n> >   compare-revs\n> >   com-revs\n> > \n> >   revised-diff\n> >   revise-diff\n> >   revised-compare\n> > \n> >   diff-revise\n\nI haven't really been following all of the discussion but from what I\ncan tell the point of this command is to generate a diff based on two\ndifferent versions of a series, so why not call it 'series-diff'? :)\n\n-- \nBrandon Williams\n"},{"id":"348251","messageId":"CAGZ79kbZm4B76Wg7yBs_NNEZh85HJP5QgKPogo4PU42WgDowBg@mail.gmail.com","threadId":"48405","inReplyTo":"20180521214057.GB125693@google.com","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-05-21T21:48:56Z","receivedAt":"2018-05-21T21:49:03Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, May 21, 2018 at 2:40 PM, Brandon Williams <bmwill@google.com> wrote:\nrevised-compare\n>> >\n>> >   diff-revise\n>\n> I haven't really been following all of the discussion but from what I\n> can tell the point of this command is to generate a diff based on two\n> different versions of a series, so why not call it 'series-diff'? :)\n\nUpon mentioning series-diff, I misheard Brandon and thought he proposed\n\n   serious-diff\n\n:-)\nStefan\n"},{"id":"348253","messageId":"20180521215216.GB16623@sigill.intra.peff.net","threadId":"48405","inReplyTo":"20180521214057.GB125693@google.com","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-21T21:52:16Z","receivedAt":"2018-05-21T21:52:23Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, May 21, 2018 at 02:40:57PM -0700, Brandon Williams wrote:\n\n> > > > Most fellow German software engineers (who seem to have a knack for\n> > > > idiotically long variable/function names) would now probably suggest:\n> > > >\n> > > >         git compare-patch-series-with-revised-patch-series\n> > > \n> > > or short:\n> > > \n> > >   revision-compare\n> > >   compare-revs\n> > >   com-revs\n> > > \n> > >   revised-diff\n> > >   revise-diff\n> > >   revised-compare\n> > > \n> > >   diff-revise\n> \n> I haven't really been following all of the discussion but from what I\n> can tell the point of this command is to generate a diff based on two\n> different versions of a series, so why not call it 'series-diff'? :)\n\nThat's OK with me, though I prefer \"range\" as I think we use that term\nelsewhere (\"series\" is usually part of \"patch series\", but many people\ndo not use a workflow with that term).\n\n-Peff\n"},{"id":"348276","messageId":"xmqqmuwszcs4.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1805211144340.77@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-05-22T01:42:35Z","receivedAt":"2018-05-22T01:42:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n>> In the picture, the three pre-context lines that are all indented by\n>> a HT are prefixed by a SP, and that is prefixed by a '+' sign of the\n>> outer diff.\n>\n> Yep, that's exactly it.\n>\n> The way tbdiff did it was to parse the diff and re-roll the coloring on\n> its own. I am not really keen on doing that in `branch --diff`, too.\n\nAre you saying that these are \"whitespace errors\" getting painted?\nIt is somewhat odd because my whitespace errors are configured to be\npainted in \"reverse blue\".  Perhaps you are forcing the internal\ndiff not to pay attention to the end-user configuration---which\nactually does make sense, as reusing of \"git diff\" to take \"diff of\ndiff\" is a mere implementation detail.\n\nIn any case, the \"whitespace errors\" in \"diff of diff\" are mostly\ndistracting.\n\n> I was wondering from the get-go whether it would make sense to make\n> --dual-color the default.\n>\n> But now I wonder whether there is actually *any* use in `--color` without\n> `--dual-color`.\n>\n> What do you think? Should I make the dual color mode the *only* color\n> mode?\n\nSorry but you are asking a good question to a wrong person.\n\nI normally do not seek much useful information in colored output, so\nmy reaction would not be very useful.  Non dual-color mode irritates\nme due to the false whitespace errors, and dual-color mode irritates\nme because it looks sufficiently different from tbdiff output that I\nam used to see.\n\n"},{"id":"348277","messageId":"xmqqin7gzbkb.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"20180521215216.GB16623@sigill.intra.peff.net","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-05-22T02:08:52Z","receivedAt":"2018-05-22T02:08:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n>> I haven't really been following all of the discussion but from what I\n>> can tell the point of this command is to generate a diff based on two\n>> different versions of a series, so why not call it 'series-diff'? :)\n>\n> That's OK with me, though I prefer \"range\" as I think we use that term\n> elsewhere (\"series\" is usually part of \"patch series\", but many people\n> do not use a workflow with that term).\n\nFWIW, I am OK with either, with a bit of preference to \"range\" over\n\"series\".  As long as this stays to be an independent command (as\nopposed to be made into a new mode to existing command) and the\ncommand name is not overly hard to type, I am OK with anything ;-)\n"},{"id":"348296","messageId":"87in7f9aym.fsf@evledraar.gmail.com","threadId":"48405","inReplyTo":"20180508034429.GA7242@sigill.intra.peff.net","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2018-05-22T11:38:41Z","receivedAt":"2018-05-22T11:39:34Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, May 08 2018, Jeff King wrote:\n\n> On Mon, May 07, 2018 at 03:24:59PM -0700, Stefan Beller wrote:\n>\n>> Hence I propose \"git range-diff\", similar to topic-diff, that\n>> was proposed earlier.\n>>\n>> * it \"diffs ranges\" of commits.\n>> * it can also deal with out-of-git things like patch series,\n>>   but that is a mere by product and may not be desired.\n>>   Just like git-diff can also compare two files outside a git\n>>   repo, that would not be a good use case.\n>>   Keep the name Git-centric!\n>> * it autocompletes well.\n>\n> FWIW, I like this by far of all of the suggested names.\n\nI agree, \"range-diff\" is the best one mentioned so far.\n"},{"id":"348565","messageId":"CAGZ79kZg-24OtWp-qk4gAyU3O8vJBdDH_maTERqzqHSE86_fqg@mail.gmail.com","threadId":"48405","inReplyTo":"87in7f9aym.fsf@evledraar.gmail.com","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-05-25T22:06:47Z","receivedAt":"2018-05-25T22:06:53Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Johannes,\n\nOn IRC you wrote:\n<dscho> And BTW this is not bike-shedding to me. Discussing the name\nof a variable, or indentation, or line wrapping, is. But improving the\nuser experience is important. We *suck* on that, historically, and I\ndo want to break with that habit.\n...\n<dscho> avar, _ikke_: so a colleague of mine whose opinion on naming I\nrespect more than all Git developers combined *also* came up with the\nterm `range-diff`, independently.\n...\n<dscho> Yes, you are looking at two ranges. But not *any* two ranges.\n*That* is my point.\n\nSo I sat back and want to try again;\n\nIIUC your dislike for \"range-diff\" boils down to:\n(A) it doesn't diff any arbitrary range, as the output would become\nvery cumbersome and hard to understand,\n(B) it is not a good intuitive name for users, as they would not think\nof range-diff when they'd want to have this feature.\n\nRegarding (A), I think the same can be said about input to the diff\nmachinery, e.g. 'git diff v2.0.0 v2.17.0' is just very much text, and\nit is hardly useful (except as a patch fed to the machine).\n\nOver time there were added tons of options that make the diff output\neasier to digest, e.g. additional pathspecs to restrict to a sub tree or\nignoring certain things (white spaces mostly), such that\n'git diff -w v2.0.0 v2.17.0 -- refs.h' is easier for a human to grok.\n\nRegarding (B), I agree, but blame it on the nature of an open\nsource project that provides a toolbox. So the way a user is\ngoing to discover this feature is via stackoverflow or via\nasking a coworker or finding the example output somewhere.\n\nI think that last point could be part of the feedback:\ngit-diff has prominently hints at its name via \"diff --git ...\"\nin the first line of its output, so maybe the output of this feature\nalso wants to name itself?\n\nOther thoughts:\nWe could go down the route and trying to find a best possible\ntechnical name, for which I could offer:\n\n  revision-walk-difference\n  revwalk-diff\n\nAs that literally describes the output: two rev walks are\nperformed and then those outputs of the rev walks is diffed.\nBased off these technicals we could get more creative:\n\n  redo-rev-walk-spot-the-difference\n  re-walk-spot\n  retravel-spot\n  spot-diff\n\nBut I think all these do not address the feedback (B).\n\n\"What would a user find intuitive?\"; I personally thought\na bit about how I discovered cherry-pick. I just took it as\na given name, without much thought, as I discovered it\nby tell tale, not looking for it in the docs. It sort of made\nsense as a command that I learned earlier about,\n\"interactive rebase\", also has had the \"pick\" command,\nsuch that \"picking\" made sense. I think I retroactively\nmade sense of the \"cherry\" part. Now I tried to find it\nin the mailing list archive and actually learn about its origin,\nbut no good stories are found there.\n\nFor what the user might find most useful, I just looked\nat other tools in Gerrits landscape and there the expectation\nseems that you upload your code first and do the diff of the different\npatches serverside. I think the same holds for Github or other\nbranch based reviewing systems. You can force push the\nbranch that is pull requested and the web UI somehow makes\nsense of it.\n\nThat leads me to the (weak) conclusion of branch-diff or tbdiff\nto be useful most for patch based / mailing list based workflows\nas there is no magic server helping you out.\n\nSearching for \"kernel +tbdiff\" to find the kernel devs using tbdiff\ngave me no results, so I may be mistaken there.\n\nTrying to find \"interdiffs\" (for the lack of a better name) between\npatches on the kernel mailing list also is not obvious to the uninitiated.\n\nSo for the various workflows, I could come up with\n\n  change-diff\n  pullrequest-diff\n  patch-series-diff\n\nbut we do not look at diffs, rather we only use this tool to work on\nincremental things, so maybe instead:\n\n  change-history\n  pullrequest-history\n  patch-series-evolution\n\nNote how these are 3 suggestions, one for each major workflow,\nand I'd *REALLY* would want to have a tool that is agnostic to the\nworkflow on top (whether you use pull requests or Gerrit changes),\nbut now I would like to step back and remind us that this tool\nis only mostly used for viewing the evolution of your new thing,\nbut it can also be very useful to inspect non-new things.\n(backported patches to maint, or some -stable branch)\n\nOr rather: We do not know the major use case yet. Sure\nI will use it in my cover letter and that is on my mind now,\nbut I think there are other use cases that are not explored\nyet, so we should rather make the naming decision based\noff of technicals rather than anticipated use case and user\ndiscovery methods.\n\nI hope this is actually useful feedback on the naming discovery.\n\nThanks,\nStefan\n"},{"id":"348580","messageId":"CAA8fPEk+49A=MFpOAL1Y+TLdX9twx1SGdNqGk5a3DSEL7t3BQw@mail.gmail.com","threadId":"48405","inReplyTo":"CAA8fPEkNjy+ETz4Mx+C2kUfLjLzR9uuOmO3GfN48ZH1SwyfE1A@mail.gmail.com","subject":"Fwd: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Øyvind Rønningstad","fromEmail":"ronningstad@gmail.com","sentAt":"2018-05-26T06:15:24Z","receivedAt":"2018-05-26T06:15:28Z","isPatch":true,"sender":{"key":"ronningstad@gmail.com","avatar":null},"body":"Just want to throw my support in for range-diff since ranges is what\nyou pass to the command.\n\nAlternatively, diff-diff since that's how I've crudely tried to\naccomplish this before.\n\ngit diff A..B > diff1\ngit diff C..D > diff2\nwinmerge diff1 diff2\n"},{"id":"348808","messageId":"20180530135505.9569-1-szeder.dev@gmail.com","threadId":"48405","inReplyTo":"3f51970cbc44bfe34133c48c0844ed3723e83808.1525448066.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 01/18] Add a function to solve least-cost assignment problems","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2018-05-30T13:55:05Z","receivedAt":"2018-05-30T13:55:18Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"> The Jonker-Volgenant algorithm was implemented to answer questions such\n> as: given two different versions of a topic branch (or iterations of a\n> patch series), what is the best pairing of commits/patches between the\n> different versions?\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  Makefile    |   1 +\n>  hungarian.c | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n>  hungarian.h |  19 +++++\n\n(Nit: I personally don't really like these filenames, I know they will\nsurprise and distract me every time I notice them for years to come... :)\n\n> +int compute_assignment(int column_count, int row_count, double *cost,\n> +\t\t       int *column2row, int *row2column)\n> +{\n> +\tdouble *v = xmalloc(sizeof(double) * column_count), *d;\n> +\tint *free_row, free_count = 0, saved_free_count, *pred, *col;\n> +\tint i, j, phase;\n\n<snip>\n\n> +\tfor (free_count = 0; free_count < saved_free_count; free_count++) {\n> +\t\tint i1 = free_row[free_count], low = 0, up = 0, last, k;\n> +\t\tdouble min, c, u1;\n\n<snip most of the loop's body>\n\n> +\t\t/* augmentation */\n> +\t\tdo {\n> +\t\t\tif (j < 0)\n> +\t\t\t\tBUG(\"negative j: %d\", j);\n> +\t\t\ti = pred[j];\n> +\t\t\tcolumn2row[j] = i;\n> +\t\t\tk = j;\n> +\t\t\tj = row2column[i];\n> +\t\t\trow2column[i] = k;\n\nCoccinelle suggests to replace the last three lines above with:\n\n  SWAP(j, row2column[i]);\n\nI think it's right, using the SWAP macro makes the resulting code not\nonly shorter and clearer, but it also saves the reader from thinking\nabout whether it's important to set 'k = j' (I think it's not), or 'k'\nis just used here in lieu of a dedicated 'tmp' variable (I think it\nis).\n\n> +\t\t} while (i1 != i);\n> +\t}\n> +\n"},{"id":"348813","messageId":"CAGZ79kZ77qBuSDGBJa5b1AvKLBSOOnTad_UXm9EP0aJSrmEohw@mail.gmail.com","threadId":"48405","inReplyTo":"20180530135505.9569-1-szeder.dev@gmail.com","subject":"Re: [PATCH v2 01/18] Add a function to solve least-cost assignment problems","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-05-30T16:14:06Z","receivedAt":"2018-05-30T16:14:11Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Wed, May 30, 2018 at 6:55 AM, SZEDER Gábor <szeder.dev@gmail.com> wrote:\n>> The Jonker-Volgenant algorithm was implemented to answer questions such\n>> as: given two different versions of a topic branch (or iterations of a\n>> patch series), what is the best pairing of commits/patches between the\n>> different versions?\n>>\n>> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n>> ---\n>>  Makefile    |   1 +\n>>  hungarian.c | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n>>  hungarian.h |  19 +++++\n>\n> (Nit: I personally don't really like these filenames, I know they will\n> surprise and distract me every time I notice them for years to come... :)\n\nGood point. I remember my initial reaction to the file names was expecting\nsome hungarian notation, which totally didn't make sense, so I refrained from\ncommenting. Searching the web for the algorithm, maybe 'lapjv.c' is adequate?\n(short for \"Linear Assignment Problem Jonker Volgenant\") Matlab has a function\nnamed lapjv solving the same problem, so it would fall in line with the outside\nworld.\n\nOut of interest, why is it called hungarian in the first place? (I presume that\ncomes from some background of DScho in image processing or such, so the\nthe answer will be interesting for sure:)\n"},{"id":"348856","messageId":"20180530232837.GD671367@genre.crustytoothpaste.net","threadId":"48405","inReplyTo":"CAGZ79kZ77qBuSDGBJa5b1AvKLBSOOnTad_UXm9EP0aJSrmEohw@mail.gmail.com","subject":"Re: [PATCH v2 01/18] Add a function to solve least-cost assignment problems","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2018-05-30T23:28:38Z","receivedAt":"2018-05-30T23:28:48Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On Wed, May 30, 2018 at 09:14:06AM -0700, Stefan Beller wrote:\n> Good point. I remember my initial reaction to the file names was expecting\n> some hungarian notation, which totally didn't make sense, so I refrained from\n> commenting. Searching the web for the algorithm, maybe 'lapjv.c' is adequate?\n> (short for \"Linear Assignment Problem Jonker Volgenant\") Matlab has a function\n> named lapjv solving the same problem, so it would fall in line with the outside\n> world.\n> \n> Out of interest, why is it called hungarian in the first place? (I presume that\n> comes from some background of DScho in image processing or such, so the\n> the answer will be interesting for sure:)\n\nI think it's because tbdiff uses the hungarian Python module, which\nimplements the Hungarian method, also known as the Kuhn-Munkres\nalgorithm, for solving the linear assignment problem.  This is the\nJonker-Volgenant algorithm, which solves the same problem.  It's faster,\nbut less tolerant.\n\nAt least this is what I just learned after about ten minutes of\nsearching.\n-- \nbrian m. carlson: Houston, Texas, US\nOpenPGP: https://keybase.io/bk2204\n"},{"id":"348895","messageId":"nycvar.QRO.7.76.6.1805311414440.82@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180530232837.GD671367@genre.crustytoothpaste.net","subject":"Re: [PATCH v2 01/18] Add a function to solve least-cost assignment problems","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-05-31T12:19:04Z","receivedAt":"2018-05-31T12:19:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Brian,\n\nOn Wed, 30 May 2018, brian m. carlson wrote:\n\n> On Wed, May 30, 2018 at 09:14:06AM -0700, Stefan Beller wrote:\n> > Good point. I remember my initial reaction to the file names was\n> > expecting some hungarian notation, which totally didn't make sense, so\n> > I refrained from commenting. Searching the web for the algorithm,\n> > maybe 'lapjv.c' is adequate?  (short for \"Linear Assignment Problem\n> > Jonker Volgenant\") Matlab has a function named lapjv solving the same\n> > problem, so it would fall in line with the outside world.\n> > \n> > Out of interest, why is it called hungarian in the first place? (I\n> > presume that comes from some background of DScho in image processing\n> > or such, so the the answer will be interesting for sure:)\n> \n> I think it's because tbdiff uses the hungarian Python module, which\n> implements the Hungarian method, also known as the Kuhn-Munkres\n> algorithm, for solving the linear assignment problem.  This is the\n> Jonker-Volgenant algorithm, which solves the same problem.  It's faster,\n> but less tolerant.\n> \n> At least this is what I just learned after about ten minutes of\n> searching.\n\nYou learned well.\n\nThe Assignment Problem (or \"Linear Assignment Problem\") is generally\nsolved by the Hungarian algorithm. I forgot why it is called that way.\nKuhn-Munkres came up with a simplification of the algorithm IIRC but it\nstill is O(N^4). Then Jonker-Volgenant took a very different approach that\nsomehow results in O(N^3). It's been *years* since I studied both\nimplementations, so I cannot really explain what they do, and how the\nlatter achieves its order-of-magnitude better performance.\n\nAnd after reading these mails, I agree that the name \"hungarian\" might be\nconfusing.\n\nI also think that \"lapjv\" is confusing. In general, I try to let Matlab\nconventions inform on my naming as little as possible, and I find my\nnaming fu a lot better for it.\n\nSo in this case, how about `linear-assignment.c`?\n\nCiao,\nDscho\n"},{"id":"348986","messageId":"nycvar.QRO.7.76.6.1806011013340.82@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAA8fPEkNjy+ETz4Mx+C2kUfLjLzR9uuOmO3GfN48ZH1SwyfE1A@mail.gmail.com","subject":"Re: [PATCH v2 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-06-01T08:15:25Z","receivedAt":"2018-06-01T08:15:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi team,\n\nespecially Stefan: your thorough investigation about a better name than\nrange-diff gives me confidence that my decision to retract my objection\nagainst has merit: it seems to be by far the one name that everybody but\nme agrees on. And I can adapt easily.\n\nOn Sat, 26 May 2018, Øyvind Rønningstad wrote:\n\n> Just want to throw my support in for range-diff since ranges is what you\n> pass to the command.\n\n`range-diff` it is.\n\nCiao,\nDscho"},{"id":"348987","messageId":"nycvar.QRO.7.76.6.1806011016150.82@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180508021029.GC26695@zaya.teonanacatl.net","subject":"Re: [PATCH v2 12/18] branch-diff: use color for the commit pairs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-06-01T08:17:26Z","receivedAt":"2018-06-01T08:17:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Todd,\n\nOn Mon, 7 May 2018, Todd Zullinger wrote:\n\n> Johannes Schindelin wrote:\n> > \n> > On Sat, 5 May 2018, Todd Zullinger wrote:\n> > \n> >>> @@ -430,6 +451,8 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n> >>>  \tstruct string_list branch1 = STRING_LIST_INIT_DUP;\n> >>>  \tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n> >>>  \n> >>> +\tgit_diff_basic_config(\"diff.color.frag\", \"magenta\", NULL);\n> >>> +\n> >>>  \tdiff_setup(&diffopt);\n> >>>  \tdiffopt.output_format = DIFF_FORMAT_PATCH;\n> >>>  \tdiffopt.flags.suppress_diff_headers = 1;\n> >> \n> >> Should this also (or only) check color.diff.frag?\n> > \n> > This code is not querying diff.color.frag, it is setting it. Without\n> > any way to override it.\n> > \n> > Having thought about it longer, and triggered by Peff's suggestion to\n> > decouple the \"reverse\" part from the actual color, I fixed this by\n> > \n> > - *not* setting .frag to magenta,\n> > \n> > - using the reverse method also to mark outer *hunk headers* (not only\n> > the outer -/+ markers).\n> > \n> > - actually calling git_diff_ui_config()...\n> \n> Excellent.  That seems to work nicely now, respecting the\n> color.diff.<slot> config.\n> \n> > The current work in progress can be pulled as `branch-diff` from\n> > https://github.com/dscho/git, if I could ask you to test?\n> \n> While the colors and 'branch --diff' usage seem to work\n> nicely, I found that with 4ac3413cc8 (\"branch-diff: left-pad\n> patch numbers\", 2018-05-05), 'git branch' itself is broken.\n> \n> Running 'git branch' creates a branch named 'branch'.\n> Calling 'git branch --list' shows only 'branch' as the only\n> branch.\n> \n> I didn't look too closely, but I'm guessing that the argv\n> handling is leaving the 'branch' argument in place where it\n> should be stripped?\n> \n> This unsurprisingly breaks a large number of tests. :)\n\nYou will be delighted to learn that all of this is now moot, as I renamed\nthe command to `range-diff`, as this is what the wisdom of the crowd\nchose.\n\nCiao,\nJohannes\n"},{"id":"348988","messageId":"nycvar.QRO.7.76.6.1806011017560.82@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"8876f376-db86-c3e1-b97d-9e33506f2df2@ramsayjones.plus.com","subject":"Re: [PATCH 02/18] Add a new builtin: branch-diff","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-06-01T08:23:09Z","receivedAt":"2018-06-01T08:23:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Ramsay,\n\nOn Wed, 9 May 2018, Ramsay Jones wrote:\n\n> On 05/05/18 20:41, Johannes Schindelin wrote:\n> [snip]\n> \n> [Sorry for the late reply - still catching up after (long weekend)\n> UK public holiday]\n> \n> > Well, what I would want to do is let the cloud work for me. By adding an\n> > automated build to my Visual Studio Team Services (VSTS) account, of\n> > course, as I have \"cloud privilege\" (i.e. I work in the organization\n> > providing the service, so I get to play with all of it for free).\n> > \n> > So I really don't want to build sparse every time a new revision needs to\n> > be tested (whether that be from one of my branches, an internal PR for\n> > pre-review of patches to be sent to the mailing list, or maybe even `pu`\n> > or the personalized branches on https://github.com/gitster/git).\n> > \n> > I really would need a ready-to-install sparse, preferably as light-weight\n> > as possible (by not requiring any dependencies outside what is available\n> > in VSTS' hosted Linux build agents.\n> > \n> > Maybe there is a specific apt source for sparse?\n> \n> Not that I'm aware of, sorry! :(\n> \n> [release _source_ tar-balls are available, but that's not\n> what you are after, right?]\n\nNo, that's not what I am after, because my goal is not to build sparse\nevery time somebody pushes a new commit.\n\nI want to use the Hosted Agents of Visual Studio Team Services (because I\nhave cloud privilege, as part of the team working on VSTS, I can use them\nfor free, as much as I want, within reason of course). And yes, I want to\nuse the Hosted Linux Agents for the sparse job.\n\nSo I cannot compile sparse and then install it into an agent. Those agents\nare recycled after every build, so that every new build starts from a\nclean slate.\n\nIf you have anything in the way of providing some easily-consumable\npackage, that would do the job. I guess I could build sparse.deb via\ncheckinstall in one VSTS build, offer it as artifact, and consume that\nfrom the VSTS job that uses it on the Git branches.\n\nCould you point me to a robus, yet current revision of sparse (and ideally\nprovide me with precise instructions how to build it so that I do not have\nto hunt for that information)?\n\nCiao,\nDscho\n"},{"id":"348989","messageId":"nycvar.QRO.7.76.6.1806011023430.82@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"xmqqmuwszcs4.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-06-01T08:28:04Z","receivedAt":"2018-06-01T08:28:14Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Tue, 22 May 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> >> In the picture, the three pre-context lines that are all indented by\n> >> a HT are prefixed by a SP, and that is prefixed by a '+' sign of the\n> >> outer diff.\n> >\n> > Yep, that's exactly it.\n> >\n> > The way tbdiff did it was to parse the diff and re-roll the coloring on\n> > its own. I am not really keen on doing that in `branch --diff`, too.\n> \n> Are you saying that these are \"whitespace errors\" getting painted?\n\nIndentation errors, to be precise. Yes.\n\n> It is somewhat odd because my whitespace errors are configured to be\n> painted in \"reverse blue\".  Perhaps you are forcing the internal\n> diff not to pay attention to the end-user configuration---which\n> actually does make sense, as reusing of \"git diff\" to take \"diff of\n> diff\" is a mere implementation detail.\n\nIt may have been the case before I introduced that call to\ngit_diff_ui_config(), but that happened after -v2, and I did not\ncontribute -v3 yet.\n\n> In any case, the \"whitespace errors\" in \"diff of diff\" are mostly\n> distracting.\n\nPrecisely. That's why I tried to suppress them in --dual-color mode.\n\nI did not try to suppress them in --color (--no-dual-color) mode, as I\nfind that mode pretty useless.\n\n> > I was wondering from the get-go whether it would make sense to make\n> > --dual-color the default.\n> >\n> > But now I wonder whether there is actually *any* use in `--color` without\n> > `--dual-color`.\n> >\n> > What do you think? Should I make the dual color mode the *only* color\n> > mode?\n> \n> Sorry but you are asking a good question to a wrong person.\n> \n> I normally do not seek much useful information in colored output, so\n> my reaction would not be very useful.  Non dual-color mode irritates\n> me due to the false whitespace errors, and dual-color mode irritates\n> me because it looks sufficiently different from tbdiff output that I\n> am used to see.\n\nDo you use --dual-color normally?\n\nI derive *a ton* of information from the colored diff. It really helps me\nnavigate the output of range-diff very quickly.\n\nI ask whether you use --dual-color because in that case I would consider\nscrapping the way I handle color right now and try to imitate tbdiff's\nway. But that would lose whitespace error coloring *altogether*. So I, for\none, would be unable to see where a subsequent patch series iteration\nfixes whitespace errors of a previous iteration.\n\nCiao,\nDscho\n"},{"id":"351596","messageId":"4a68b95ce2a6463708c92d1bcc0208352c14f836.1530617166.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 20/20] range-diff: make --dual-color the default mode","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-06-30T20:41:41Z","receivedAt":"2018-07-03T11:26:48Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nAfter using this command extensively for the last two months, this\ndeveloper came to the conclusion that even if the dual color mode still\nleaves a lot of room for confusion what was actually changed, the\nnon-dual color mode is substantially worse in that regard.\n\nTherefore, we really want to make the dual color mode the default.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-range-diff.txt       | 13 ++++++++-----\n builtin/range-diff.c                   | 10 ++++++----\n contrib/completion/git-completion.bash |  2 +-\n 3 files changed, 15 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\nindex 189236cc6..02d33ac43 100644\n--- a/Documentation/git-range-diff.txt\n+++ b/Documentation/git-range-diff.txt\n@@ -31,11 +31,14 @@ all of their ancestors have been shown.\n \n OPTIONS\n -------\n---dual-color::\n-\tWhen the commit diffs differ, recreate the original diffs'\n-\tcoloring, and add outer -/+ diff markers with the *background*\n-\tbeing red/green to make it easier to see e.g. when there was a\n-\tchange in what exact lines were added.\n+--no-dual-color::\n+\tWhen the commit diffs differ, `git range-diff` recreates the\n+\toriginal diffs' coloring, and add outer -/+ diff markers with\n+\tthe *background* being red/green to make it easier to see e.g.\n+\twhen there was a change in what exact lines were added. This is\n+\tknown to `range-diff` as \"dual coloring\". Use `--no-dual-color`\n+\tto revert to color all lines according to the outer diff markers\n+\t(and completely ignore the inner diff when it comes to color).\n \n --creation-factor=<percent>::\n \tSet the creation/deletion cost fudge factor to `<percent>`.\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex e8f7fe452..6cee0c73a 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -20,11 +20,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n {\n \tint creation_factor = 60;\n \tstruct diff_options diffopt = { NULL };\n-\tint dual_color = 0;\n+\tint simple_color = -1;\n \tstruct option options[] = {\n \t\tOPT_INTEGER(0, \"creation-factor\", &creation_factor,\n \t\t\t    N_(\"Percentage by which creation is weighted\")),\n-\t\tOPT_BOOL(0, \"dual-color\", &dual_color,\n+\t\tOPT_BOOL(0, \"no-dual-color\", &simple_color,\n \t\t\t    N_(\"color both diff and diff-between-diffs\")),\n \t\tOPT_END()\n \t};\n@@ -53,8 +53,10 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \targc = j;\n \tdiff_setup_done(&diffopt);\n \n-\tif (dual_color) {\n-\t\tdiffopt.use_color = 1;\n+\tif (simple_color < 1) {\n+\t\tif (!simple_color)\n+\t\t\t/* force color when --dual-color was used */\n+\t\t\tdiffopt.use_color = 1;\n \t\tdiffopt.flags.dual_color_diffed_diffs = 1;\n \t}\n \ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 402490673..e35fc28fc 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1981,7 +1981,7 @@ _git_range_diff ()\n   case \"$cur\" in\n   --*)\n           __gitcomp \"\n-\t  \t--creation-factor= --dual-color\n+\t  \t--creation-factor= --no-dual-color\n                   $__git_diff_common_options\n                   \"\n           return\n-- \ngitgitgadget\n"},{"id":"351603","messageId":"pull.1.v3.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"cover.1525448066.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 00/20] Add `range-diff`, a `tbdiff` lookalike","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-03T11:26:06Z","receivedAt":"2018-07-03T11:27:25Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The incredibly useful `git-tbdiff` tool to compare patch series (say, to see what changed between two iterations sent to the Git mailing list) is slightly less useful for this developer due to the fact that it requires the `hungarian` and `numpy` Python packages which are for some reason really hard to build in MSYS2. So hard that I even had to give up, because it was simply easier to reimplement the whole shebang as a builtin command.\n\nThe project at https://github.com/trast/tbdiff seems to be dormant, anyway. Funny (and true) story: I looked at the open Pull Requests to see how active that project is, only to find to my surprise that I had submitted one in August 2015, and that it was still unanswered let alone merged.\n\nWhile at it, I forward-ported AEvar's patch to force `--decorate=no` because `git -p tbdiff` would fail otherwise.\n\nSide note: I work on implementing branch-diff not only to make life easier for reviewers who have to suffer through v2, v3, ... of my patch series, but also to verify my changes before submitting a new iteraion. And also, maybe even more importantly, I plan to use it to verify my merging-rebases of Git for\nWindows (for which I previously used to redirect the pre-rebase/post-rebase diffs vs upstream and then compare them using `git diff --no-index`). And of course any interested person can see what changes were necessary e.g. in the merging-rebase of Git for Windows onto v2.17.0 by running a command like:\n\n```sh\n        base=^{/Start.the.merging-rebase}\n        tag=v2.17.0.windows.1\n        pre=$tag$base^2\n        git branch-diff --dual-color $pre$base..$pre $tag$base..$tag\n```\n\nThe --dual-color mode will identify the many changes that are solely due to different diff context lines (where otherwise uncolored lines start with a background-colored -/+ marker), i.e. merge conflicts I had to resolve.\n\nChanges since v2:\n\n- Right-aligned the patch numbers in the commit pairs.\n- Used ALLOC_ARRAY() in hungarian.c instead of xmalloc(sizeof()*size).\n- Changed compute_assignment()s return type from int to void, as it always succeeds.\n- Changed the Hungarian Algorithm to use an integer cost matrix.\n- Changed the --creation-weight <double> option to --creation-factor <percent> where <percent> is an integer.\n- Retitled 1/19 and 2/19 to better conform with the current conventions, as pointed out (and suggested) by Junio.\n- Shut up Coverity, and at the same time avoided passing the unnecessary `i` and `j` parameters to output_pair_header().\n- Removed support for the `--no-patches` option: we inherit diff_options' support for `-s` already (and much more).\n- Removed the ugly `_INV` enum values, and introduced a beautiful GIT_COLOR_REVERSE instead. This way, whatever the user configured as color.diff.new (or .old) will be used in reverse in the dual color mode.\n- Instead of overriding the fragment header color, the dual color mode will now reverse the \"outer\" fragment headers, too.\n- Turned the stand-alone branch-diff command into the `--diff` option of `git branch`. Adjusted pretty much *all* commit messages to account for this. This change should no longer be visible: see below.\n- Pretty much re-wrote the completion, to support the new --diff mode of git-branch. See below: it was reverted for range-diff.\n- Renamed t7910 to t3206, to be closer to the git-branch tests.\n- Ensured that git_diff_ui_config() gets called, and therefore color.diff.* respected.\n- Avoided leaking `four_spaces`.\n- Fixed a declaration in a for (;;) statement (which Junio had as a fixup! that I almost missed).\n- Renamed `branch --diff`, which had been renamed from `branch-diff` (which was picked to avoid re-using `tbdiff`) to `range-diff`.\n- Renamed `hungarian.c` and its header to `linear-assignment.c`\n- Made `--dual-color` the default, and changed it to still auto-detect whether color should be used rather than forcing it\n\nJohannes Schindelin (19):\n  linear-assignment: a function to solve least-cost assignment problems\n  Introduce `range-diff` to compare iterations of a topic branch\n  range-diff: first rudimentary implementation\n  range-diff: improve the order of the shown commits\n  range-diff: also show the diff between patches\n  range-diff: right-trim commit messages\n  range-diff: indent the diffs just like tbdiff\n  range-diff: suppress the diff headers\n  range-diff: adjust the output of the commit pairs\n  range-diff: do not show \"function names\" in hunk headers\n  range-diff: use color for the commit pairs\n  color: add the meta color GIT_COLOR_REVERSE\n  diff: add an internal option to dual-color diffs of diffs\n  range-diff: offer to dual-color the diffs\n  range-diff --dual-color: work around bogus white-space warning\n  range-diff: add a man page\n  completion: support `git range-diff`\n  range-diff: left-pad patch numbers\n  range-diff: make --dual-color the default mode\n\nThomas Rast (1):\n  range-diff: add tests\n\n .gitignore                             |   1 +\n Documentation/git-range-diff.txt       | 238 ++++++++++\n Makefile                               |   3 +\n builtin.h                              |   1 +\n builtin/range-diff.c                   | 104 +++++\n color.h                                |   1 +\n command-list.txt                       |   1 +\n contrib/completion/git-completion.bash |  14 +\n diff.c                                 |  94 +++-\n diff.h                                 |   2 +\n git.c                                  |   1 +\n linear-assignment.c                    | 203 +++++++++\n linear-assignment.h                    |  22 +\n range-diff.c                           | 437 ++++++++++++++++++\n range-diff.h                           |   9 +\n t/.gitattributes                       |   1 +\n t/t3206-range-diff.sh                  | 145 ++++++\n t/t3206/history.export                 | 604 +++++++++++++++++++++++++\n 18 files changed, 1865 insertions(+), 16 deletions(-)\n create mode 100644 Documentation/git-range-diff.txt\n create mode 100644 builtin/range-diff.c\n create mode 100644 linear-assignment.c\n create mode 100644 linear-assignment.h\n create mode 100644 range-diff.c\n create mode 100644 range-diff.h\n create mode 100755 t/t3206-range-diff.sh\n create mode 100644 t/t3206/history.export\n\n\nbase-commit: e3331758f12da22f4103eec7efe1b5304a9be5e9\nPublished-As: https://github.com/gitgitgadget/git/releases/tags/pr-1%2Fdscho%2Fbranch-diff-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1/dscho/branch-diff-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/1\n\nRange-diff vs v2:\n\n  1:  3f51970cb !  1:  39272eefc Add a function to solve least-cost assignment problems\n     @@ -1,11 +1,17 @@\n      Author: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -    Add a function to solve least-cost assignment problems\n     +    linear-assignment: a function to solve least-cost assignment problems\n      \n     -    The Jonker-Volgenant algorithm was implemented to answer questions such\n     -    as: given two different versions of a topic branch (or iterations of a\n     -    patch series), what is the best pairing of commits/patches between the\n     -    different versions?\n     +    The problem solved by the code introduced in this commit goes like this:\n     +    given two sets of items, and a cost matrix which says how much it\n     +    \"costs\" to assign any given item of the first set to any given item of\n     +    the second, assign all items (except when the sets have different size)\n     +    in the cheapest way.\n     +\n     +    We use the Jonker-Volgenant algorithm to solve the assignment problem to\n     +    answer questions such as: given two different versions of a topic branch\n     +    (or iterations of a patch series), what is the best pairing of\n     +    commits/patches between the different versions?\n      \n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     @@ -16,15 +22,15 @@\n       LIB_OBJS += graph.o\n       LIB_OBJS += grep.o\n       LIB_OBJS += hashmap.o\n     -+LIB_OBJS += hungarian.o\n     ++LIB_OBJS += linear-assignment.o\n       LIB_OBJS += help.o\n       LIB_OBJS += hex.o\n       LIB_OBJS += ident.o\n      \n     -diff --git a/hungarian.c b/hungarian.c\n     +diff --git a/linear-assignment.c b/linear-assignment.c\n      new file mode 100644\n      --- /dev/null\n     -+++ b/hungarian.c\n     ++++ b/linear-assignment.c\n      @@\n      +/*\n      + * Based on: Jonker, R., & Volgenant, A. (1987). <i>A shortest augmenting path\n     @@ -32,8 +38,7 @@\n      + * 38(4), 325-340.\n      + */\n      +#include \"cache.h\"\n     -+#include \"hungarian.h\"\n     -+#include <float.h>\n     ++#include \"linear-assignment.h\"\n      +\n      +#define COST(column, row) cost[(column) + column_count * (row)]\n      +\n     @@ -41,15 +46,16 @@\n      + * The parameter `cost` is the cost matrix: the cost to assign column j to row\n      + * i is `cost[j + column_count * i].\n      + */\n     -+int compute_assignment(int column_count, int row_count, double *cost,\n     -+\t\t       int *column2row, int *row2column)\n     ++void compute_assignment(int column_count, int row_count, int *cost,\n     ++\t\t\tint *column2row, int *row2column)\n      +{\n     -+\tdouble *v = xmalloc(sizeof(double) * column_count), *d;\n     ++\tint *v, *d;\n      +\tint *free_row, free_count = 0, saved_free_count, *pred, *col;\n      +\tint i, j, phase;\n      +\n      +\tmemset(column2row, -1, sizeof(int) * column_count);\n      +\tmemset(row2column, -1, sizeof(int) * row_count);\n     ++\tALLOC_ARRAY(v, column_count);\n      +\n      +\t/* column reduction */\n      +\tfor (j = column_count - 1; j >= 0; j--) {\n     @@ -71,15 +77,15 @@\n      +\t}\n      +\n      +\t/* reduction transfer */\n     -+\tfree_row = xmalloc(sizeof(int) * row_count);\n     -+\tfor (int i = 0; i < row_count; i++) {\n     ++\tALLOC_ARRAY(free_row, row_count);\n     ++\tfor (i = 0; i < row_count; i++) {\n      +\t\tint j1 = row2column[i];\n      +\t\tif (j1 == -1)\n      +\t\t\tfree_row[free_count++] = i;\n      +\t\telse if (j1 < -1)\n      +\t\t\trow2column[i] = -2 - j1;\n      +\t\telse {\n     -+\t\t\tdouble min = COST(!j1, i) - v[!j1];\n     ++\t\t\tint min = COST(!j1, i) - v[!j1];\n      +\t\t\tfor (j = 1; j < column_count; j++)\n      +\t\t\t\tif (j != j1 && min > COST(j, i) - v[j])\n      +\t\t\t\t\tmin = COST(j, i) - v[j];\n     @@ -91,7 +97,7 @@\n      +\t    (column_count < row_count ? row_count - column_count : 0)) {\n      +\t\tfree(v);\n      +\t\tfree(free_row);\n     -+\t\treturn 0;\n     ++\t\treturn;\n      +\t}\n      +\n      +\t/* augmenting row reduction */\n     @@ -101,15 +107,15 @@\n      +\t\tsaved_free_count = free_count;\n      +\t\tfree_count = 0;\n      +\t\twhile (k < saved_free_count) {\n     -+\t\t\tdouble u1, u2;\n     ++\t\t\tint u1, u2;\n      +\t\t\tint j1 = 0, j2, i0;\n      +\n      +\t\t\ti = free_row[k++];\n      +\t\t\tu1 = COST(j1, i) - v[j1];\n      +\t\t\tj2 = -1;\n     -+\t\t\tu2 = DBL_MAX;\n     ++\t\t\tu2 = INT_MAX;\n      +\t\t\tfor (j = 1; j < column_count; j++) {\n     -+\t\t\t\tdouble c = COST(j, i) - v[j];\n     ++\t\t\t\tint c = COST(j, i) - v[j];\n      +\t\t\t\tif (u2 > c) {\n      +\t\t\t\t\tif (u1 < c) {\n      +\t\t\t\t\t\tu2 = c;\n     @@ -148,12 +154,12 @@\n      +\n      +\t/* augmentation */\n      +\tsaved_free_count = free_count;\n     -+\td = xmalloc(sizeof(double) * column_count);\n     -+\tpred = xmalloc(sizeof(int) * column_count);\n     -+\tcol = xmalloc(sizeof(int) * column_count);\n     ++\tALLOC_ARRAY(d, column_count);\n     ++\tALLOC_ARRAY(pred, column_count);\n     ++\tALLOC_ARRAY(col, column_count);\n      +\tfor (free_count = 0; free_count < saved_free_count; free_count++) {\n      +\t\tint i1 = free_row[free_count], low = 0, up = 0, last, k;\n     -+\t\tdouble min, c, u1;\n     ++\t\tint min, c, u1;\n      +\n      +\t\tfor (j = 0; j < column_count; j++) {\n      +\t\t\td[j] = COST(j, i1) - v[j];\n     @@ -228,14 +234,12 @@\n      +\tfree(d);\n      +\tfree(v);\n      +\tfree(free_row);\n     -+\n     -+\treturn 0;\n      +}\n      \n     -diff --git a/hungarian.h b/hungarian.h\n     +diff --git a/linear-assignment.h b/linear-assignment.h\n      new file mode 100644\n      --- /dev/null\n     -+++ b/hungarian.h\n     ++++ b/linear-assignment.h\n      @@\n      +#ifndef HUNGARIAN_H\n      +#define HUNGARIAN_H\n     @@ -252,7 +256,10 @@\n      + * assignments (-1 for unassigned, which can happen only if column_count !=\n      + * row_count).\n      + */\n     -+int compute_assignment(int column_count, int row_count, double *cost,\n     -+\t\t       int *column2row, int *row2column);\n     ++void compute_assignment(int column_count, int row_count, int *cost,\n     ++\t\t\tint *column2row, int *row2column);\n     ++\n     ++/* The maximal cost in the cost matrix (to prevent integer overflows). */\n     ++#define COST_MAX (1<<16)\n      +\n      +#endif\n  2:  a1ea0320b <  -:  --------- Add a new builtin: branch-diff\n  -:  --------- >  2:  7f15b26d4 Introduce `range-diff` to compare iterations of a topic branch\n  3:  e530e450e !  3:  076e1192d branch-diff: first rudimentary implementation\n     @@ -1,46 +1,117 @@\n      Author: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -    branch-diff: first rudimentary implementation\n     +    range-diff: first rudimentary implementation\n      \n     -    At this stage, `git branch-diff` can determine corresponding commits of\n     -    two related commit ranges. This makes use of the recently introduced\n     +    At this stage, `git range-diff` can determine corresponding commits\n     +    of two related commit ranges. This makes use of the recently introduced\n          implementation of the Hungarian algorithm.\n      \n          The core of this patch is a straight port of the ideas of tbdiff, the\n     -    seemingly dormant project at https://github.com/trast/tbdiff.\n     +    apparently dormant project at https://github.com/trast/tbdiff.\n      \n          The output does not at all match `tbdiff`'s output yet, as this patch\n          really concentrates on getting the patch matching part right.\n      \n     -    Note: due to differences in the diff algorithm (`tbdiff` uses the\n     -    Python module `difflib`, Git uses its xdiff fork), the cost matrix\n     -    calculated by `branch-diff` is different (but very similar) to the one\n     -    calculated by `tbdiff`. Therefore, it is possible that they find\n     -    different matching commits in corner cases (e.g. when a patch was split\n     -    into two patches of roughly equal length).\n     +    Note: due to differences in the diff algorithm (`tbdiff` uses the Python\n     +    module `difflib`, Git uses its xdiff fork), the cost matrix calculated\n     +    by `range-diff` is different (but very similar) to the one calculated\n     +    by `tbdiff`. Therefore, it is possible that they find different matching\n     +    commits in corner cases (e.g. when a patch was split into two patches of\n     +    roughly equal length).\n      \n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n     ---- a/builtin/branch-diff.c\n     -+++ b/builtin/branch-diff.c\n     +diff --git a/Makefile b/Makefile\n     +--- a/Makefile\n     ++++ b/Makefile\n     +@@\n     + LIB_OBJS += prompt.o\n     + LIB_OBJS += protocol.o\n     + LIB_OBJS += quote.o\n     ++LIB_OBJS += range-diff.o\n     + LIB_OBJS += reachable.o\n     + LIB_OBJS += read-cache.o\n     + LIB_OBJS += reflog-walk.o\n     +\n     +diff --git a/builtin/range-diff.c b/builtin/range-diff.c\n     +--- a/builtin/range-diff.c\n     ++++ b/builtin/range-diff.c\n      @@\n       #include \"cache.h\"\n       #include \"builtin.h\"\n       #include \"parse-options.h\"\n     ++#include \"range-diff.h\"\n     + \n     + static const char * const builtin_range_diff_usage[] = {\n     + N_(\"git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n     +@@\n     + \t\t\t    N_(\"Percentage by which creation is weighted\")),\n     + \t\tOPT_END()\n     + \t};\n     ++\tint res = 0;\n     ++\tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n     + \n     +-\targc = parse_options(argc, argv, NULL, options,\n     +-\t\t\t     builtin_range_diff_usage, 0);\n     ++\targc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,\n     ++\t\t\t     0);\n     + \n     +-\treturn 0;\n     ++\tif (argc == 2) {\n     ++\t\tif (!strstr(argv[0], \"..\"))\n     ++\t\t\twarning(_(\"no .. in range: '%s'\"), argv[0]);\n     ++\t\tstrbuf_addstr(&range1, argv[0]);\n     ++\n     ++\t\tif (!strstr(argv[1], \"..\"))\n     ++\t\t\twarning(_(\"no .. in range: '%s'\"), argv[1]);\n     ++\t\tstrbuf_addstr(&range2, argv[1]);\n     ++\t} else if (argc == 3) {\n     ++\t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n     ++\t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n     ++\t} else if (argc == 1) {\n     ++\t\tconst char *b = strstr(argv[0], \"...\"), *a = argv[0];\n     ++\t\tint a_len;\n     ++\n     ++\t\tif (!b)\n     ++\t\t\tdie(_(\"single arg format requires a symmetric range\"));\n     ++\n     ++\t\ta_len = (int)(b - a);\n     ++\t\tif (!a_len) {\n     ++\t\t\ta = \"HEAD\";\n     ++\t\t\ta_len = strlen(a);\n     ++\t\t}\n     ++\t\tb += 3;\n     ++\t\tif (!*b)\n     ++\t\t\tb = \"HEAD\";\n     ++\t\tstrbuf_addf(&range1, \"%s..%.*s\", b, a_len, a);\n     ++\t\tstrbuf_addf(&range2, \"%.*s..%s\", a_len, a, b);\n     ++\t} else {\n     ++\t\terror(_(\"need two commit ranges\"));\n     ++\t\tusage_with_options(builtin_range_diff_usage, options);\n     ++\t}\n     ++\n     ++\tres = show_range_diff(range1.buf, range2.buf, creation_factor);\n     ++\n     ++\tstrbuf_release(&range1);\n     ++\tstrbuf_release(&range2);\n     ++\n     ++\treturn res;\n     + }\n     +\n     +diff --git a/range-diff.c b/range-diff.c\n     +new file mode 100644\n     +--- /dev/null\n     ++++ b/range-diff.c\n     +@@\n     ++#include \"cache.h\"\n     ++#include \"range-diff.h\"\n      +#include \"string-list.h\"\n      +#include \"run-command.h\"\n      +#include \"argv-array.h\"\n      +#include \"hashmap.h\"\n      +#include \"xdiff-interface.h\"\n     -+#include \"hungarian.h\"\n     - \n     - static const char * const builtin_branch_diff_usage[] = {\n     - N_(\"git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n     -@@\n     - \treturn 0;\n     - }\n     - \n     ++#include \"linear-assignment.h\"\n     ++\n      +struct patch_util {\n      +\t/* For the search for an exact match */\n      +\tstruct hashmap_entry e;\n     @@ -219,16 +290,19 @@\n      +\t\treturn count;\n      +\n      +\terror(_(\"failed to generate diff\"));\n     -+\treturn INT_MAX;\n     ++\treturn COST_MAX;\n      +}\n      +\n     -+static int get_correspondences(struct string_list *a, struct string_list *b,\n     -+\t\t\t       double creation_weight)\n     ++static void get_correspondences(struct string_list *a, struct string_list *b,\n     ++\t\t\t\tint creation_factor)\n      +{\n      +\tint n = a->nr + b->nr;\n     -+\tdouble *cost = xmalloc(sizeof(double) * n * n), c;\n     -+\tint *a2b = xmalloc(sizeof(int) * n), *b2a = xmalloc(sizeof(int) * n);\n     -+\tint i, j, res;\n     ++\tint *cost, c, *a2b, *b2a;\n     ++\tint i, j;\n     ++\n     ++\tALLOC_ARRAY(cost, st_mult(n, n));\n     ++\tALLOC_ARRAY(a2b, n);\n     ++\tALLOC_ARRAY(b2a, n);\n      +\n      +\tfor (i = 0; i < a->nr; i++) {\n      +\t\tstruct patch_util *a_util = a->items[i].util;\n     @@ -241,12 +315,12 @@\n      +\t\t\telse if (a_util->matching < 0 && b_util->matching < 0)\n      +\t\t\t\tc = diffsize(a_util->diff, b_util->diff);\n      +\t\t\telse\n     -+\t\t\t\tc = INT_MAX;\n     ++\t\t\t\tc = COST_MAX;\n      +\t\t\tcost[i + n * j] = c;\n      +\t\t}\n      +\n      +\t\tc = a_util->matching < 0 ?\n     -+\t\t\ta_util->diffsize * creation_weight : INT_MAX;\n     ++\t\t\ta_util->diffsize * creation_factor / 100 : COST_MAX;\n      +\t\tfor (j = b->nr; j < n; j++)\n      +\t\t\tcost[i + n * j] = c;\n      +\t}\n     @@ -255,7 +329,7 @@\n      +\t\tstruct patch_util *util = b->items[j].util;\n      +\n      +\t\tc = util->matching < 0 ?\n     -+\t\t\tutil->diffsize * creation_weight : INT_MAX;\n     ++\t\t\tutil->diffsize * creation_factor / 100 : COST_MAX;\n      +\t\tfor (i = a->nr; i < n; i++)\n      +\t\t\tcost[i + n * j] = c;\n      +\t}\n     @@ -264,7 +338,7 @@\n      +\t\tfor (j = b->nr; j < n; j++)\n      +\t\t\tcost[i + n * j] = 0;\n      +\n     -+\tres = compute_assignment(n, n, cost, a2b, b2a);\n     ++\tcompute_assignment(n, n, cost, a2b, b2a);\n      +\n      +\tfor (i = 0; i < a->nr; i++)\n      +\t\tif (a2b[i] >= 0 && a2b[i] < b->nr) {\n     @@ -278,8 +352,6 @@\n      +\tfree(cost);\n      +\tfree(a2b);\n      +\tfree(b2a);\n     -+\n     -+\treturn res;\n      +}\n      +\n      +static const char *short_oid(struct patch_util *util)\n     @@ -314,71 +386,40 @@\n      +\t}\n      +}\n      +\n     - int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n     - {\n     - \tdouble creation_weight = 0.6;\n     -@@\n     - \t\t\t0, parse_creation_weight },\n     - \t\tOPT_END()\n     - \t};\n     ++int show_range_diff(const char *range1, const char *range2,\n     ++\t\t    int creation_factor)\n     ++{\n      +\tint res = 0;\n     -+\tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n     ++\n      +\tstruct string_list branch1 = STRING_LIST_INIT_DUP;\n      +\tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n     - \n     - \targc = parse_options(argc, argv, NULL, options,\n     - \t\t\tbuiltin_branch_diff_usage, 0);\n     - \n     --\treturn 0;\n     -+\tif (argc == 2) {\n     -+\t\tif (!strstr(argv[0], \"..\"))\n     -+\t\t\twarning(_(\"no .. in range: '%s'\"), argv[0]);\n     -+\t\tstrbuf_addstr(&range1, argv[0]);\n     -+\n     -+\t\tif (!strstr(argv[1], \"..\"))\n     -+\t\t\twarning(_(\"no .. in range: '%s'\"), argv[1]);\n     -+\t\tstrbuf_addstr(&range2, argv[1]);\n     -+\t} else if (argc == 3) {\n     -+\t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n     -+\t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n     -+\t} else if (argc == 1) {\n     -+\t\tconst char *b = strstr(argv[0], \"...\"), *a = argv[0];\n     -+\t\tint a_len;\n      +\n     -+\t\tif (!b)\n     -+\t\t\tdie(_(\"single arg format requires a symmetric range\"));\n     -+\n     -+\t\ta_len = (int)(b - a);\n     -+\t\tif (!a_len) {\n     -+\t\t\ta = \"HEAD\";\n     -+\t\t\ta_len = strlen(a);\n     -+\t\t}\n     -+\t\tb += 3;\n     -+\t\tif (!*b)\n     -+\t\t\tb = \"HEAD\";\n     -+\t\tstrbuf_addf(&range1, \"%s..%.*s\", b, a_len, a);\n     -+\t\tstrbuf_addf(&range2, \"%.*s..%s\", a_len, a, b);\n     -+\t} else {\n     -+\t\terror(_(\"need two commit ranges\"));\n     -+\t\tusage_with_options(builtin_branch_diff_usage, options);\n     -+\t}\n     -+\n     -+\tif (read_patches(range1.buf, &branch1))\n     -+\t\tres = error(_(\"could not parse log for '%s'\"), range1.buf);\n     -+\tif (!res && read_patches(range2.buf, &branch2))\n     -+\t\tres = error(_(\"could not parse log for '%s'\"), range2.buf);\n     ++\tif (read_patches(range1, &branch1))\n     ++\t\tres = error(_(\"could not parse log for '%s'\"), range1);\n     ++\tif (!res && read_patches(range2, &branch2))\n     ++\t\tres = error(_(\"could not parse log for '%s'\"), range2);\n      +\n      +\tif (!res) {\n      +\t\tfind_exact_matches(&branch1, &branch2);\n     -+\t\tres = get_correspondences(&branch1, &branch2, creation_weight);\n     -+\t\tif (!res)\n     -+\t\t\toutput(&branch1, &branch2);\n     ++\t\tget_correspondences(&branch1, &branch2, creation_factor);\n     ++\t\toutput(&branch1, &branch2);\n      +\t}\n      +\n     -+\tstrbuf_release(&range1);\n     -+\tstrbuf_release(&range2);\n      +\tstring_list_clear(&branch1, 1);\n      +\tstring_list_clear(&branch2, 1);\n      +\n     -+\treturn !!res;\n     - }\n     ++\treturn res;\n     ++}\n     +\n     +diff --git a/range-diff.h b/range-diff.h\n     +new file mode 100644\n     +--- /dev/null\n     ++++ b/range-diff.h\n     +@@\n     ++#ifndef BRANCH_DIFF_H\n     ++#define BRANCH_DIFF_H\n     ++\n     ++int show_range_diff(const char *range1, const char *range2,\n     ++\t\t    int creation_factor);\n     ++\n     ++#endif\n  4:  3032e2709 !  4:  e98489c8c branch-diff: improve the order of the shown commits\n     @@ -1,13 +1,13 @@\n      Author: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -    branch-diff: improve the order of the shown commits\n     +    range-diff: improve the order of the shown commits\n      \n     -    This patch lets branch-diff use the same order as tbdiff.\n     +    This patch lets `git range-diff` use the same order as tbdiff.\n      \n          The idea is simple: for left-to-right readers, it is natural to assume\n     -    that the branch-diff is performed between an older vs a newer version of\n     -    the branch. As such, the user is probably more interested in the\n     -    question \"where did this come from?\" rather than \"where did that one\n     +    that the `git range-diff` is performed between an older vs a newer\n     +    version of the branch. As such, the user is probably more interested in\n     +    the question \"where did this come from?\" rather than \"where did that one\n          go?\".\n      \n          To that end, we list the commits in the order of the second commit range\n     @@ -16,9 +16,9 @@\n      \n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n     ---- a/builtin/branch-diff.c\n     -+++ b/builtin/branch-diff.c\n     +diff --git a/range-diff.c b/range-diff.c\n     +--- a/range-diff.c\n     ++++ b/range-diff.c\n      @@\n       \tstruct hashmap_entry e;\n       \tconst char *diff, *patch;\n  5:  12d9c7977 <  -:  --------- branch-diff: also show the diff between patches\n  -:  --------- >  5:  935cad180 range-diff: also show the diff between patches\n  6:  53ee6ba38 !  6:  93ac1931d branch-diff: right-trim commit messages\n     @@ -1,6 +1,6 @@\n      Author: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -    branch-diff: right-trim commit messages\n     +    range-diff: right-trim commit messages\n      \n          When comparing commit messages, we need to keep in mind that they are\n          indented by four spaces. That is, empty lines are no longer empty, but\n     @@ -9,13 +9,13 @@\n      \n          Let's just right-trim the lines in the commit message, it's not like\n          trailing white-space in the commit messages are important enough to care\n     -    about in branch-diff.\n     +    about in `git range-diff`.\n      \n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n     ---- a/builtin/branch-diff.c\n     -+++ b/builtin/branch-diff.c\n     +diff --git a/range-diff.c b/range-diff.c\n     +--- a/range-diff.c\n     ++++ b/range-diff.c\n      @@\n       \t\t\t\tstrbuf_addbuf(&buf, &line);\n       \t\t\t\tstrbuf_addstr(&buf, \"\\n\\n\");\n  7:  c856c460a <  -:  --------- branch-diff: indent the diffs just like tbdiff\n  -:  --------- >  7:  ca5282815 range-diff: indent the diffs just like tbdiff\n  8:  35a9681a1 !  8:  80622685f branch-diff: suppress the diff headers\n     @@ -1,6 +1,6 @@\n      Author: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -    branch-diff: suppress the diff headers\n     +    range-diff: suppress the diff headers\n      \n          When showing the diff between corresponding patches of the two branch\n          versions, we have to make up a fake filename to run the diff machinery.\n     @@ -10,9 +10,9 @@\n      \n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n     ---- a/builtin/branch-diff.c\n     -+++ b/builtin/branch-diff.c\n     +diff --git a/builtin/range-diff.c b/builtin/range-diff.c\n     +--- a/builtin/range-diff.c\n     ++++ b/builtin/range-diff.c\n      @@\n       \n       \tdiff_setup(&diffopt);\n  9:  0e4c8279e !  9:  6b31cbf72 branch-diff: adjust the output of the commit pairs\n     @@ -1,33 +1,33 @@\n      Author: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -    branch-diff: adjust the output of the commit pairs\n     +    range-diff: adjust the output of the commit pairs\n      \n     -    This change brings branch-diff yet another step closer to feature parity\n     -    with tbdiff: it now shows the oneline, too, and indicates with `=` when\n     -    the commits have identical diffs.\n     +    This change brings `git range-diff` yet another step closer to\n     +    feature parity with tbdiff: it now shows the oneline, too, and indicates\n     +    with `=` when the commits have identical diffs.\n      \n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n     ---- a/builtin/branch-diff.c\n     -+++ b/builtin/branch-diff.c\n     +diff --git a/range-diff.c b/range-diff.c\n     +--- a/range-diff.c\n     ++++ b/range-diff.c\n      @@\n     - #include \"hungarian.h\"\n     - #include \"diff.h\"\n     + #include \"xdiff-interface.h\"\n     + #include \"linear-assignment.h\"\n       #include \"diffcore.h\"\n      +#include \"commit.h\"\n      +#include \"pretty.h\"\n       \n     - static const char * const builtin_branch_diff_usage[] = {\n     - N_(\"git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n     + struct patch_util {\n     + \t/* For the search for an exact match */\n      @@\n     - \treturn res;\n     + \tfree(b2a);\n       }\n       \n      -static const char *short_oid(struct patch_util *util)\n      +static void output_pair_header(struct strbuf *buf,\n     -+\t\t\t       int i, struct patch_util *a_util,\n     -+\t\t\t       int j, struct patch_util *b_util)\n     ++\t\t\t       struct patch_util *a_util,\n     ++\t\t\t       struct patch_util *b_util)\n       {\n      -\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n      +\tstatic char *dashes;\n     @@ -43,25 +43,25 @@\n      +\t}\n      +\n      +\tstrbuf_reset(buf);\n     -+\tif (i < 0)\n     ++\tif (!a_util)\n      +\t\tstrbuf_addf(buf, \"-:  %s \", dashes);\n      +\telse\n     -+\t\tstrbuf_addf(buf, \"%d:  %s \", i + 1,\n     ++\t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n      +\t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n      +\n     -+\tif (i < 0)\n     ++\tif (!a_util)\n      +\t\tstrbuf_addch(buf, '>');\n     -+\telse if (j < 0)\n     ++\telse if (!b_util)\n      +\t\tstrbuf_addch(buf, '<');\n      +\telse if (strcmp(a_util->patch, b_util->patch))\n      +\t\tstrbuf_addch(buf, '!');\n      +\telse\n      +\t\tstrbuf_addch(buf, '=');\n      +\n     -+\tif (j < 0)\n     ++\tif (!b_util)\n      +\t\tstrbuf_addf(buf, \" -:  %s\", dashes);\n      +\telse\n     -+\t\tstrbuf_addf(buf, \" %d:  %s\", j + 1,\n     ++\t\tstrbuf_addf(buf, \" %d:  %s\", b_util->i + 1,\n      +\t\t\t    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));\n      +\n      +\tcommit = lookup_commit_reference(oid);\n     @@ -79,7 +79,7 @@\n      +\tfwrite(buf->buf, buf->len, 1, stdout);\n       }\n       \n     - static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)\n     + static struct diff_filespec *get_filespec(const char *name, const char *p)\n      @@\n       static void output(struct string_list *a, struct string_list *b,\n       \t\t   struct diff_options *diffopt)\n     @@ -94,7 +94,7 @@\n       \t\tif (i < a->nr && a_util->matching < 0) {\n      -\t\t\tprintf(\"%d: %s < -: --------\\n\",\n      -\t\t\t       i + 1, short_oid(a_util));\n     -+\t\t\toutput_pair_header(&buf, i, a_util, -1, NULL);\n     ++\t\t\toutput_pair_header(&buf, a_util, NULL);\n       \t\t\ti++;\n       \t\t\tcontinue;\n       \t\t}\n     @@ -103,7 +103,7 @@\n       \t\twhile (j < b->nr && b_util->matching < 0) {\n      -\t\t\tprintf(\"-: -------- > %d: %s\\n\",\n      -\t\t\t       j + 1, short_oid(b_util));\n     -+\t\t\toutput_pair_header(&buf, -1, NULL, j, b_util);\n     ++\t\t\toutput_pair_header(&buf, NULL, b_util);\n       \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n       \t\t}\n       \n     @@ -113,8 +113,7 @@\n      -\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n      -\t\t\t       b_util->matching + 1, short_oid(a_util),\n      -\t\t\t       j + 1, short_oid(b_util));\n     -+\t\t\toutput_pair_header(&buf,\n     -+\t\t\t\t\t   b_util->matching, a_util, j, b_util);\n     ++\t\t\toutput_pair_header(&buf, a_util, b_util);\n       \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n       \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n       \t\t\t\t\t   b->items[j].string, diffopt);\n     @@ -125,4 +124,4 @@\n      +\tstrbuf_release(&buf);\n       }\n       \n     - int cmd_branch_diff(int argc, const char **argv, const char *prefix)\n     + int show_range_diff(const char *range1, const char *range2,\n 10:  2695a6abc ! 10:  ef997bb8b branch-diff: do not show \"function names\" in hunk headers\n     @@ -1,25 +1,25 @@\n      Author: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -    branch-diff: do not show \"function names\" in hunk headers\n     +    range-diff: do not show \"function names\" in hunk headers\n      \n          We are comparing complete, formatted commit messages with patches. There\n          are no function names here, so stop looking for them.\n      \n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n     ---- a/builtin/branch-diff.c\n     -+++ b/builtin/branch-diff.c\n     +diff --git a/range-diff.c b/range-diff.c\n     +--- a/range-diff.c\n     ++++ b/range-diff.c\n      @@\n       #include \"diffcore.h\"\n       #include \"commit.h\"\n       #include \"pretty.h\"\n      +#include \"userdiff.h\"\n       \n     - static const char * const builtin_branch_diff_usage[] = {\n     - N_(\"git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n     + struct patch_util {\n     + \t/* For the search for an exact match */\n      @@\n     - \treturn data;\n     + \tfwrite(buf->buf, buf->len, 1, stdout);\n       }\n       \n      +static struct userdiff_driver no_func_name = {\n 11:  313beeed3 ! 11:  3d9e5b0ba branch-diff: add tests\n     @@ -1,11 +1,12 @@\n      Author: Thomas Rast <tr@thomasrast.ch>\n      \n     -    branch-diff: add tests\n     +    range-diff: add tests\n      \n          These are essentially lifted from https://github.com/trast/tbdiff, with\n     -    light touch-ups to account for the new command name.\n     +    light touch-ups to account for the command now being an option of `git\n     +    branch`.\n      \n     -    Apart from renaming `tbdiff` to `branch-diff`, only one test case needed\n     +    Apart from renaming `tbdiff` to `range-diff`, only one test case needed\n          to be adjusted: 11 - 'changed message'.\n      \n          The underlying reason it had to be adjusted is that diff generation is\n     @@ -28,26 +29,27 @@\n       /t8005/*.txt eol=lf\n       /t9*/*.dump eol=lf\n      \n     -diff --git a/t/t7910-branch-diff.sh b/t/t7910-branch-diff.sh\n     +diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh\n      new file mode 100755\n      --- /dev/null\n     -+++ b/t/t7910-branch-diff.sh\n     ++++ b/t/t3206-range-diff.sh\n      @@\n      +#!/bin/sh\n      +\n     -+test_description='branch-diff tests'\n     ++test_description='range-diff tests'\n      +\n      +. ./test-lib.sh\n      +\n     -+# Note that because of git-branch-diff's heuristics, test_commit does more\n     ++# Note that because of the range-diff's heuristics, test_commit does more\n      +# harm than good.  We need some real history.\n      +\n      +test_expect_success 'setup' '\n     -+\tgit fast-import < \"$TEST_DIRECTORY\"/t7910/history.export\n     ++\tgit fast-import < \"$TEST_DIRECTORY\"/t3206/history.export\n      +'\n      +\n      +test_expect_success 'simple A..B A..C (unmodified)' '\n     -+\tgit branch-diff --no-color master..topic master..unmodified >actual &&\n     ++\tgit range-diff --no-color master..topic master..unmodified \\\n     ++\t\t>actual &&\n      +\tcat >expected <<-EOF &&\n      +\t1:  4de457d = 1:  35b9b25 s/5/A/\n      +\t2:  fccce22 = 2:  de345ab s/4/A/\n     @@ -58,19 +60,19 @@\n      +'\n      +\n      +test_expect_success 'simple B...C (unmodified)' '\n     -+\tgit branch-diff --no-color topic...unmodified >actual &&\n     ++\tgit range-diff --no-color topic...unmodified >actual &&\n      +\t# same \"expected\" as above\n      +\ttest_cmp expected actual\n      +'\n      +\n      +test_expect_success 'simple A B C (unmodified)' '\n     -+\tgit branch-diff --no-color master topic unmodified >actual &&\n     ++\tgit range-diff --no-color master topic unmodified >actual &&\n      +\t# same \"expected\" as above\n      +\ttest_cmp expected actual\n      +'\n      +\n      +test_expect_success 'trivial reordering' '\n     -+\tgit branch-diff --no-color master topic reordered >actual &&\n     ++\tgit range-diff --no-color master topic reordered >actual &&\n      +\tcat >expected <<-EOF &&\n      +\t1:  4de457d = 1:  aca177a s/5/A/\n      +\t3:  147e64e = 2:  14ad629 s/11/B/\n     @@ -81,7 +83,7 @@\n      +'\n      +\n      +test_expect_success 'removed a commit' '\n     -+\tgit branch-diff --no-color master topic removed >actual &&\n     ++\tgit range-diff --no-color master topic removed >actual &&\n      +\tcat >expected <<-EOF &&\n      +\t1:  4de457d = 1:  7657159 s/5/A/\n      +\t2:  fccce22 < -:  ------- s/4/A/\n     @@ -92,7 +94,7 @@\n      +'\n      +\n      +test_expect_success 'added a commit' '\n     -+\tgit branch-diff --no-color master topic added >actual &&\n     ++\tgit range-diff --no-color master topic added >actual &&\n      +\tcat >expected <<-EOF &&\n      +\t1:  4de457d = 1:  2716022 s/5/A/\n      +\t2:  fccce22 = 2:  b62accd s/4/A/\n     @@ -104,7 +106,7 @@\n      +'\n      +\n      +test_expect_success 'new base, A B C' '\n     -+\tgit branch-diff --no-color master topic rebased >actual &&\n     ++\tgit range-diff --no-color master topic rebased >actual &&\n      +\tcat >expected <<-EOF &&\n      +\t1:  4de457d = 1:  cc9c443 s/5/A/\n      +\t2:  fccce22 = 2:  c5d9641 s/4/A/\n     @@ -116,7 +118,7 @@\n      +\n      +test_expect_success 'new base, B...C' '\n      +\t# this syntax includes the commits from master!\n     -+\tgit branch-diff --no-color topic...rebased >actual &&\n     ++\tgit range-diff --no-color topic...rebased >actual &&\n      +\tcat >expected <<-EOF &&\n      +\t-:  ------- > 1:  a31b12e unrelated\n      +\t1:  4de457d = 2:  cc9c443 s/5/A/\n     @@ -128,7 +130,7 @@\n      +'\n      +\n      +test_expect_success 'changed commit' '\n     -+\tgit branch-diff --no-color topic...changed >actual &&\n     ++\tgit range-diff --no-color topic...changed >actual &&\n      +\tcat >expected <<-EOF &&\n      +\t1:  4de457d = 1:  a4b3333 s/5/A/\n      +\t2:  fccce22 = 2:  f51d370 s/4/A/\n     @@ -157,7 +159,7 @@\n      +'\n      +\n      +test_expect_success 'changed message' '\n     -+\tgit branch-diff --no-color topic...changed-message >actual &&\n     ++\tgit range-diff --no-color topic...changed-message >actual &&\n      +\tsed s/Z/\\ /g >expected <<-EOF &&\n      +\t1:  4de457d = 1:  f686024 s/5/A/\n      +\t2:  fccce22 ! 2:  4ab067d s/4/A/\n     @@ -178,10 +180,10 @@\n      +\n      +test_done\n      \n     -diff --git a/t/t7910/history.export b/t/t7910/history.export\n     +diff --git a/t/t3206/history.export b/t/t3206/history.export\n      new file mode 100644\n      --- /dev/null\n     -+++ b/t/t7910/history.export\n     ++++ b/t/t3206/history.export\n      @@\n      +blob\n      +mark :1\n 12:  ba4791918 ! 12:  7273cc647 branch-diff: use color for the commit pairs\n     @@ -1,30 +1,28 @@\n      Author: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -    branch-diff: use color for the commit pairs\n     +    range-diff: use color for the commit pairs\n      \n     -    Arguably the most important part of branch-diff's output is the list of\n     -    commits in the two branches, together with their relationships.\n     +    Arguably the most important part of `git range-diff`'s output is the\n     +    list of commits in the two branches, together with their relationships.\n      \n          For that reason, tbdiff introduced color-coding that is pretty\n          intuitive, especially for unchanged patches (all dim yellow, like the\n          first line in `git show`'s output) vs modified patches (old commit is\n          red, new commit is green). Let's imitate that color scheme.\n      \n     -    While at it, also copy tbdiff's change of the fragment color to magenta.\n     -\n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n     ---- a/builtin/branch-diff.c\n     -+++ b/builtin/branch-diff.c\n     +diff --git a/range-diff.c b/range-diff.c\n     +--- a/range-diff.c\n     ++++ b/range-diff.c\n      @@\n     - \treturn res;\n     + \tfree(b2a);\n       }\n       \n      -static void output_pair_header(struct strbuf *buf,\n      +static void output_pair_header(struct diff_options *diffopt, struct strbuf *buf,\n     - \t\t\t       int i, struct patch_util *a_util,\n     - \t\t\t       int j, struct patch_util *b_util)\n     + \t\t\t       struct patch_util *a_util,\n     + \t\t\t       struct patch_util *b_util)\n       {\n       \tstatic char *dashes;\n       \tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n     @@ -42,10 +40,10 @@\n       \t\t\t*p = '-';\n       \t}\n       \n     -+\tif (j < 0) {\n     ++\tif (!b_util) {\n      +\t\tcolor = color_old;\n      +\t\tstatus = '<';\n     -+\t} else if (i < 0) {\n     ++\t} else if (!a_util) {\n      +\t\tcolor = color_new;\n      +\t\tstatus = '>';\n      +\t} else if (strcmp(a_util->patch, b_util->patch)) {\n     @@ -58,15 +56,15 @@\n      +\n       \tstrbuf_reset(buf);\n      +\tstrbuf_addstr(buf, status == '!' ? color_old : color);\n     - \tif (i < 0)\n     + \tif (!a_util)\n       \t\tstrbuf_addf(buf, \"-:  %s \", dashes);\n       \telse\n     - \t\tstrbuf_addf(buf, \"%d:  %s \", i + 1,\n     + \t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n       \t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n       \n     --\tif (i < 0)\n     +-\tif (!a_util)\n      -\t\tstrbuf_addch(buf, '>');\n     --\telse if (j < 0)\n     +-\telse if (!b_util)\n      -\t\tstrbuf_addch(buf, '<');\n      -\telse if (strcmp(a_util->patch, b_util->patch))\n      -\t\tstrbuf_addch(buf, '!');\n     @@ -78,7 +76,7 @@\n      +\tif (status == '!')\n      +\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color_new);\n       \n     - \tif (j < 0)\n     + \tif (!b_util)\n       \t\tstrbuf_addf(buf, \" -:  %s\", dashes);\n      @@\n       \t\tconst char *commit_buffer = get_commit_buffer(commit, NULL);\n     @@ -101,33 +99,24 @@\n       \n       \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n       \t\tif (i < a->nr && a_util->matching < 0) {\n     --\t\t\toutput_pair_header(&buf, i, a_util, -1, NULL);\n     -+\t\t\toutput_pair_header(diffopt, &buf, i, a_util, -1, NULL);\n     +-\t\t\toutput_pair_header(&buf, a_util, NULL);\n     ++\t\t\toutput_pair_header(diffopt, &buf, a_util, NULL);\n       \t\t\ti++;\n       \t\t\tcontinue;\n       \t\t}\n       \n       \t\t/* Show unmatched RHS commits. */\n       \t\twhile (j < b->nr && b_util->matching < 0) {\n     --\t\t\toutput_pair_header(&buf, -1, NULL, j, b_util);\n     -+\t\t\toutput_pair_header(diffopt, &buf, -1, NULL, j, b_util);\n     +-\t\t\toutput_pair_header(&buf, NULL, b_util);\n     ++\t\t\toutput_pair_header(diffopt, &buf, NULL, b_util);\n       \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n       \t\t}\n       \n       \t\t/* Show matching LHS/RHS pair. */\n       \t\tif (j < b->nr) {\n       \t\t\ta_util = a->items[b_util->matching].util;\n     --\t\t\toutput_pair_header(&buf,\n     -+\t\t\toutput_pair_header(diffopt, &buf,\n     - \t\t\t\t\t   b_util->matching, a_util, j, b_util);\n     +-\t\t\toutput_pair_header(&buf, a_util, b_util);\n     ++\t\t\toutput_pair_header(diffopt, &buf, a_util, b_util);\n       \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n       \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n     -@@\n     - \tstruct string_list branch1 = STRING_LIST_INIT_DUP;\n     - \tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n     - \n     -+\tgit_diff_basic_config(\"diff.color.frag\", \"magenta\", NULL);\n     -+\n     - \tdiff_setup(&diffopt);\n     - \tdiffopt.output_format = DIFF_FORMAT_PATCH;\n     - \tdiffopt.flags.suppress_diff_headers = 1;\n     + \t\t\t\t\t   b->items[j].string, diffopt);\n 13:  1ebbe3595 <  -:  --------- color: provide inverted colors, too\n  -:  --------- > 13:  96a3073fb color: add the meta color GIT_COLOR_REVERSE\n 14:  ae0ea5dfc ! 14:  6be4baf60 diff: add an internal option to dual-color diffs of diffs\n     @@ -14,7 +14,8 @@\n          now.\n      \n          This is a feature that was invented by git-tbdiff, and it will be used\n     -    in `branch-diff` in the next commit.\n     +    by `git range-diff` in the next commit, by offering it via a new option:\n     +    `--dual-color`.\n      \n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     @@ -22,26 +23,15 @@\n      --- a/diff.c\n      +++ b/diff.c\n      @@\n     - \tGIT_COLOR_BOLD_YELLOW,\t/* NEW_MOVED ALTERNATIVE */\n     - \tGIT_COLOR_FAINT,\t/* NEW_MOVED_DIM */\n     - \tGIT_COLOR_FAINT_ITALIC,\t/* NEW_MOVED_ALTERNATIVE_DIM */\n     -+\tGIT_COLOR_INV_RED,\t/* OLD_INV */\n     -+\tGIT_COLOR_INV_GREEN,\t/* NEW_INV */\n     - };\n     - \n     - static NORETURN void die_want_option(const char *option_name)\n     -@@\n     - \t\treturn DIFF_FILE_NEW_MOVED_DIM;\n     - \tif (!strcasecmp(var, \"newmovedalternativedimmed\"))\n     - \t\treturn DIFF_FILE_NEW_MOVED_ALT_DIM;\n     -+\tif (!strcasecmp(var, \"oldinv\"))\n     -+\t\treturn DIFF_FILE_OLD_INV;\n     -+\tif (!strcasecmp(var, \"newinv\"))\n     -+\t\treturn DIFF_FILE_NEW_INV;\n     - \treturn -1;\n     + \tecbdata->blank_at_eof_in_postimage = (at - l2) + 1;\n       }\n       \n     -@@\n     +-static void emit_line_0(struct diff_options *o, const char *set, const char *reset,\n     ++static void emit_line_0(struct diff_options *o,\n     ++\t\t\tconst char *set, unsigned reverse, const char *reset,\n     + \t\t\tint first, const char *line, int len)\n     + {\n     + \tint has_trailing_newline, has_trailing_carriage_return;\n       \tint nofirst;\n       \tFILE *file = o->file;\n       \n     @@ -54,8 +44,11 @@\n       \tif (len == 0) {\n       \t\thas_trailing_newline = (first == '\\n');\n      @@\n     + \t}\n       \n       \tif (len || !nofirst) {\n     ++\t\tif (reverse && want_color(o->use_color))\n     ++\t\t\tfputs(GIT_COLOR_REVERSE, file);\n       \t\tfputs(set, file);\n      -\t\tif (!nofirst)\n      +\t\tif (first && !nofirst)\n     @@ -63,6 +56,15 @@\n       \t\tfwrite(line, len, 1, file);\n       \t\tfputs(reset, file);\n      @@\n     + static void emit_line(struct diff_options *o, const char *set, const char *reset,\n     + \t\t      const char *line, int len)\n     + {\n     +-\temit_line_0(o, set, reset, line[0], line+1, len-1);\n     ++\temit_line_0(o, set, 0, reset, line[0], line+1, len-1);\n     + }\n     + \n     + enum diff_symbol {\n     +@@\n       \n       static void emit_line_ws_markup(struct diff_options *o,\n       \t\t\t\tconst char *set, const char *reset,\n     @@ -77,20 +79,24 @@\n       \t}\n       \n      -\tif (!ws)\n     -+\tif (!ws && set_sign == set)\n     - \t\temit_line_0(o, set, reset, sign, line, len);\n     +-\t\temit_line_0(o, set, reset, sign, line, len);\n      -\telse if (blank_at_eof)\n     ++\tif (!ws && !set_sign)\n     ++\t\temit_line_0(o, set, 0, reset, sign, line, len);\n      +\telse if (!ws) {\n      +\t\t/* Emit just the prefix, then the rest. */\n     -+\t\temit_line_0(o, set_sign, reset, sign, \"\", 0);\n     -+\t\temit_line_0(o, set, reset, 0, line, len);\n     ++\t\temit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n     ++\t\t\t    sign, \"\", 0);\n     ++\t\temit_line_0(o, set, 0, reset, 0, line, len);\n      +\t} else if (blank_at_eof)\n       \t\t/* Blank line at EOF - paint '+' as well */\n     - \t\temit_line_0(o, ws, reset, sign, line, len);\n     +-\t\temit_line_0(o, ws, reset, sign, line, len);\n     ++\t\temit_line_0(o, ws, 0, reset, sign, line, len);\n       \telse {\n       \t\t/* Emit just the prefix, then the rest. */\n      -\t\temit_line_0(o, set, reset, sign, \"\", 0);\n     -+\t\temit_line_0(o, set_sign, reset, sign, \"\", 0);\n     ++\t\temit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n     ++\t\t\t    sign, \"\", 0);\n       \t\tws_check_emit(line, len, ws_rule,\n       \t\t\t      o->file, set, reset, ws);\n       \t}\n     @@ -103,17 +109,28 @@\n       \tstruct strbuf sb = STRBUF_INIT;\n       \n       \tenum diff_symbol s = eds->s;\n     +@@\n     + \t\tcontext = diff_get_color_opt(o, DIFF_CONTEXT);\n     + \t\treset = diff_get_color_opt(o, DIFF_RESET);\n     + \t\tputc('\\n', o->file);\n     +-\t\temit_line_0(o, context, reset, '\\\\',\n     ++\t\temit_line_0(o, context, 0, reset, '\\\\',\n     + \t\t\t    nneof, strlen(nneof));\n     + \t\tbreak;\n     + \tcase DIFF_SYMBOL_SUBMODULE_HEADER:\n      @@\n       \tcase DIFF_SYMBOL_CONTEXT:\n       \t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n       \t\treset = diff_get_color_opt(o, DIFF_RESET);\n      -\t\temit_line_ws_markup(o, set, reset, line, len, ' ',\n     -+\t\tset_sign = set;\n     ++\t\tset_sign = NULL;\n      +\t\tif (o->flags.dual_color_diffed_diffs) {\n      +\t\t\tchar c = !len ? 0 : line[0];\n      +\n      +\t\t\tif (c == '+')\n      +\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n     ++\t\t\telse if (c == '@')\n     ++\t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n      +\t\t\telse if (c == '-')\n      +\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n      +\t\t}\n     @@ -127,13 +144,15 @@\n       \t\treset = diff_get_color_opt(o, DIFF_RESET);\n      -\t\temit_line_ws_markup(o, set, reset, line, len, '+',\n      +\t\tif (!o->flags.dual_color_diffed_diffs)\n     -+\t\t\tset_sign = set;\n     ++\t\t\tset_sign = NULL;\n      +\t\telse {\n      +\t\t\tchar c = !len ? 0 : line[0];\n      +\n     -+\t\t\tset_sign = diff_get_color_opt(o, DIFF_FILE_NEW_INV);\n     ++\t\t\tset_sign = set;\n      +\t\t\tif (c == '-')\n      +\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n     ++\t\t\telse if (c == '@')\n     ++\t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n      +\t\t\telse if (c != '+')\n      +\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n      +\t\t}\n     @@ -147,13 +166,15 @@\n       \t\treset = diff_get_color_opt(o, DIFF_RESET);\n      -\t\temit_line_ws_markup(o, set, reset, line, len, '-',\n      +\t\tif (!o->flags.dual_color_diffed_diffs)\n     -+\t\t\tset_sign = set;\n     ++\t\t\tset_sign = NULL;\n      +\t\telse {\n      +\t\t\tchar c = !len ? 0 : line[0];\n      +\n     -+\t\t\tset_sign = diff_get_color_opt(o, DIFF_FILE_OLD_INV);\n     ++\t\t\tset_sign = set;\n      +\t\t\tif (c == '+')\n      +\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n     ++\t\t\telse if (c == '@')\n     ++\t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n      +\t\t\telse if (c != '-')\n      +\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n      +\t\t}\n     @@ -161,6 +182,23 @@\n       \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);\n       \t\tbreak;\n       \tcase DIFF_SYMBOL_WORDS_PORCELAIN:\n     +@@\n     + \tconst char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);\n     + \tconst char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);\n     + \tconst char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);\n     ++\tconst char *reverse = ecbdata->color_diff ? GIT_COLOR_REVERSE : \"\";\n     + \tstatic const char atat[2] = { '@', '@' };\n     + \tconst char *cp, *ep;\n     + \tstruct strbuf msgbuf = STRBUF_INIT;\n     +@@\n     + \tep += 2; /* skip over @@ */\n     + \n     + \t/* The hunk header in fraginfo color */\n     ++\tif (ecbdata->opt->flags.dual_color_diffed_diffs)\n     ++\t\tstrbuf_addstr(&msgbuf, reverse);\n     + \tstrbuf_addstr(&msgbuf, frag);\n     + \tstrbuf_add(&msgbuf, line, ep - line);\n     + \tstrbuf_addstr(&msgbuf, reset);\n      \n      diff --git a/diff.h b/diff.h\n      --- a/diff.h\n     @@ -173,14 +211,3 @@\n       };\n       \n       static inline void diff_flags_or(struct diff_flags *a,\n     -@@\n     - \tDIFF_FILE_NEW_MOVED = 13,\n     - \tDIFF_FILE_NEW_MOVED_ALT = 14,\n     - \tDIFF_FILE_NEW_MOVED_DIM = 15,\n     --\tDIFF_FILE_NEW_MOVED_ALT_DIM = 16\n     -+\tDIFF_FILE_NEW_MOVED_ALT_DIM = 16,\n     -+\tDIFF_FILE_OLD_INV = 17,\n     -+\tDIFF_FILE_NEW_INV = 18\n     - };\n     - const char *diff_get_color(int diff_use_color, enum color_diff ix);\n     - #define diff_get_color_opt(o, ix) \\\n 15:  b9be01705 ! 15:  02e13c0c6 branch-diff: offer to dual-color the diffs\n     @@ -1,6 +1,6 @@\n      Author: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -    branch-diff: offer to dual-color the diffs\n     +    range-diff: offer to dual-color the diffs\n      \n          When showing what changed between old and new commits, we show a diff of\n          the patches. This diff is a diff between diffs, therefore there are\n     @@ -13,21 +13,22 @@\n      \n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c\n     ---- a/builtin/branch-diff.c\n     -+++ b/builtin/branch-diff.c\n     +diff --git a/builtin/range-diff.c b/builtin/range-diff.c\n     +--- a/builtin/range-diff.c\n     ++++ b/builtin/range-diff.c\n      @@\n       {\n     + \tint creation_factor = 60;\n       \tstruct diff_options diffopt = { NULL };\n     - \tstruct strbuf four_spaces = STRBUF_INIT;\n      +\tint dual_color = 0;\n     - \tdouble creation_weight = 0.6;\n       \tstruct option options[] = {\n     + \t\tOPT_INTEGER(0, \"creation-factor\", &creation_factor,\n     + \t\t\t    N_(\"Percentage by which creation is weighted\")),\n      +\t\tOPT_BOOL(0, \"dual-color\", &dual_color,\n      +\t\t\t    N_(\"color both diff and diff-between-diffs\")),\n     - \t\tOPT_SET_INT(0, \"no-patches\", &diffopt.output_format,\n     - \t\t\t    N_(\"short format (no diffs)\"),\n     - \t\t\t    DIFF_FORMAT_NO_OUTPUT),\n     + \t\tOPT_END()\n     + \t};\n     + \tint i, j, res = 0;\n      @@\n       \targc = j;\n       \tdiff_setup_done(&diffopt);\n 16:  b99ab186c ! 16:  dfa7b1e71 branch-diff --dual-color: work around bogus white-space warning\n     @@ -1,6 +1,6 @@\n      Author: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -    branch-diff --dual-color: work around bogus white-space warning\n     +    range-diff --dual-color: work around bogus white-space warning\n      \n          When displaying a diff of diffs, it is possible that there is an outer\n          `+` before a context line. That happens when the context changed between\n     @@ -20,8 +20,9 @@\n          However, the proper fix would be relatively ugly and intrusive because\n          it would have to *weaken* the WS_SPACE_BEFORE_TAB option in ws.c.\n          Besides, we do not expose the --dual-color option in cases other than\n     -    the `branch-diff` command, which only uses a hard-coded output_prefix of\n     -    four spaces (which misses the problem by one column ;-)).\n     +    the `git range-diff` command, which only uses a hard-coded\n     +    output_prefix of four spaces (which misses the problem by one\n     +    column... ;-)).\n      \n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     @@ -29,7 +30,7 @@\n      --- a/diff.c\n      +++ b/diff.c\n      @@\n     - \t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n     + \t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n       \t\t\telse if (c != '+')\n       \t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n      +\t\t\t/* Avoid space-before-tab warning */\n 17:  950c75377 ! 17:  799da25ef branch-diff: add a man page\n     @@ -1,29 +1,30 @@\n      Author: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -    branch-diff: add a man page\n     +    range-diff: add a man page\n      \n     -    This is a heavily butchered version of the README written by Thomas\n     -    Rast and Thomas Gummerer, lifted from https://github.com/trast/tbdiff.\n     +    The bulk of this patch consists of a heavily butchered version of\n     +    tbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from\n     +    https://github.com/trast/tbdiff.\n      \n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -diff --git a/Documentation/git-branch-diff.txt b/Documentation/git-branch-diff.txt\n     +diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\n      new file mode 100644\n      --- /dev/null\n     -+++ b/Documentation/git-branch-diff.txt\n     ++++ b/Documentation/git-range-diff.txt\n      @@\n     -+git-branch-diff(1)\n     ++git-range-diff(1)\n      +==================\n      +\n      +NAME\n      +----\n     -+git-branch-diff - Compare two versions of a branch\n     ++git-range-diff - Compare two commit ranges (e.g. two versions of a branch)\n      +\n      +SYNOPSIS\n      +--------\n      +[verse]\n     -+'git branch-diff' [--color=[<when>]] [--no-color] [<diff-options>]\n     -+\t[--dual-color] [--no-patches] [--creation-weight=<weight>]\n     ++'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]\n     ++\t[--dual-color] [--creation-factor=<factor>]\n      +\t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n      +\n      +DESCRIPTION\n     @@ -45,23 +46,19 @@\n      +\n      +OPTIONS\n      +-------\n     -+--no-patches::\n     -+\tSuppress the diffs between commit pairs that were deemed to\n     -+\tcorrespond; only show the pairings.\n     -+\n      +--dual-color::\n      +\tWhen the commit diffs differ, recreate the original diffs'\n      +\tcoloring, and add outer -/+ diff markers with the *background*\n      +\tbeing red/green to make it easier to see e.g. when there was a\n      +\tchange in what exact lines were added.\n      +\n     -+--creation-weight=<factor>::\n     -+\tSet the creation/deletion cost fudge factor to `<factor>`.\n     -+\tDefaults to 0.6. Try a larger value if `git branch-diff`\n     -+\terroneously considers a large change a total rewrite (deletion\n     -+\tof one commit and addition of another), and a smaller one in\n     -+\tthe reverse case. See the ``Algorithm`` section below for an\n     -+\texplanation why this is needed.\n     ++--creation-factor=<percent>::\n     ++\tSet the creation/deletion cost fudge factor to `<percent>`.\n     ++\tDefaults to 60. Try a larger value if `git range-diff` erroneously\n     ++\tconsiders a large change a total rewrite (deletion of one commit\n     ++\tand addition of another), and a smaller one in the reverse case.\n     ++\tSee the ``Algorithm`` section below for an explanation why this is\n     ++\tneeded.\n      +\n      +<range1> <range2>::\n      +\tCompare the commits specified by the two ranges, where\n     @@ -74,10 +71,10 @@\n      +\tEquivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.\n      +\tNote that `<base>` does not need to be the exact branch point\n      +\tof the branches. Example: after rebasing a branch `my-topic`,\n     -+\t`git branch-diff my-topic@{u} my-topic@{1} my-topic` would\n     ++\t`git range-diff my-topic@{u} my-topic@{1} my-topic` would\n      +\tshow the differences introduced by the rebase.\n      +\n     -+`git branch-diff` also accepts the regular diff options (see\n     ++`git range-diff` also accepts the regular diff options (see\n      +linkgit:git-diff[1]), most notably the `--color=[<when>]` and\n      +`--no-color` options. These options are used when generating the \"diff\n      +between patches\", i.e. to compare the author, commit message and diff of\n     @@ -87,23 +84,23 @@\n      +\n      +CONFIGURATION\n      +-------------\n     -+This command uses the `diff.color.*` and `pager.branch-diff` settings\n     ++This command uses the `diff.color.*` and `pager.range-diff` settings\n      +(the latter is on by default).\n      +See linkgit:git-config[1].\n      +\n      +\n     -+Examples\n     ++EXAMPLES\n      +--------\n      +\n      +When a rebase required merge conflicts to be resolved, compare the changes\n      +introduced by the rebase directly afterwards using:\n      +\n      +------------\n     -+$ git branch-diff @{u} @{1} @\n     ++$ git range-diff @{u} @{1} @\n      +------------\n      +\n      +\n     -+A typical output of `git branch-diff` would look like this:\n     ++A typical output of `git range-diff` would look like this:\n      +\n      +------------\n      +-:  ------- > 1:  0ddba11 Prepare for the inevitable!\n     @@ -216,11 +213,11 @@\n      +------------\n      +\n      +The cost of an edge `o--C` is the size of `C`'s diff, modified by a\n     -+fudge factor that should be smaller than 1.0. The cost of an edge `o--o`\n     -+is free. The fudge factor is necessary because even if `1` and `C` have\n     -+nothing in common, they may still share a few empty lines and such,\n     -+possibly making the assignment `1--C`, `o--o` slightly cheaper than\n     -+`1--o`, `o--C` even if `1` and `C` have nothing in common. With the\n     ++fudge factor that should be smaller than 100%. The cost of an edge\n     ++`o--o` is free. The fudge factor is necessary because even if `1` and\n     ++`C` have nothing in common, they may still share a few empty lines and\n     ++such, possibly making the assignment `1--C`, `o--o` slightly cheaper\n     ++than `1--o`, `o--C` even if `1` and `C` have nothing in common. With the\n      +fudge factor we require a much larger common part to consider patches as\n      +corresponding.\n      +\n 18:  71698f118 <  -:  --------- completion: support branch-diff\n  -:  --------- > 18:  d05b54c60 completion: support `git range-diff`\n  -:  --------- > 19:  144363006 range-diff: left-pad patch numbers\n  -:  --------- > 20:  4a68b95ce range-diff: make --dual-color the default mode\n\n-- \ngitgitgadget\n"},{"id":"351829","messageId":"xmqqtvpcgf40.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"39272eefcfe66de3ca1aa2ee43d6626ce558caae.1530617166.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-06T22:43:11Z","receivedAt":"2018-07-06T22:43:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n>\n> The problem solved by the code introduced in this commit goes like this:\n> given two sets of items, and a cost matrix which says how much it\n> \"costs\" to assign any given item of the first set to any given item of\n> the second, assign all items (except when the sets have different size)\n> in the cheapest way.\n>\n> We use the Jonker-Volgenant algorithm to solve the assignment problem to\n> answer questions such as: given two different versions of a topic branch\n> (or iterations of a patch series), what is the best pairing of\n> commits/patches between the different versions?\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n\nDoes the \"gitgitgadget\" thing lie on the Date: e-mail header?\n\nPostdating the patch with in-body header is fine, but mailbox tools\noften use and trust the Date: timestamp when sorting and finding\nmessages etc. so sending a new patch to add linear-assignment.c that\nis different from what was added 9 weeks ago with \"Date: Mon, 30 Apr\n2018\" header can easily cause me to miss that message when I look\nfor things that happened within the past few weeks, for example.\n\n"},{"id":"351830","messageId":"xmqqpo00geys.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"d05b54c603dc68acf198bb8826e3af4f2f11021f.1530617166.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 18/20] completion: support `git range-diff`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-06T22:46:19Z","receivedAt":"2018-07-06T22:46:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n>\n> Tab completion of `git range-diff` is very convenient, especially\n> given that the revision arguments to specify the commit ranges to\n> compare are typically more complex than, say, your grandfather's `git\n> log` arguments.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nHave three-dash lines here, or perhaps have some validation hook in\nthe garden-shears tool to notice these leftoer bits we see below?\n\n>\n> squash! WIP completion: support `git range-diff`\n>\n> Revert \"WIP completion: support `git range-diff`\"\n>\n> This reverts commit 2e7af652af9e53a19fd947f8ebe37a78043afa49.\n> ---\n>  contrib/completion/git-completion.bash | 14 ++++++++++++++\n>  1 file changed, 14 insertions(+)\n>\n> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n> index 94c95516e..402490673 100644\n> --- a/contrib/completion/git-completion.bash\n> +++ b/contrib/completion/git-completion.bash\n> @@ -1976,6 +1976,20 @@ _git_push ()\n>  \t__git_complete_remote_or_refspec\n>  }\n>  \n> +_git_range_diff ()\n> +{\n> +  case \"$cur\" in\n> +  --*)\n> +          __gitcomp \"\n> +\t  \t--creation-factor= --dual-color\n> +                  $__git_diff_common_options\n> +                  \"\n> +          return\n> +          ;;\n> +  esac\n> +  __git_complete_revlist\n> +}\n> +\n>  _git_rebase ()\n>  {\n>  \t__git_find_repo_path\n"},{"id":"351841","messageId":"nycvar.QRO.7.76.6.1807071320110.75@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"xmqqtvpcgf40.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-07T11:34:01Z","receivedAt":"2018-07-07T11:34:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Fri, 6 Jul 2018, Junio C Hamano wrote:\n\n> \"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\n> writes:\n> \n> > From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> >\n> > The problem solved by the code introduced in this commit goes like this:\n> > given two sets of items, and a cost matrix which says how much it\n> > \"costs\" to assign any given item of the first set to any given item of\n> > the second, assign all items (except when the sets have different size)\n> > in the cheapest way.\n> >\n> > We use the Jonker-Volgenant algorithm to solve the assignment problem to\n> > answer questions such as: given two different versions of a topic branch\n> > (or iterations of a patch series), what is the best pairing of\n> > commits/patches between the different versions?\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> \n> Does the \"gitgitgadget\" thing lie on the Date: e-mail header?\n\nNo, GitGitGadget takes the literal output from `git format-patch`, as far\nas I can tell. So if at all, it is `format-patch` that is lying.\n\nYou can compare the mail's date to the commit date:\n\nhttps://public-inbox.org/git/39272eefcfe66de3ca1aa2ee43d6626ce558caae.1530617166.git.gitgitgadget@gmail.com/\nhttps://github.com/dscho/git/commit/39272eefcfe66de3ca1aa2ee43d6626ce558caae\n\n(the nice thing about GitGitGadget is that you can rely on its mails to\nreflect *precisely* what the commit is like, the user does not have any\nopportunity to interfere with the code that generates the mails:\nhttps://github.com/gitgitgadget/gitgitgadget/blob/c4805370f/lib/patch-series.ts#L605-L611).\n\n> Postdating the patch with in-body header is fine, but mailbox tools\n> often use and trust the Date: timestamp when sorting and finding\n> messages etc. so sending a new patch to add linear-assignment.c that\n> is different from what was added 9 weeks ago with \"Date: Mon, 30 Apr\n> 2018\" header can easily cause me to miss that message when I look\n> for things that happened within the past few weeks, for example.\n\nWell, isn't it too bad that we use emails to transport commits, then.\n\nSeriously, I have very little sympathy here, as all I am doing is to\nautomate *the suggested usage* of `git format-patch` and `git send-email`\n(the latter of which I cannot even use due to its limitations).\n\nSo if you want to see this \"fixed\", you should think how you want to see\n`git format-patch` fixed.\n\nOr maybe you want to write a script that re-orders the patches on top of\nthe cover letter according to the `[PATCH M/N]` order, to reinstate the\norder of the original commits that got somewhat lost via emailing them.\n\nOf course, you could also save yourself a lot of trouble and use Git:\n\n\tgit fetch https://github.com/gitgitgadget/git \\\n\t\tpr-1/dscho/branch-diff-v3\n\tgit cherry-pick -s ..FETCH_HEAD\n\n(This is assuming that you insist, as you did in the past, on changing the\nbase commit from what the original author chose. If you are fine with my\nchoice, which is the current `master`, then you could save yourself *even\nmore* trouble by just pulling my branch, and merely signing off on the\nmerge commit. Which would be totes okay with me.)\n\n\nCiao,\nDscho\n"},{"id":"351842","messageId":"nycvar.QRO.7.76.6.1807071334480.75@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"xmqqpo00geys.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 18/20] completion: support `git range-diff`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-07T11:38:38Z","receivedAt":"2018-07-07T11:39:15Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Fri, 6 Jul 2018, Junio C Hamano wrote:\n\n> \"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\n> writes:\n> \n> > From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> >\n> > Tab completion of `git range-diff` is very convenient, especially\n> > given that the revision arguments to specify the commit ranges to\n> > compare are typically more complex than, say, your grandfather's `git\n> > log` arguments.\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> \n> Have three-dash lines here, or perhaps have some validation hook in\n> the garden-shears tool to notice these leftoer bits we see below?\n\nThis is just a simple case of me overlooking the `squash!` while rebasing.\nThe shears were not involved, as it is not a branch thicket, it is a\nsimple, linear branch.\n\nI guess I could install some sort of post-rewrite hook, but then, I'd\nrather have this as a more generally useful feature, directly in `rebase\n-i`: if running in autosquash mode, when offering to edit squashed commit\nmessages, `git rebase -i` should refuse by default to accept a commit\nmessage that contains a line that starts with `squash! `.\n\nWould make for a nice micro-project, methinks.\n\n> > squash! WIP completion: support `git range-diff`\n> >\n> > Revert \"WIP completion: support `git range-diff`\"\n> >\n> > This reverts commit 2e7af652af9e53a19fd947f8ebe37a78043afa49.\n> > ---\n\nI will fix this in my branch, of course, and force-push, but will wait\nwith sending out a new revision of the patch series in case more reviews\nroll in.\n\nCiao,\nDscho\n"},{"id":"351848","messageId":"xmqq7em7gg3j.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1807071320110.75@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-07T16:34:08Z","receivedAt":"2018-07-07T16:34:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n>> Does the \"gitgitgadget\" thing lie on the Date: e-mail header?\n>\n> No, GitGitGadget takes the literal output from `git format-patch`, as far\n> as I can tell. So if at all, it is `format-patch` that is lying.\n\nformat-patch faithfully records the fact about the commit that is\nmade into the patch.  How pieces of information should (or should\nnot) be used depends on the purpose of the application that uses\nits output.\n\nI'd suggest to match what send-email does, which is to notice but\nuse the current date when adding a Date: header.  An option to lie\nto SMTP servers may be OK but I do not think we want to encourage\nsuch a behaviour by making it the default.\n\nWhat is missing in the core-git tools is an ability to tell\nsend-email to optionaly add an in-body header to record the author\ndate of the original.  We add an in-body header that records the\nreal author when it is different from the sender automatically, and\nit is OK to have an option to allow doing so (but not encouraged\naround here---it is easier to reason about the resulting history for\neverybody, perhaps other than the original author, to record the\nfirst time you show the change to the public as the author time).\n\n\n"},{"id":"351850","messageId":"nycvar.QRO.7.76.6.1807072116570.75@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"xmqq7em7gg3j.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-07T19:27:07Z","receivedAt":"2018-07-07T19:27:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Sat, 7 Jul 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> >> Does the \"gitgitgadget\" thing lie on the Date: e-mail header?\n> >\n> > No, GitGitGadget takes the literal output from `git format-patch`, as far\n> > as I can tell. So if at all, it is `format-patch` that is lying.\n> \n> format-patch faithfully records the fact about the commit that is\n> made into the patch.  How pieces of information should (or should\n> not) be used depends on the purpose of the application that uses\n> its output.\n\nI guess this is one of the fallouts for abusing the `format-patch|am`\ndance for `rebase--am`.\n\n> I'd suggest to match what send-email does, which is to notice but\n> use the current date when adding a Date: header.  An option to lie\n> to SMTP servers may be OK but I do not think we want to encourage\n> such a behaviour by making it the default.\n\nI opened a PR to add a TODO:\n\n\thttps://github.com/gitgitgadget/gitgitgadget/pull/15\n\n> What is missing in the core-git tools is an ability to tell\n> send-email to optionaly add an in-body header to record the author\n> date of the original.  We add an in-body header that records the\n> real author when it is different from the sender automatically, and\n> it is OK to have an option to allow doing so (but not encouraged\n> around here---it is easier to reason about the resulting history for\n> everybody, perhaps other than the original author, to record the\n> first time you show the change to the public as the author time).\n\nPull Request-based workflows keep the original author date all the time.\nIf that is not desired, we need to do more than paper over it by adjusting\n`send-email`.\n\nCiao,\nDscho\n"},{"id":"351859","messageId":"nycvar.QRO.7.76.6.1807080017160.75@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1807072116570.75@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-07T22:23:39Z","receivedAt":"2018-07-07T22:23:49Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Sat, 7 Jul 2018, Johannes Schindelin wrote:\n\n> On Sat, 7 Jul 2018, Junio C Hamano wrote:\n> \n> > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> > \n> > >> Does the \"gitgitgadget\" thing lie on the Date: e-mail header?\n> > >\n> > > No, GitGitGadget takes the literal output from `git format-patch`, as far\n> > > as I can tell. So if at all, it is `format-patch` that is lying.\n> > \n> > format-patch faithfully records the fact about the commit that is\n> > made into the patch.  How pieces of information should (or should\n> > not) be used depends on the purpose of the application that uses\n> > its output.\n> \n> I guess this is one of the fallouts for abusing the `format-patch|am`\n> dance for `rebase--am`.\n\nSpeaking of GitGitGadget: I just encoutered a problem with your\n`refs/notes/amlog` and I hope you can help me with that.\n\nConcretely, I want GitGitGadget to be able to identify the commit that\ncorresponds to a given mail that contained a patch (if it ever made it\ninto `pu`), to automate all kinds of tedious things that I currently have\nto perform manually.\n\nAnd here I hit a block: I am looking for the commit corresponding to\naca087479b35cbcbd7c84c7ca3bcf556133d0548.1530274571.git.gitgitgadget@gmail.com\n\nWhen I ask `git notes --ref=refs/notes/gitster-amlog show\n4cec3986f017d84c8d6a2c4233d2eba4a3ffa60d` (the SHA-1 is the one\ncorresponding to `Message-Id: <...>` for that mail), it insists on\noutputting\n\n\t5902152ab02291af4454f24a8ccaf2adddefc306\n\nHowever, I cannot find that commit anywhere.\n\nWhen I look for the commit in the same manual, tedious way that I want to\nautomate, I find that it *is* in `pu`, but as\n\n\t5cf8e064747be2026bb23be37f84f2f0b2a31781\n\nEven curiouser: when I now ask for the commit notes for both of those\nSHA-1s, I get back the correct, same Message-Id *for both of them*, which\nmakes me think that it was recorded correctly, but then overwritten due to\nsome process I don't understand.\n\nWould you be able to shed light into this?\n\nThank you,\nDscho\n"},{"id":"351974","messageId":"CAGZ79ka9kjnu=taVBnkTicZBGZo-EbPOkzRxXihH8Y=Fcn5+-g@mail.gmail.com","threadId":"48405","inReplyTo":"799da25ef35d2b23dc0df1e6af0772e634f39f19.1530617166.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 17/20] range-diff: add a man page","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-09T18:20:38Z","receivedAt":"2018-07-09T18:20:53Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Tue, Jul 3, 2018 at 4:26 AM Johannes Schindelin via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n\n> +'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]\n> +       [--dual-color] [--creation-factor=<factor>]\n> +       ( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n> +\n> +DESCRIPTION\n> +-----------\n> +\n> +This command shows the differences between two versions of a patch\n> +series, or more generally, two commit ranges (ignoring merges).\n\nDoes it completely ignore merges or does it die(\"not supported\"), how is the\nuser expected to cope with the accidental merge in the given range?\n\n> +To that end, it first finds pairs of commits from both commit ranges\n> +that correspond with each other. Two commits are said to correspond when\n> +the diff between their patches (i.e. the author information, the commit\n> +message and the commit diff) is reasonably small compared to the\n> +patches' size. See ``Algorithm` below for details.\n> +\n> +Finally, the list of matching commits is shown in the order of the\n> +second commit range, with unmatched commits being inserted just after\n> +all of their ancestors have been shown.\n> +\n> +\n> +OPTIONS\n> +-------\n> +--dual-color::\n> +       When the commit diffs differ, recreate the original diffs'\n> +       coloring, and add outer -/+ diff markers with the *background*\n> +       being red/green to make it easier to see e.g. when there was a\n> +       change in what exact lines were added.\n\nI presume this is a boolean option, and can be turned off with\n--no-dual-color, but not with --dual-color=no. Would it be worth to\ngive the --no-option here as well.\nThe more pressing question I had when reading this, is whether this\nis the default.\n\n> +--creation-factor=<percent>::\n> +       Set the creation/deletion cost fudge factor to `<percent>`.\n> +       Defaults to 60. Try a larger value if `git range-diff` erroneously\n> +       considers a large change a total rewrite (deletion of one commit\n> +       and addition of another), and a smaller one in the reverse case.\n> +       See the ``Algorithm`` section below for an explanation why this is\n> +       needed.\n> +\n> +<range1> <range2>::\n> +       Compare the commits specified by the two ranges, where\n> +       `<range1>` is considered an older version of `<range2>`.\n\nIs it really older? How does that help the user?\nI think this comes from the notion of e.g. patch 4 (\"range-diff: improve the\norder of the shown commits \"), that assume the user wants the range-diff\nto be expressed with range2 as its \"base range\".\n\n> +<rev1>...<rev2>::\n> +       Equivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.\n\nThat is cool.\n\n> +Algorithm\n> +---------\n> +\n> +The general idea is this: we generate a cost matrix between the commits\n> +in both commit ranges, then solve the least-cost assignment.\n\nCan you say more about the generation of the cost matrix?\nI assume that it counts the number of lines added/deleted to make\none patch into the other patch.\n\nIf that assumption was correct, an edit of a commit message adding one\nline is just as costly as adding one line in the diff.\n\nFurther I would assume that the context lines are ignored?\n\nI think this is worth spelling out.\n\nAnother spot to look at is further metadata, such as author and author-date,\nwhich are kept the same in a rebase workflow.\n\nMaybe worth noting that this algorithm doesn't pay special attention to these,\nbut a change in them would be strong signal that the two patches compared are\nnot the same?\n\nI like the example below, thanks!\nStefan\n"},{"id":"351991","messageId":"CAGZ79kb4RS-KxEX+x07XsFiGwgG+1AiRUha=zcxexe1=RLL8kg@mail.gmail.com","threadId":"48405","inReplyTo":"6be4baf60b0376e77ec164cedea2b58fc21d16ee.1530617166.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 14/20] diff: add an internal option to dual-color diffs of diffs","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-09T19:29:35Z","receivedAt":"2018-07-09T19:29:51Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Tue, Jul 3, 2018 at 4:27 AM Johannes Schindelin via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n>\n> When diffing diffs, it can be quite daunting to figure out what the heck\n> is going on, as there are nested +/- signs.\n>\n> Let's make this easier by adding a flag in diff_options that allows\n> color-coding the outer diff sign with inverted colors, so that the\n> preimage and postimage is colored like the diff it is.\n>\n> Of course, this really only makes sense when the preimage and postimage\n> *are* diffs. So let's not expose this flag via a command-line option for\n> now.\n>\n> This is a feature that was invented by git-tbdiff, and it will be used\n> by `git range-diff` in the next commit, by offering it via a new option:\n> `--dual-color`.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  diff.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++-----------\n>  diff.h |  1 +\n>  2 files changed, 69 insertions(+), 15 deletions(-)\n>\n> diff --git a/diff.c b/diff.c\n> index 8c568cbe0..26445ffa1 100644\n> --- a/diff.c\n> +++ b/diff.c\n> @@ -562,14 +562,18 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,\n>         ecbdata->blank_at_eof_in_postimage = (at - l2) + 1;\n>  }\n>\n> -static void emit_line_0(struct diff_options *o, const char *set, const char *reset,\n> +static void emit_line_0(struct diff_options *o,\n> +                       const char *set, unsigned reverse, const char *reset,\n>                         int first, const char *line, int len)\n>  {\n>         int has_trailing_newline, has_trailing_carriage_return;\n>         int nofirst;\n>         FILE *file = o->file;\n>\n> -       fputs(diff_line_prefix(o), file);\n> +       if (first)\n> +               fputs(diff_line_prefix(o), file);\n> +       else if (!len)\n> +               return;\n\nThis case is not a problem for empty lines in e.g. \"git-log --line-prefix\"\nbecause first would contain the LF.\n\n>         if (len == 0) {\n>                 has_trailing_newline = (first == '\\n');\n> @@ -587,8 +591,10 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res\n>         }\n>\n>         if (len || !nofirst) {\n> +               if (reverse && want_color(o->use_color))\n> +                       fputs(GIT_COLOR_REVERSE, file);\n\nWould it make sense to have the function signature take a char* for reverse\nand we pass in diff_get_color(o, GIT_COLOR_REVERSE), that would align\nwith the set and reset color passed in?\n\n>                 fputs(set, file);\n> -               if (!nofirst)\n> +               if (first && !nofirst)\n>                         fputc(first, file);\n\n'first' is line[0] and comes from user data, so I think this could change\nthe output of diffs that has lines with the NUL character first in a line\nas then that character would be silently eaten?\n\nThe 'nofirst' (which is a bad name) is used to detect if we do not\nwant to double print the first character in case of an empty line.\n(Before this series we always had 'first' as a valid character, now we also\nhave 0 encoded for \"do not print anything?\"\n\n> @@ -962,7 +968,8 @@ static void dim_moved_lines(struct diff_options *o)\n>\n>  static void emit_line_ws_markup(struct diff_options *o,\n>                                 const char *set, const char *reset,\n> -                               const char *line, int len, char sign,\n> +                               const char *line, int len,\n> +                               const char *set_sign, char sign,\n>                                 unsigned ws_rule, int blank_at_eof)\n>  {\n>         const char *ws = NULL;\n> @@ -973,14 +980,20 @@ static void emit_line_ws_markup(struct diff_options *o,\n>                         ws = NULL;\n>         }\n>\n> -       if (!ws)\n> -               emit_line_0(o, set, reset, sign, line, len);\n> -       else if (blank_at_eof)\n> +       if (!ws && !set_sign)\n> +               emit_line_0(o, set, 0, reset, sign, line, len);\n> +       else if (!ws) {\n> +               /* Emit just the prefix, then the rest. */\n> +               emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n> +                           sign, \"\", 0);\n> +               emit_line_0(o, set, 0, reset, 0, line, len);\n\n(FYI:)\nMy long term vision for the emit_line_* functions was to have them actually\nline oriented, and here we observe that the preimage already breaks\nthis assumption but just uses it as it sees fit.\nI added that wart when refactoring the diff code to use the emit_ functionality\nas I wanted to stay backwards compatible.\n\nThe actual issue is that each emit_line_0 will encapsulate its content\nwith its designated color and then end it with a reset. Looking at t4015\na common occurrence is output like:\n\n  <GREEN>+<RESET><GREEN>{<RESET>\n\nwhich we'd add one more layer to it now when set_sign is set.\n\nI think this is ok for now (I value having this series land over insisting\non the perfect code), but just wanted to note my concern.\n\nIdeally we'd refactor to only call emit_line once per line and when\nthe set sign (and the newly introduced set_sign) are the same as the\nline color we'd avoid the intermediate RESET-and-SAME_COLOR\npattern that the test suite expects a lot currently.\n\n> @@ -990,7 +1003,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n>                                          struct emitted_diff_symbol *eds)\n>  {\n>         static const char *nneof = \" No newline at end of file\\n\";\n> -       const char *context, *reset, *set, *meta, *fraginfo;\n> +       const char *context, *reset, *set, *set_sign, *meta, *fraginfo;\n>         struct strbuf sb = STRBUF_INIT;\n>\n>         enum diff_symbol s = eds->s;\n> @@ -1003,7 +1016,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n>                 context = diff_get_color_opt(o, DIFF_CONTEXT);\n>                 reset = diff_get_color_opt(o, DIFF_RESET);\n>                 putc('\\n', o->file);\n> -               emit_line_0(o, context, reset, '\\\\',\n> +               emit_line_0(o, context, 0, reset, '\\\\',\n\n\n\n> @@ -1030,7 +1043,18 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n>         case DIFF_SYMBOL_CONTEXT:\n>                 set = diff_get_color_opt(o, DIFF_CONTEXT);\n>                 reset = diff_get_color_opt(o, DIFF_RESET);\n> -               emit_line_ws_markup(o, set, reset, line, len, ' ',\n> +               set_sign = NULL;\n> +               if (o->flags.dual_color_diffed_diffs) {\n> +                       char c = !len ? 0 : line[0];\n> +\n> +                       if (c == '+')\n> +                               set = diff_get_color_opt(o, DIFF_FILE_NEW);\n> +                       else if (c == '@')\n> +                               set = diff_get_color_opt(o, DIFF_FRAGINFO);\n> +                       else if (c == '-')\n> +                               set = diff_get_color_opt(o, DIFF_FILE_OLD);\n> +               }\n\nThis hunk is replicated below very similarly/\n'set' depends on the initial symbol (the case we're in), and the first character\nof line.\n\nWould it make sense to have a function\n\n  diff_get_color_set_sign(struct diffopt *, \\\n      struct emitted_diff_symbol *)\n\nthat takes care of all the computation and doesn't need\nrepetition in each case? For example @ always maps to DIFF_FRAGINFO,\nand +/- have either DIFF_FILE_{OLD, NEW}, with the exception of\n(sign == set_sign), when they become CONTEXT AFAICT?\n\n> +               emit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',\n>                                     flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);\n>                 break;\n>         case DIFF_SYMBOL_PLUS:\n> @@ -1057,7 +1081,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n>                         set = diff_get_color_opt(o, DIFF_FILE_NEW);\n>                 }\n>                 reset = diff_get_color_opt(o, DIFF_RESET);\n> -               emit_line_ws_markup(o, set, reset, line, len, '+',\n> +               if (!o->flags.dual_color_diffed_diffs)\n> +                       set_sign = NULL;\n> +               else {\n> +                       char c = !len ? 0 : line[0];\n> +\n> +                       set_sign = set;\n> +                       if (c == '-')\n> +                               set = diff_get_color_opt(o, DIFF_FILE_OLD);\n> +                       else if (c == '@')\n> +                               set = diff_get_color_opt(o, DIFF_FRAGINFO);\n> +                       else if (c != '+')\n> +                               set = diff_get_color_opt(o, DIFF_CONTEXT);\n> +               }\n> +               emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n>                                     flags & DIFF_SYMBOL_CONTENT_WS_MASK,\n>                                     flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);\n>                 break;\n> @@ -1085,7 +1122,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n>                         set = diff_get_color_opt(o, DIFF_FILE_OLD);\n>                 }\n>                 reset = diff_get_color_opt(o, DIFF_RESET);\n> -               emit_line_ws_markup(o, set, reset, line, len, '-',\n> +               if (!o->flags.dual_color_diffed_diffs)\n> +                       set_sign = NULL;\n> +               else {\n> +                       char c = !len ? 0 : line[0];\n> +\n> +                       set_sign = set;\n> +                       if (c == '+')\n> +                               set = diff_get_color_opt(o, DIFF_FILE_NEW);\n> +                       else if (c == '@')\n> +                               set = diff_get_color_opt(o, DIFF_FRAGINFO);\n> +                       else if (c != '-')\n> +                               set = diff_get_color_opt(o, DIFF_CONTEXT);\n> +               }\n> +               emit_line_ws_markup(o, set, reset, line, len, set_sign, '-',\n>                                     flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);\n>                 break;\n>         case DIFF_SYMBOL_WORDS_PORCELAIN:\n> @@ -1276,6 +1326,7 @@ static void emit_hunk_header(struct emit_callback *ecbdata,\n>         const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);\n>         const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);\n>         const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);\n> +       const char *reverse = ecbdata->color_diff ? GIT_COLOR_REVERSE : \"\";\n>         static const char atat[2] = { '@', '@' };\n>         const char *cp, *ep;\n>         struct strbuf msgbuf = STRBUF_INIT;\n> @@ -1296,6 +1347,8 @@ static void emit_hunk_header(struct emit_callback *ecbdata,\n>         ep += 2; /* skip over @@ */\n>\n>         /* The hunk header in fraginfo color */\n> +       if (ecbdata->opt->flags.dual_color_diffed_diffs)\n> +               strbuf_addstr(&msgbuf, reverse);\n>         strbuf_addstr(&msgbuf, frag);\n>         strbuf_add(&msgbuf, line, ep - line);\n>         strbuf_addstr(&msgbuf, reset);\n> diff --git a/diff.h b/diff.h\n> index 928f48995..79beb6eea 100644\n> --- a/diff.h\n> +++ b/diff.h\n> @@ -95,6 +95,7 @@ struct diff_flags {\n>         unsigned default_follow_renames:1;\n>         unsigned stat_with_summary:1;\n>         unsigned suppress_diff_headers:1;\n> +       unsigned dual_color_diffed_diffs:1;\n>  };\n>\n>  static inline void diff_flags_or(struct diff_flags *a,\n> --\n> gitgitgadget\n>\n"},{"id":"351994","messageId":"CAGZ79kYQTTjipfBn3oAbpjZGnszWNiTKN3Ai4Pp-QA+i_xigbg@mail.gmail.com","threadId":"48405","inReplyTo":"dfa7b1e71f7a39dfa608e1e205579d3b95d8a34f.1530617166.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 16/20] range-diff --dual-color: work around bogus white-space warning","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-09T19:34:14Z","receivedAt":"2018-07-09T19:34:29Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Tue, Jul 3, 2018 at 4:26 AM Johannes Schindelin via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n>\n> When displaying a diff of diffs, it is possible that there is an outer\n> `+` before a context line. That happens when the context changed between\n> old and new commit. When that context line starts with a tab (after the\n> space that marks it as context line), our diff machinery spits out a\n> white-space error (space before tab), but in this case, that is\n> incorrect.\n>\n> Work around this by detecting that situation and simply *not* printing\n> the space in that case.\n\nok. If that is the workaround that you deem to be the right thing for now.\n(I do not have an opinion if that is the right approach, or if we'd want\nto s/<TAB>/<SPACE>/ for example.)\n\n> This is slightly improper a fix because it is conceivable that an\n> output_prefix might be configured with *just* the right length to let\n> that tab jump to a different tab stop depending whether we emit that\n> space or not.\n>\n> However, the proper fix would be relatively ugly and intrusive because\n> it would have to *weaken* the WS_SPACE_BEFORE_TAB option in ws.c.\n> Besides, we do not expose the --dual-color option in cases other than\n> the `git range-diff` command, which only uses a hard-coded\n> output_prefix of four spaces (which misses the problem by one\n> column... ;-)).\n\nThat makes sense!\n\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  diff.c | 6 ++++++\n>  1 file changed, 6 insertions(+)\n>\n> diff --git a/diff.c b/diff.c\n> index 26445ffa1..325007167 100644\n> --- a/diff.c\n> +++ b/diff.c\n> @@ -1093,6 +1093,12 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n>                                 set = diff_get_color_opt(o, DIFF_FRAGINFO);\n>                         else if (c != '+')\n>                                 set = diff_get_color_opt(o, DIFF_CONTEXT);\n> +                       /* Avoid space-before-tab warning */\n> +                       if (c == ' ' && (len < 2 || line[1] == '\\t' ||\n> +                                        line[1] == '\\r' || line[1] == '\\n')) {\n> +                               line++;\n> +                               len--;\n> +                       }\n>                 }\n\nAnd this is inside the check for 'o->flags.dual_color_diffed_diffs',\nso that is protected against other diffs.\n\nThanks,\nStefan\n"},{"id":"352000","messageId":"nycvar.QRO.7.76.6.1807092132040.75@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAGZ79ka9kjnu=taVBnkTicZBGZo-EbPOkzRxXihH8Y=Fcn5+-g@mail.gmail.com","subject":"Re: [PATCH v3 17/20] range-diff: add a man page","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-09T20:00:40Z","receivedAt":"2018-07-09T20:00:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Mon, 9 Jul 2018, Stefan Beller wrote:\n\n> On Tue, Jul 3, 2018 at 4:26 AM Johannes Schindelin via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> \n> > +'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]\n> > +       [--dual-color] [--creation-factor=<factor>]\n> > +       ( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n> > +\n> > +DESCRIPTION\n> > +-----------\n> > +\n> > +This command shows the differences between two versions of a patch\n> > +series, or more generally, two commit ranges (ignoring merges).\n> \n> Does it completely ignore merges or does it die(\"not supported\"), how is\n> the user expected to cope with the accidental merge in the given range?\n\nIt ignores merges. It does not reject them. It simply ignores them and\nwon't talk about them as a consequence.\n\nCould you suggest an improved way to say that?\n\n> > +To that end, it first finds pairs of commits from both commit ranges\n> > +that correspond with each other. Two commits are said to correspond when\n> > +the diff between their patches (i.e. the author information, the commit\n> > +message and the commit diff) is reasonably small compared to the\n> > +patches' size. See ``Algorithm` below for details.\n> > +\n> > +Finally, the list of matching commits is shown in the order of the\n> > +second commit range, with unmatched commits being inserted just after\n> > +all of their ancestors have been shown.\n> > +\n> > +\n> > +OPTIONS\n> > +-------\n> > +--dual-color::\n> > +       When the commit diffs differ, recreate the original diffs'\n> > +       coloring, and add outer -/+ diff markers with the *background*\n> > +       being red/green to make it easier to see e.g. when there was a\n> > +       change in what exact lines were added.\n> \n> I presume this is a boolean option, and can be turned off with\n> --no-dual-color, but not with --dual-color=no. Would it be worth to\n> give the --no-option here as well.\n> The more pressing question I had when reading this, is whether this\n> is the default.\n\nIn the final patch (which I mulled about adding or not for a couple of\nweeks), the `--dual-color` mode is the default, and the man page talks\nabout `--no-dual-color`.\n\nDo you want me to change this intermediate commit, even if that change\nwill be reverted anyway?\n\n> > +--creation-factor=<percent>::\n> > +       Set the creation/deletion cost fudge factor to `<percent>`.\n> > +       Defaults to 60. Try a larger value if `git range-diff` erroneously\n> > +       considers a large change a total rewrite (deletion of one commit\n> > +       and addition of another), and a smaller one in the reverse case.\n> > +       See the ``Algorithm`` section below for an explanation why this is\n> > +       needed.\n> > +\n> > +<range1> <range2>::\n> > +       Compare the commits specified by the two ranges, where\n> > +       `<range1>` is considered an older version of `<range2>`.\n> \n> Is it really older? How does that help the user?\n\nIt is important to get your ducks in a row, so to speak, when looking at\nrange-diffs. They are even more unintuitive than diffs, so it makes sense\nto have a very clear mental picture of what you are trying to compare\nhere.\n\nThe coloring gives a strong hint of \"pre\" vs \"post\", i.e. old vs new: the\nchanges that are only in the \"old\" patches are marked with a minus with a\nred background color, which only really makes sense if you think about\nthese changes as \"dropped\" or \"removed\" from the \"new\" changes.\n\nSo yes, it is really considered an older version, in my mind.\n\nAgain, if you have suggestions how to improve my patch (giving rise to a\n\"new\" patch :-)), let's hear them.\n\n> I think this comes from the notion of e.g. patch 4 (\"range-diff: improve the\n> order of the shown commits \"), that assume the user wants the range-diff\n> to be expressed with range2 as its \"base range\".\n\nNo, it is motivated by the fact that we use -/+ markers to indicate\ndifferences between the \"old\" and the \"new\" patches.\n\n> > +Algorithm\n> > +---------\n> > +\n> > +The general idea is this: we generate a cost matrix between the commits\n> > +in both commit ranges, then solve the least-cost assignment.\n> \n> Can you say more about the generation of the cost matrix?\n> I assume that it counts the number of lines added/deleted to make\n> one patch into the other patch.\n\nI think that is correct.\n\n*reading the patch*\n\nActually, no, I was wrong. For the cost matrix, the *length* of the diff\n*of the diffs* is computed. Think of it as\n\n\tgit diff --no-index <(git diff A^!) <(git diff B^!) | wc -l\n\n> If that assumption was correct, an edit of a commit message adding one\n> line is just as costly as adding one line in the diff.\n\nNope, editing a commit message does not have any influence on the\nalgorithm's idea whether the commit matches or not. Only the content\nchanges associated with the commit have any say over this.\n\n> Further I would assume that the context lines are ignored?\n\nNo.\n\n> I think this is worth spelling out.\n\nSure.\n\n> Another spot to look at is further metadata, such as author and\n> author-date, which are kept the same in a rebase workflow.\n\nI encourage you to offer that as an add-on patch series. Because what you\nsuggest is not necessary for my use cases, so I'd rather not spend time on\nit.\n\nCiao,\nDscho\n"},{"id":"352009","messageId":"CAGZ79kZHcRNLh2M0oCKkUPqmzGfRczNdG=xYuyZRZ0ORd5i_zQ@mail.gmail.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1807092132040.75@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 17/20] range-diff: add a man page","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-09T20:25:06Z","receivedAt":"2018-07-09T20:25:22Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Jul 9, 2018 at 1:00 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Stefan,\n>\n> On Mon, 9 Jul 2018, Stefan Beller wrote:\n>\n> > On Tue, Jul 3, 2018 at 4:26 AM Johannes Schindelin via GitGitGadget\n> > <gitgitgadget@gmail.com> wrote:\n> >\n> > > +'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]\n> > > +       [--dual-color] [--creation-factor=<factor>]\n> > > +       ( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n> > > +\n> > > +DESCRIPTION\n> > > +-----------\n> > > +\n> > > +This command shows the differences between two versions of a patch\n> > > +series, or more generally, two commit ranges (ignoring merges).\n> >\n> > Does it completely ignore merges or does it die(\"not supported\"), how is\n> > the user expected to cope with the accidental merge in the given range?\n>\n> It ignores merges. It does not reject them. It simply ignores them and\n> won't talk about them as a consequence.\n>\n> Could you suggest an improved way to say that?\n\nWell that is what the patch said already; I was just dense in reading.\nI just tested it, and the commit with more than one parent itself is\nignored (not showing up in the output), but the commits that are merged\nin are still considered. So giving range1 as\nf7761a5a065..0a5677f6f68 with\n\n  0a5677f6f68 (Merge branch 'js/branch-diff' into pu, 2018-07-06)\n  f7761a5a065 (Merge branch 'jk/fsck-gitmodules-gently' into jch, 2018-07-06)\n\nstill produces cool output and with --word-diff it is even more amazing, as it\njust tells me a large part was s/branch-diff/range-diff/ :-)\n\n12:  cbc752c57ce ! 12:  7273cc64797 branch-diff: use color for the commit pairs\n    @@ -1,31 +1,28 @@\n    Author: Johannes Schindelin <johannes.schindelin@gmx.de>\n\n        [-branch-diff:-]{+range-diff:+} use color for the commit pairs\n\n        Arguably the most important part of [-branch-diff's-]{+`git\nrange-diff`'s+} output is the\n        list of commits in the two branches, together with their relationships.\n\n        For that reason, tbdiff introduced color-coding that is pretty\n        intuitive, especially for unchanged patches (all dim yellow, like the\n        first line in `git show`'s output) vs modified patches (old commit is\n        red, new commit is green). Let's imitate that color scheme.\n\n    [-    While at it, also copy tbdiff's change of the fragment color\nto magenta.-]\n\n        Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n    [-    Signed-off-by: Junio C Hamano <gitster@pobox.com>-]\n\nSorry for being offtopic here; I do not have a better suggestion than what\nis already said.\n\n\n> > I presume this is a boolean option, and can be turned off with\n> > --no-dual-color, but not with --dual-color=no. Would it be worth to\n> > give the --no-option here as well.\n> > The more pressing question I had when reading this, is whether this\n> > is the default.\n>\n> In the final patch (which I mulled about adding or not for a couple of\n> weeks), the `--dual-color` mode is the default, and the man page talks\n> about `--no-dual-color`.\n>\n> Do you want me to change this intermediate commit, even if that change\n> will be reverted anyway?\n\nNo, I just wasn't aware of that part, yet, as I have seen some patch series\nthat add the man page/documentation as their final patch. This looked\nso similar that I assumed this is the final man page. My bad!\n\n\n> The coloring gives a strong hint of \"pre\" vs \"post\", i.e. old vs new: the\n> changes that are only in the \"old\" patches are marked with a minus with a\n> red background color, which only really makes sense if you think about\n> these changes as \"dropped\" or \"removed\" from the \"new\" changes.\n>\n> So yes, it is really considered an older version, in my mind.\n>\n> Again, if you have suggestions how to improve my patch (giving rise to a\n> \"new\" patch :-)), let's hear them.\n\nI will send patches as I get more used to this new tool.\n\n>\n> > I think this comes from the notion of e.g. patch 4 (\"range-diff: improve the\n> > order of the shown commits \"), that assume the user wants the range-diff\n> > to be expressed with range2 as its \"base range\".\n>\n> No, it is motivated by the fact that we use -/+ markers to indicate\n> differences between the \"old\" and the \"new\" patches.\n\nAnd at this point in time we do not want to question the use of -/+ markers\nfor the next layer of abstraction. While +/- are well understood on\nthe patch level\nwe could argue for a different set of characters for diffs of diffs,\nas that helps\nto differentiate between the layers (\"is it added in the diff or the\ndiff of the diff\ndue to e.g. different context?\"), but then it would not recurse. (I am\nnot sure I\nwant to read diff of diffs of diffs)\n\nOkay, for now I accept the terms of old and new patches, as I have no\nbetter idea.\n\n>\n> > > +Algorithm\n> > > +---------\n> > > +\n> > > +The general idea is this: we generate a cost matrix between the commits\n> > > +in both commit ranges, then solve the least-cost assignment.\n> >\n> > Can you say more about the generation of the cost matrix?\n> > I assume that it counts the number of lines added/deleted to make\n> > one patch into the other patch.\n>\n> I think that is correct.\n>\n> *reading the patch*\n>\n> Actually, no, I was wrong. For the cost matrix, the *length* of the diff\n> *of the diffs* is computed. Think of it as\n>\n>         git diff --no-index <(git diff A^!) <(git diff B^!) | wc -l\n\nSo the matching is based only on diffs, but the output still takes\nthe commit messages into account. So when diffing my series to the\nseries that Junio applies, (merely adding his sign off,) would be a\n\"cost of 0\" in this context, but I still have output.\n\n>\n> > Another spot to look at is further metadata, such as author and\n> > author-date, which are kept the same in a rebase workflow.\n>\n> I encourage you to offer that as an add-on patch series. Because what you\n> suggest is not necessary for my use cases, so I'd rather not spend time on\n> it.\n\nMakes sense. When I stumble about this yet theoretical problem to materialize\nin practice I will send a patch. In my mind this is not another use\ncase, but just\nan improved matching, with the matching that this series provides being\ngood enough for now.\n\nThanks,\nStefan\n"},{"id":"352016","messageId":"nycvar.QRO.7.76.6.1807092233220.75@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAGZ79kZHcRNLh2M0oCKkUPqmzGfRczNdG=xYuyZRZ0ORd5i_zQ@mail.gmail.com","subject":"Re: [PATCH v3 17/20] range-diff: add a man page","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-09T20:38:09Z","receivedAt":"2018-07-09T20:38:23Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Mon, 9 Jul 2018, Stefan Beller wrote:\n\n> On Mon, Jul 9, 2018 at 1:00 PM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> >\n> > On Mon, 9 Jul 2018, Stefan Beller wrote:\n> >\n> > > On Tue, Jul 3, 2018 at 4:26 AM Johannes Schindelin via GitGitGadget\n> > > <gitgitgadget@gmail.com> wrote:\n> > >\n> > > > +'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]\n> > > > +       [--dual-color] [--creation-factor=<factor>]\n> > > > +       ( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n> > > > +\n> > > > +DESCRIPTION\n> > > > +-----------\n> > > > +\n> > > > +This command shows the differences between two versions of a patch\n> > > > +series, or more generally, two commit ranges (ignoring merges).\n> > >\n> > > Does it completely ignore merges or does it die(\"not supported\"), how is\n> > > the user expected to cope with the accidental merge in the given range?\n> >\n> > It ignores merges. It does not reject them. It simply ignores them and\n> > won't talk about them as a consequence.\n> >\n> > Could you suggest an improved way to say that?\n> \n> Well that is what the patch said already; I was just dense in reading.\n> I just tested it, and the commit with more than one parent itself is\n> ignored (not showing up in the output), but the commits that are merged\n> in are still considered.\n\nSo a more accurate wording would be \"ignoring merge commits\" rather than\n\"ignoring merges\".\n\nMakes sense?\n\n> So giving range1 as f7761a5a065..0a5677f6f68 with\n> \n>   0a5677f6f68 (Merge branch 'js/branch-diff' into pu, 2018-07-06)\n>   f7761a5a065 (Merge branch 'jk/fsck-gitmodules-gently' into jch, 2018-07-06)\n> \n> still produces cool output and with --word-diff it is even more amazing, as it\n> just tells me a large part was s/branch-diff/range-diff/ :-)\n\nI never thought about using `--word-diff` with `range-diff`.\n\nSadly, for me it gives rather stupid output, e.g.\n\n4:  6927c11a311 ! 4:  ebf3fea2517 range-diff: make --dual-color the default mode\n    @@ -14,6 +14,15 @@\n    diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\n    --- a/Documentation/git-range-diff.txt\n    +++ b/Documentation/git-range-diff.txt\n    {+@@+}\n    {+ --------+}\n    {+ [verse]+}\n    {+ 'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]+}\n    {+- [--dual-color] [--creation-factor=<factor>]+}\n    {++ [--no-dual-color] [--creation-factor=<factor>]+}\n    {+  ( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )+}\n    {+ +}\n    {+ DESCRIPTION+}\n    @@\n\n     OPTIONS\n\n> > > > +Algorithm\n> > > > +---------\n> > > > +\n> > > > +The general idea is this: we generate a cost matrix between the commits\n> > > > +in both commit ranges, then solve the least-cost assignment.\n> > >\n> > > Can you say more about the generation of the cost matrix?\n> > > I assume that it counts the number of lines added/deleted to make\n> > > one patch into the other patch.\n> >\n> > I think that is correct.\n> >\n> > *reading the patch*\n> >\n> > Actually, no, I was wrong. For the cost matrix, the *length* of the diff\n> > *of the diffs* is computed. Think of it as\n> >\n> >         git diff --no-index <(git diff A^!) <(git diff B^!) | wc -l\n> \n> So the matching is based only on diffs, but the output still takes\n> the commit messages into account. So when diffing my series to the\n> series that Junio applies, (merely adding his sign off,) would be a\n> \"cost of 0\" in this context, but I still have output.\n\nExactly.\n\n> > > Another spot to look at is further metadata, such as author and\n> > > author-date, which are kept the same in a rebase workflow.\n> >\n> > I encourage you to offer that as an add-on patch series. Because what you\n> > suggest is not necessary for my use cases, so I'd rather not spend time on\n> > it.\n> \n> Makes sense. When I stumble about this yet theoretical problem to\n> materialize in practice I will send a patch. In my mind this is not\n> another use case, but just an improved matching, with the matching that\n> this series provides being good enough for now.\n\nSure.\n\nCiao,\nDscho\n"},{"id":"352025","messageId":"xmqq601ocecs.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"CAGZ79kYQTTjipfBn3oAbpjZGnszWNiTKN3Ai4Pp-QA+i_xigbg@mail.gmail.com","subject":"Re: [PATCH v3 16/20] range-diff --dual-color: work around bogus white-space warning","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-09T21:02:11Z","receivedAt":"2018-07-09T21:02:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> On Tue, Jul 3, 2018 at 4:26 AM Johannes Schindelin via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n>>\n>> When displaying a diff of diffs, it is possible that there is an outer\n>> `+` before a context line. That happens when the context changed between\n>> old and new commit. When that context line starts with a tab (after the\n>> space that marks it as context line), our diff machinery spits out a\n>> white-space error (space before tab), but in this case, that is\n>> incorrect.\n>>\n>> Work around this by detecting that situation and simply *not* printing\n>> the space in that case.\n>\n> ok. If that is the workaround that you deem to be the right thing for now.\n> (I do not have an opinion if that is the right approach, or if we'd want\n> to s/<TAB>/<SPACE>/ for example.)\n>\n>> This is slightly improper a fix because it is conceivable that an\n>> output_prefix might be configured with *just* the right length to let\n>> that tab jump to a different tab stop depending whether we emit that\n>> space or not.\n>>\n>> However, the proper fix would be relatively ugly and intrusive because\n>> it would have to *weaken* the WS_SPACE_BEFORE_TAB option in ws.c.\n\nI agree that weaking the error checking is a wrong solution.  Is the\nroot cause of this whole problem because for a diff of diff e.g.\n\n\t  context that did not change between iterations\n\t- context in old interation\n\t-+whatever new contents added by old iteration\n\t+ context in new interation updated by earlier step\n\t++whatever new contents added by new iteration\n\nthere needs to be a way to tell the ws.c whitespace breakage\nchecking logic that the very first column is not interesting at all,\nand the \"+\" before \"whatever\" and \" \" before \"context\" should be\nconsidered to actually sit at the first (or zero-th) column of the\ndiff output to be checked, but there is no interface to tell the\nmachinery that wish, because there is no such need when inspecting a\ndiff of contents?  If the word \"context\" above were indented with HT,\nI can understand that the one common between iterations would\ntrigger SP+HT violation that way.  Is that what is happening here?\n\nAdding a way to tell that the apparent first column is to be ignored\nto ws.c machinery (or arranging the caller to skip the first column)\nmay be more intrusive than it is worth, only to support this tool.\nIgnoring the problem altogether and live with an incorrectly colored\nSP-before-HT might be a less noisy but still acceptable solution\nfrom that point of view, though.\n\nI also wonder if we should be feeding the context lines to ws.c\nmachinery in the first place though.  In the above hypothetical\ndiff-of-diff output, I _think_ the only two lines we want to check\nfor ws.c breakage are the ones that begin with \"whatever\".  We may\nfind that both iterations are trying to introduce a ws breakage, or\nwe may find that old one had violation which the new one corrected.\nA whitespace breakage on \"context\" lines, whether they are the ones\nbeing removed by the patch or the ones staying the same across the\npatch, is not worth painting---the normal diff-of-contents do not\nby default show them as violation, no?\n"},{"id":"352037","messageId":"nycvar.QRO.7.76.6.1807092342490.75@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1807080017160.75@tvgsbejvaqbjf.bet","subject":"refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-09T22:08:13Z","receivedAt":"2018-07-09T22:08:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Sun, 8 Jul 2018, Johannes Schindelin wrote:\n\n> I just encoutered a problem with your `refs/notes/amlog` and I hope you\n> can help me with that.\n> \n> Concretely, I want GitGitGadget to be able to identify the commit that\n> corresponds to a given mail that contained a patch (if it ever made it\n> into `pu`), to automate all kinds of tedious things that I currently have\n> to perform manually.\n> \n> And here I hit a block: I am looking for the commit corresponding to\n> aca087479b35cbcbd7c84c7ca3bcf556133d0548.1530274571.git.gitgitgadget@gmail.com\n> \n> When I ask `git notes --ref=refs/notes/gitster-amlog show\n> 4cec3986f017d84c8d6a2c4233d2eba4a3ffa60d` (the SHA-1 is the one\n> corresponding to `Message-Id: <...>` for that mail), it insists on\n> outputting\n> \n> \t5902152ab02291af4454f24a8ccaf2adddefc306\n> \n> However, I cannot find that commit anywhere.\n> \n> When I look for the commit in the same manual, tedious way that I want to\n> automate, I find that it *is* in `pu`, but as\n> \n> \t5cf8e064747be2026bb23be37f84f2f0b2a31781\n> \n> Even curiouser: when I now ask for the commit notes for both of those\n> SHA-1s, I get back the correct, same Message-Id *for both of them*, which\n> makes me think that it was recorded correctly, but then overwritten due to\n> some process I don't understand.\n> \n> Would you be able to shed light into this?\n\nI think I reconstructed the culprit:\n\nIn https://github.com/git/git/commit/a7cddab6e8, your post-applypatch hook\nadded the note for commit 5902152ab02291af4454f24a8ccaf2adddefc306 that it\nwas generated from Message-Id:\n<aca087479b35cbcbd7c84c7ca3bcf556133d0548.1530274571.git.gitgitgadget@gmail.com>,\nand then https://github.com/git/git/commit/ff28c8f9283 added the note to\nmap that Message-Id back to that commit.\n\nSo far, so good!\n\nBut then, https://github.com/git/git/commit/81b08c718e9 indicates that you\nran an interactive rebase and amended the commit\n5902152ab02291af4454f24a8ccaf2adddefc306 and the result was a new commit\n5cf8e064747be2026bb23be37f84f2f0b2a31781 that was then also mapped to that\nMessage-Id.\n\nAnd obviously, you lack a post-rewrite hook a la\n\n```sh\nrefopt=--ref=refs/notes/amlog\nwhile read old new rest\ndo\n\tmid=\"$(git notes $refopt show $old 2>/dev/null)\" &&\n\tgit notes $refopt set -m \"$mid\" $new\ndone\n```\n\nI was pretty happy to figure that out all on my own, and already on my way\nto come up with that post-rewrite hook and a script to parse all of the\ncommits in refs/notes/amlog whose commit message contains `commit --amend`\nto fix those problems, but before starting, I wanted to sanity check the\noldest such commit: https://github.com/git/git/commit/49bc3858e3c\n\nYou will be readily able to verify that it maps the commit\n73bfebd43e14bcc1502577c0933b6a16ad540b99 to Message-Id:\n<20170619175605.27864-3-phillip.wood@talktalk.net>, but that 7c1a3dcf23e\n(which corresponds to that Message-Id) maps to\nf64760904766db662badf1256923532b9e1a6ebd. So yes, there is the same\nproblem with this mapping, and we need to fix it.\n\n*However*. Neither https://github.com/git/git/commit/73bfebd43e1 nor\nhttps://github.com/git/git/commit/f6476090476 show any commit!\n\nDoes that mean that the patch with that Message-Id never made it into\n`master` and was simply dropped and gc'ed at some stage?\n\nActually, no:\nhttps://public-inbox.org/git/20170619175605.27864-3-phillip.wood@talktalk.net/\ncorresponds quite clearly to\nhttps://github.com/git/git/commit/1ceb9dfab7e\n\nNow, that commit message was clearly edited by you (I note the capital \"A\"\nin Phillip's \"Add\" vs your lower-case \"a\" in \"add\"), but the patch\nquite obviously made it into our code based in its original shape.\n\nSo I looked for the commit notes for that commit, but there aren't any!\n\nTo summarize, there are two commits recorded for that Message-Id, the\nlater one not mapped back, and neither is the correct commit that made it\ninto `master`.\n\nIt would be nice to figure out what went wrong there, and how to fix it\nfor the future (and also to fix up the existing mis-mappings in `amlog`).\n\nHowever, at this stage I really have not enough information at my hands,\neven with as much effort as I spent so far to figure out where my patch\nwent (which started this bug hunt). Could you kindly spend some time on\nthat? Otherwise, `amlog` is a lot less useful than it could otherwise be.\n\nThanks,\nDscho\n\nP.S.: funny side note: it would appear that the rewritten notes all get\nthe author of the patch author, look e.g. at the author of\nhttps://github.com/git/git/commit/81b08c718e97\n"},{"id":"352038","messageId":"xmqq601oaw00.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1807080017160.75@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-09T22:23:59Z","receivedAt":"2018-07-09T22:24:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Speaking of GitGitGadget: I just encoutered a problem with your\n> `refs/notes/amlog` and I hope you can help me with that.\n> ...\n> When I ask `git notes --ref=refs/notes/gitster-amlog show\n> 4cec3986f017d84c8d6a2c4233d2eba4a3ffa60d` (the SHA-1 is the one\n> corresponding to `Message-Id: <...>` for that mail), it insists on\n> outputting\n>\n> \t5902152ab02291af4454f24a8ccaf2adddefc306\n\nIt is not uncommon for me to have to do \"am\" the same patch twice\nwhen attempting to find the right branch/commit to base a change on,\nso the reverse direction that abuses the notes mechanism to map\nmessage id to resulting commits would be unreliable, especially\ngiven that they may need to further go through \"rebase -i\" or manual\n\"cherry-pick <range>\" depending on the situation.\n\nI am kind of surprised that the message-to-commit mapping still\nrecords any data that is remotely useful (these days, I only use it\nto run \"show --notes=amlog\" for commit-to-message mapping).  I do\nnot think I have anything special when amending the commit, but\namlog notes should be updated in both diretions for its entries to\nstay correct across amending, I would think.\n\n\n"},{"id":"352080","messageId":"nycvar.QRO.7.76.6.1807101149130.75@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"xmqq601ocecs.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 16/20] range-diff --dual-color: work around bogus white-space warning","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-10T10:08:36Z","receivedAt":"2018-07-10T10:08:45Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Mon, 9 Jul 2018, Junio C Hamano wrote:\n\n> I also wonder if we should be feeding the context lines to ws.c\n> machinery in the first place though.\n\nIt *is* confusing, I know. The entire \"diff of diffs\" concept *is*\nconfusing. I just don't know about a better alternative.\n\nSo hear me out, because there is a big misconception here: there are *two*\nlevels of diffs. The outer one and the inner one.\n\nContext lines of the outer diffs have no problem [*1*].\n\nThe problem arises when the outer diff shows a - or + line (i.e. the line\nis present *either* in the old patch set or in the new patch set, but not\nboth), *and* that line is *not* a context line of the inner diff.\n\nLet's illustrate this via an example. Let's assume that both the old patch\nset and the new patch set add a comment to a statement, and that the\ncontext of that statement changed between old and new patch set. Something\nlike this would be in the old patch set:\n\n```diff\n \tint quiet = 0;\n+\t/* This is only needed for the reflog message */\n \tconst char *branch = \"HEAD\";\n```\n\nAnd this would be in the new patch set:\n\n```diff\n \tint quiet = 0, try_harder = 0;\n+\t/* This is only needed for the reflog message */\n \tconst char *branch = \"HEAD\";\n```\n\nSo as you see, both old and new revision of the same patch add that\ncomment, and it is just a context line that changed, which a regular\nreviewer would want to *not* consider a \"real\" change between the patch\nset iterations.\n\nNow, let's look at the \"diff of diffs\":\n\n```diff\n- \tint quiet = 0;\n+ \tint quiet = 0, try_harder = 0;\n +\t/* This is only needed for the reflog message */\n  \tconst char *branch = \"HEAD\";\n```\n\nPlease understand that in the dual color mode:\n\n- The first line's `-` would have a red background color, the rest of that\n  line would be uncolored (because it is a context line of the inner\n  diff),\n\n- the second line's `+` would have a green background color, the rest\n  would be just as uncolored as the rest of the first line,\n\n- the third line would be a context line of the outer diff, but a `+` line\n  of the inner diff, therefore that rest of the line would be green, and\n\n- the fourth line is completely uncolored; It is a context line both of\n  the inner and the outer diff.\n\nThat's it for the diff colors. Now for the white space: The first two\nlines start with a `-` and a `+` respectively (outer diff marker), and\nthen most crucially continue with a space to indicate the inner diff's\ncontext line, *and then continue with a horizontal tab*.\n\nAs far as the inner diff is concerned, this *is* a context line.\n\nAs far as the outer diff is concerned, this is *not* a context line.\n\nAnd that is the conundrum: the whitespace checker is called because the\nouter diff claims that the second line is a `+` line and the whitespace\nchecker has no idea that it should treat it as a context line instead.\n\nI'll try to find some time this afternoon to study Stefan's reply, as I\nhave a hunch that there is a deep insight hidden that helps me to figure\nout the proper path ahead (because I do not want to uglify the `diff.c`\ncode the way my current iteration does, and I'd rather have a way to color\nthe diff more intelligently myself, in a function in `range-diff.c`).\n\nCiao,\nDscho\n\nFootnote *1*: Actually, that is only half the truth. In dual color mode,\nif a line is a context line of the outer diff, but a - or + line of the\ninner diff, *we still want it colored*. And of course, ideally we still\nwant whitespace checking for the + lines.\n"},{"id":"352083","messageId":"nycvar.QRO.7.76.6.1807101241010.75@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"xmqq601oaw00.fsf@gitster-ct.c.googlers.com","subject":"refs/notes/amlog woes, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-10T10:47:30Z","receivedAt":"2018-07-10T10:47:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Mon, 9 Jul 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > Speaking of GitGitGadget: I just encoutered a problem with your\n> > `refs/notes/amlog` and I hope you can help me with that.\n> > ...\n> > When I ask `git notes --ref=refs/notes/gitster-amlog show\n> > 4cec3986f017d84c8d6a2c4233d2eba4a3ffa60d` (the SHA-1 is the one\n> > corresponding to `Message-Id: <...>` for that mail), it insists on\n> > outputting\n> >\n> > \t5902152ab02291af4454f24a8ccaf2adddefc306\n> \n> It is not uncommon for me to have to do \"am\" the same patch twice\n> when attempting to find the right branch/commit to base a change on,\n\nBut then the `post-applypatch` hook just kicks in twice, leaving the\ncorrect mapping in place, no?\n\n> so the reverse direction that abuses the notes mechanism to map\n> message id to resulting commits would be unreliable, especially\n> given that they may need to further go through \"rebase -i\" or manual\n> \"cherry-pick <range>\" depending on the situation.\n\nWe already have a mechanism in place that rewrites notes in `rebase -i`'s\ncase. Not so sure about `cherry-pick`, but if it is missing, then that is\ndefinitely something we will want to address.\n\nIn other words, let's not let shortcomings of our own software dictate\nwhat we record and what we don't record.\n\nThis is highly important information that we willfully lose by using the\npatch contribution process we are going with. And we *can* at least record\nthat information.\n\n> I am kind of surprised that the message-to-commit mapping still\n> records any data that is remotely useful (these days, I only use it\n> to run \"show --notes=amlog\" for commit-to-message mapping).\n\nPlease do understand that this information is the only remotely sane way\nto work around the limitations of the mailing list-based approach we use\nhere.\n\nIt costs me a ton of time to figure out these mappings manually, and I\nthink that others simply are not as tenacious as I am and simply drop the\nball, which is not good for the project.\n\n> I do not think I have anything special when amending the commit, but\n> amlog notes should be updated in both diretions for its entries to stay\n> correct across amending, I would think.\n\nIndeed. See my other mail about the `post-rewrite` hook I suggest you to\ninstall (I did not test this code, of course, but you will probably be\nable to validate/fix it without much trouble).\n\nCiao,\nDscho\n"},{"id":"352119","messageId":"xmqqr2kb9jk2.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1807101149130.75@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 16/20] range-diff --dual-color: work around bogus white-space warning","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-10T15:50:21Z","receivedAt":"2018-07-10T15:50:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Hi Junio,\n>\n> On Mon, 9 Jul 2018, Junio C Hamano wrote:\n>\n>> I also wonder if we should be feeding the context lines to ws.c\n>> machinery in the first place though.\n>\n> It *is* confusing, I know. The entire \"diff of diffs\" concept *is*\n> confusing. I just don't know about a better alternative.\n>\n> So hear me out, because there is a big misconception here: there are *two*\n> levels of diffs. The outer one and the inner one.\n>\n> Context lines of the outer diffs have no problem [*1*].\n>\n> The problem arises when the outer diff shows a - or + line (i.e. the line\n> is present *either* in the old patch set or in the new patch set, but not\n> both), *and* that line is *not* a context line of the inner diff.\n>\n> Let's illustrate this via an example. Let's assume that both the old patch\n> set and the new patch set add a comment to a statement, and that the\n> context of that statement changed between old and new patch set. Something\n> like this would be in the old patch set:\n>\n> ```diff\n>  \tint quiet = 0;\n> +\t/* This is only needed for the reflog message */\n>  \tconst char *branch = \"HEAD\";\n> ```\n>\n> And this would be in the new patch set:\n>\n> ```diff\n>  \tint quiet = 0, try_harder = 0;\n> +\t/* This is only needed for the reflog message */\n>  \tconst char *branch = \"HEAD\";\n> ```\n>\n> So as you see, both old and new revision of the same patch add that\n> comment, and it is just a context line that changed, which a regular\n> reviewer would want to *not* consider a \"real\" change between the patch\n> set iterations.\n>\n> Now, let's look at the \"diff of diffs\":\n>\n> ```diff\n> - \tint quiet = 0;\n> + \tint quiet = 0, try_harder = 0;\n>  +\t/* This is only needed for the reflog message */\n>   \tconst char *branch = \"HEAD\";\n> ```\n>\n> Please understand that in the dual color mode:\n>\n> - The first line's `-` would have a red background color, the rest of that\n>   line would be uncolored (because it is a context line of the inner\n>   diff),\n>\n> - the second line's `+` would have a green background color, the rest\n>   would be just as uncolored as the rest of the first line,\n>\n> - the third line would be a context line of the outer diff, but a `+` line\n>   of the inner diff, therefore that rest of the line would be green, and\n>\n> - the fourth line is completely uncolored; It is a context line both of\n>   the inner and the outer diff.\n\nAll of the above about colouring I find sensible.\n\n> That's it for the diff colors. Now for the white space: The first two\n> lines start with a `-` and a `+` respectively (outer diff marker), and\n> then most crucially continue with a space to indicate the inner diff's\n> context line, *and then continue with a horizontal tab*.\n>\n> As far as the inner diff is concerned, this *is* a context line.\n>\n> As far as the outer diff is concerned, this is *not* a context line.\n\nWhat I meant was that there is no point checking ws errors in the\nouter diff.  The fact that the older and the newer revisions have an\nunchanged context line (i.e. begins with two SPs in the outer diff)\nor different one (i.e. begins with \"- \" and \"+ \") are worth knowing\n(and you have red and green leading \"-/+\" for that), but the fact\nthat the outer diff's new context line begins with \"+ <HT>\" has\nnothing to do with the goodness of the new patch, as such a line\nshows that the new patch touches a line near an unchanged [*1*] line\nthat happens to begin with a <HT>, which has no whitespace breakage\nto begin with, and even if such a line had a trailing whitespace, it\nis not something the new patch introduces.\n\n\tside note *1*; the fact that it has leading \"+\" means that\n\tthere are differences in the previous steps between old and\n\tnew patch series and that is why the context in this step is\n\tdifferent between old and new series.  But in the context of\n\tapplying the new series, the patch does *not* change that\n\tline.\n\n> And that is the conundrum: the whitespace checker is called because the\n> outer diff claims that the second line is a `+` line and the whitespace\n> checker has no idea that it should treat it as a context line instead.\n\nI think you are saying the same thing as I said in the previous\nmessage.  Trying to reuse ws.c without first giving it a way to be\ntold that the caller does not care about the early columns (in the\n\"+ <HT>\" example, you want to tell ws.c machinery that it is *not*\nan added line that begins with SP + HT; you want it to know that it\nis a context line whose contents begins with a HT) will of course\ncause headaches.\n"},{"id":"352130","messageId":"CAGZ79kbG0QZZSstC85pqSPS1awXq44vsBSvn_gfgP=22fdpzcA@mail.gmail.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1807101149130.75@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 16/20] range-diff --dual-color: work around bogus white-space warning","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-10T16:32:23Z","receivedAt":"2018-07-10T16:32:39Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Tue, Jul 10, 2018 at 3:08 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Junio,\n>\n> On Mon, 9 Jul 2018, Junio C Hamano wrote:\n>\n> > I also wonder if we should be feeding the context lines to ws.c\n> > machinery in the first place though.\n>\n> It *is* confusing, I know. The entire \"diff of diffs\" concept *is*\n> confusing. I just don't know about a better alternative.\n\nI agree, but I am sure we'll get used to it quickly.\n\n> So hear me out, because there is a big misconception here: there are *two*\n> levels of diffs. The outer one and the inner one.\n\nYes, the inner diff is just input that was generated before because it is\nso convenient to generate. Recently when using this too (back then\nwhen it was called branch-diff), I came across the following:\n\nPatch 1 looked like:\n\n    line 1\n+    new line\n    line 2\n    line 3\n\nand in the next iteration it looked like:\n    line 1\n    line 2\n+    new line\n    line 3\n\nsuch that the diff of diffs showed the move correctly, but as the inner diffs\nhad different context ranges, other lines looked like added/removed\nin the outer diff, though it was both context.\nSo I wonder if eventually (not in this series) we want to tweak the context\nlines, generate more than needed in the inner diffs and cut them off in\nthe outer diff \"at the same line\".\n\nI digress again w.r.t. white space.\n\n> Context lines of the outer diffs have no problem [*1*].\n>\n> The problem arises when the outer diff shows a - or + line (i.e. the line\n> is present *either* in the old patch set or in the new patch set, but not\n> both), *and* that line is *not* a context line of the inner diff.\n\nSo an actual change in the patches; an incremental reviewer would want\nto spend most care on these.\n\n>\n> Let's illustrate this via an example. Let's assume that both the old patch\n> set and the new patch set add a comment to a statement, and that the\n> context of that statement changed between old and new patch set. Something\n> like this would be in the old patch set:\n>\n> ```diff\n>         int quiet = 0;\n> +       /* This is only needed for the reflog message */\n>         const char *branch = \"HEAD\";\n> ```\n>\n> And this would be in the new patch set:\n>\n> ```diff\n>         int quiet = 0, try_harder = 0;\n> +       /* This is only needed for the reflog message */\n>         const char *branch = \"HEAD\";\n> ```\n>\n> So as you see, both old and new revision of the same patch add that\n> comment, and it is just a context line that changed, which a regular\n> reviewer would want to *not* consider a \"real\" change between the patch\n> set iterations.\n>\n> Now, let's look at the \"diff of diffs\":\n>\n> ```diff\n> -       int quiet = 0;\n> +       int quiet = 0, try_harder = 0;\n>  +      /* This is only needed for the reflog message */\n>         const char *branch = \"HEAD\";\n> ```\n>\n> Please understand that in the dual color mode:\n>\n> - The first line's `-` would have a red background color, the rest of that\n>   line would be uncolored (because it is a context line of the inner\n>   diff),\n>\n> - the second line's `+` would have a green background color, the rest\n>   would be just as uncolored as the rest of the first line,\n>\n> - the third line would be a context line of the outer diff, but a `+` line\n>   of the inner diff, therefore that rest of the line would be green, and\n>\n> - the fourth line is completely uncolored; It is a context line both of\n>   the inner and the outer diff.\n>\n> That's it for the diff colors. Now for the white space: The first two\n> lines start with a `-` and a `+` respectively (outer diff marker), and\n> then most crucially continue with a space to indicate the inner diff's\n> context line, *and then continue with a horizontal tab*.\n>\n> As far as the inner diff is concerned, this *is* a context line.\n>\n> As far as the outer diff is concerned, this is *not* a context line.\n>\n> And that is the conundrum: the whitespace checker is called because the\n> outer diff claims that the second line is a `+` line and the whitespace\n> checker has no idea that it should treat it as a context line instead.\n\nSpelled out this way, we might want to add more symbols to\nenum diff_symbol, such as\n    DIFF_SYMBOL_DUAL_DIFF_PLUS_PLUS\n    DIFF_SYMBOL_DUAL_DIFF_PLUS_MINUS\n    DIFF_SYMBOL_PLUS_MINUS\nor so.\n\nThese would need to get generated when we create the diff of diffs\nin emit_{del,add,context}_line or even fn_out_consume; and then have\ntheir own treatment regarding white spaces in emit_diff_symbol_from_struct.\n\nI am not sure if that would help for the series as-is, as I am\nthinking already how\nto move these diff-diffs in-core (as that would help a lot with the context line\ncutting mentioned above).\n\n> I'll try to find some time this afternoon to study Stefan's reply, as I\n> have a hunch that there is a deep insight hidden that helps me to figure\n> out the proper path ahead (because I do not want to uglify the `diff.c`\n> code the way my current iteration does, and I'd rather have a way to color\n> the diff more intelligently myself, in a function in `range-diff.c`).\n\nI considered trying a cleanup on top of your series as I had the impression\nthe move detection added some ugliness as well.\n\nThanks,\nStefan\n"},{"id":"352154","messageId":"20180710174552.30123-3-sbeller@google.com","threadId":"48405","inReplyTo":"20180710174552.30123-1-sbeller@google.com","subject":"[PATCH 2/2] WIP diff.c: clarify emit_line_0","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-10T17:45:52Z","receivedAt":"2018-07-10T18:37:28Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"This breaks t4034 (word diffs), but all other tests pass.\n\nemit_line_0 grew complicated again, so here is an attempt to make it\na bit simpler. emit_line_0 is called for all lines that are added,\nremoved or context lines, and it follows the format:\n\n <sign color> <sign> <main color> <content of length 'len'> <reset> <CR> <LF>\n\nwith each of the components optional. However a few rules apply:\n* The CR/LF is passed to the function as part of line/len, so we have\n  to figure out if we need to separate them\n* As the sign color is a rather recent addition, we do not optimize it\n  yet\n* the main color insertion is new, as the color used to be inserted before\n  the sign\n* another follow up cleanup (that also touches the tests) could be\n  a stricter check to consolidate with ws_check_emit (and not emit the\n  color/reset twice)\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n diff.c | 78 ++++++++++++++++++++++++++++------------------------------\n 1 file changed, 37 insertions(+), 41 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 028d7d9a59c..7b649f57c27 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -563,44 +563,44 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,\n }\n \n static void emit_line_0(struct diff_options *o,\n-\t\t\tconst char *set, unsigned reverse, const char *reset,\n-\t\t\tint first, const char *line, int len)\n+\t\t\tconst char *maincolor, const char *signcolor,\n+\t\t\tconst char *reset, const char *sign,\n+\t\t\tconst char *line, int len)\n {\n \tint has_trailing_newline, has_trailing_carriage_return;\n-\tint nofirst;\n \tFILE *file = o->file;\n \n-\tif (first)\n-\t\tfputs(diff_line_prefix(o), file);\n-\telse if (!len)\n-\t\treturn;\n+\tfputs(diff_line_prefix(o), file);\n \n-\tif (len == 0) {\n-\t\thas_trailing_newline = (first == '\\n');\n-\t\thas_trailing_carriage_return = (!has_trailing_newline &&\n-\t\t\t\t\t\t(first == '\\r'));\n-\t\tnofirst = has_trailing_newline || has_trailing_carriage_return;\n-\t} else {\n-\t\thas_trailing_newline = (len > 0 && line[len-1] == '\\n');\n-\t\tif (has_trailing_newline)\n-\t\t\tlen--;\n-\t\thas_trailing_carriage_return = (len > 0 && line[len-1] == '\\r');\n-\t\tif (has_trailing_carriage_return)\n-\t\t\tlen--;\n-\t\tnofirst = 0;\n-\t}\n+\thas_trailing_newline = (len > 0 && line[len-1] == '\\n');\n+\tif (has_trailing_newline)\n+\t\tlen--;\n+\thas_trailing_carriage_return = (len > 0 && line[len-1] == '\\r');\n+\tif (has_trailing_carriage_return)\n+\t\tlen--;\n \n-\tif (len || !nofirst) {\n-\t\tif (reverse && want_color(o->use_color))\n-\t\t\tfputs(GIT_COLOR_REVERSE, file);\n-\t\tfputs(set, file);\n-\t\tif (first && !nofirst)\n-\t\t\tfputc(first, file);\n+\tif (signcolor)\n+\t\tfputs(signcolor, file);\n+\telse if (maincolor)\n+\t\tfputs(maincolor, file);\n+\n+\n+\tif (sign)\n+\t\tfputs(sign, file);\n+\n+\t/* only put main color here if it we did not color the sign the same way */\n+\tif (signcolor && maincolor && signcolor != maincolor)\n+\t\tfputs(maincolor, file);\n+\n+\tif (len)\n \t\tfwrite(line, len, 1, file);\n+\n+\tif ((signcolor || maincolor) && reset)\n \t\tfputs(reset, file);\n-\t}\n+\n \tif (has_trailing_carriage_return)\n \t\tfputc('\\r', file);\n+\n \tif (has_trailing_newline)\n \t\tfputc('\\n', file);\n }\n@@ -608,7 +608,7 @@ static void emit_line_0(struct diff_options *o,\n static void emit_line(struct diff_options *o, const char *set, const char *reset,\n \t\t      const char *line, int len)\n {\n-\temit_line_0(o, set, 0, reset, line[0], line+1, len-1);\n+\temit_line_0(o, set, NULL, reset, NULL, line, len);\n }\n \n enum diff_symbol {\n@@ -980,20 +980,16 @@ static void emit_line_ws_markup(struct diff_options *o,\n \t\t\tws = NULL;\n \t}\n \n-\tif (!ws && !set_sign)\n-\t\temit_line_0(o, set, 0, reset, sign[0], line, len);\n-\telse if (!ws) {\n-\t\t/* Emit just the prefix, then the rest. */\n-\t\temit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n-\t\t\t    sign[0], \"\", 0);\n-\t\temit_line_0(o, set, 0, reset, 0, line, len);\n-\t} else if (blank_at_eof)\n+\tif (!ws)\n+\t\temit_line_0(o, set, set_sign, reset,\n+\t\t\t    sign, line, len);\n+\telse if (blank_at_eof)\n \t\t/* Blank line at EOF - paint '+' as well */\n-\t\temit_line_0(o, ws, 0, reset, sign[0], line, len);\n+\t\temit_line_0(o, ws, set_sign, reset, sign, line, len);\n \telse {\n \t\t/* Emit just the prefix, then the rest. */\n-\t\temit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n-\t\t\t    sign[0], \"\", 0);\n+\t\temit_line_0(o, set, set_sign, reset,\n+\t\t\t    sign, \"\", 0);\n \t\tws_check_emit(line, len, ws_rule,\n \t\t\t      o->file, set, reset, ws);\n \t}\n@@ -1016,7 +1012,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\tcontext = diff_get_color_opt(o, DIFF_CONTEXT);\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n \t\tputc('\\n', o->file);\n-\t\temit_line_0(o, context, 0, reset, '\\\\',\n+\t\temit_line_0(o, context, 0, reset, \"\\\\\",\n \t\t\t    nneof, strlen(nneof));\n \t\tbreak;\n \tcase DIFF_SYMBOL_SUBMODULE_HEADER:\n-- \n2.18.0.203.gfac676dfb9-goog\n\n"},{"id":"352159","messageId":"20180710174552.30123-2-sbeller@google.com","threadId":"48405","inReplyTo":"20180710174552.30123-1-sbeller@google.com","subject":"[PATCH 1/2] diff.c: convert emit_line_ws_markup to take string for sign","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-10T17:45:51Z","receivedAt":"2018-07-10T18:45:36Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"For the diff of diffs, we have more than one character at the beginning\nof the line with special meaning, so let's pass around a string that\ncontains all the markup for the line\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n diff.c | 16 ++++++++--------\n 1 file changed, 8 insertions(+), 8 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 32500716740..028d7d9a59c 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -969,7 +969,7 @@ static void dim_moved_lines(struct diff_options *o)\n static void emit_line_ws_markup(struct diff_options *o,\n \t\t\t\tconst char *set, const char *reset,\n \t\t\t\tconst char *line, int len,\n-\t\t\t\tconst char *set_sign, char sign,\n+\t\t\t\tconst char *set_sign, const char *sign,\n \t\t\t\tunsigned ws_rule, int blank_at_eof)\n {\n \tconst char *ws = NULL;\n@@ -981,19 +981,19 @@ static void emit_line_ws_markup(struct diff_options *o,\n \t}\n \n \tif (!ws && !set_sign)\n-\t\temit_line_0(o, set, 0, reset, sign, line, len);\n+\t\temit_line_0(o, set, 0, reset, sign[0], line, len);\n \telse if (!ws) {\n \t\t/* Emit just the prefix, then the rest. */\n \t\temit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n-\t\t\t    sign, \"\", 0);\n+\t\t\t    sign[0], \"\", 0);\n \t\temit_line_0(o, set, 0, reset, 0, line, len);\n \t} else if (blank_at_eof)\n \t\t/* Blank line at EOF - paint '+' as well */\n-\t\temit_line_0(o, ws, 0, reset, sign, line, len);\n+\t\temit_line_0(o, ws, 0, reset, sign[0], line, len);\n \telse {\n \t\t/* Emit just the prefix, then the rest. */\n \t\temit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n-\t\t\t    sign, \"\", 0);\n+\t\t\t    sign[0], \"\", 0);\n \t\tws_check_emit(line, len, ws_rule,\n \t\t\t      o->file, set, reset, ws);\n \t}\n@@ -1054,7 +1054,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\telse if (c == '-')\n \t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n \t\t}\n-\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, \" \",\n \t\t\t\t    flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);\n \t\tbreak;\n \tcase DIFF_SYMBOL_PLUS:\n@@ -1100,7 +1100,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\t\tlen--;\n \t\t\t}\n \t\t}\n-\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, \"+\",\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK,\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);\n \t\tbreak;\n@@ -1141,7 +1141,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\telse if (c != '-')\n \t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n \t\t}\n-\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '-',\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, \"-\",\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);\n \t\tbreak;\n \tcase DIFF_SYMBOL_WORDS_PORCELAIN:\n-- \n2.18.0.203.gfac676dfb9-goog\n\n"},{"id":"352164","messageId":"20180710174552.30123-1-sbeller@google.com","threadId":"48405","inReplyTo":"CAGZ79kb4RS-KxEX+x07XsFiGwgG+1AiRUha=zcxexe1=RLL8kg@mail.gmail.com","subject":"[PATCH 0/2] Re: [PATCH v3 14/20] diff: add an internal option to dual-color diffs of diffs","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-10T17:45:50Z","receivedAt":"2018-07-10T18:53:16Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"This is developed on top of 4a68b95ce2a6 (your series here)\n\nThis is an attempt to explain the previous email better,\nspecially the second (yet unfinished) patch, but the resulting\nemit_line_0 is way clearer in my mind, dropping the 'first' character\nand instead having a 'char *sign' that (a) we can color differently for\ndual color and (b) can have multiple chars, the refactoring for the multiple\nchars would need to happen at a slightly higher level.\n\nFeel free to draw inspiration from here, but if not that is fine, too\n(as I did not fully understand the word diffing problem yet, this may add a\nburden instead of just taking these patches).\nI can send up a cleanup series after yours lands, as well.\n\nThanks,\nStefan\n\nStefan Beller (2):\n  diff.c: convert emit_line_ws_markup to take string for sign\n  WIP diff.c: clarify emit_line_0\n\n diff.c | 84 ++++++++++++++++++++++++++++------------------------------\n 1 file changed, 40 insertions(+), 44 deletions(-)\n\n-- \n2.18.0.203.gfac676dfb9-goog\n\n"},{"id":"352174","messageId":"20180710195803.131062-1-sbeller@google.com","threadId":"48405","inReplyTo":"20180710174552.30123-3-sbeller@google.com","subject":"[PATCH 1/2] diff.c: convert emit_line_ws_markup to take string for sign","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-10T19:58:03Z","receivedAt":"2018-07-10T19:58:22Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"For the diff of diffs, we have more than one character at the beginning\nof the line with special meaning, so let's pass around a string that\ncontains all the markup for the line\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n diff.c | 16 ++++++++--------\n 1 file changed, 8 insertions(+), 8 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 32500716740..028d7d9a59c 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -969,7 +969,7 @@ static void dim_moved_lines(struct diff_options *o)\n static void emit_line_ws_markup(struct diff_options *o,\n \t\t\t\tconst char *set, const char *reset,\n \t\t\t\tconst char *line, int len,\n-\t\t\t\tconst char *set_sign, char sign,\n+\t\t\t\tconst char *set_sign, const char *sign,\n \t\t\t\tunsigned ws_rule, int blank_at_eof)\n {\n \tconst char *ws = NULL;\n@@ -981,19 +981,19 @@ static void emit_line_ws_markup(struct diff_options *o,\n \t}\n \n \tif (!ws && !set_sign)\n-\t\temit_line_0(o, set, 0, reset, sign, line, len);\n+\t\temit_line_0(o, set, 0, reset, sign[0], line, len);\n \telse if (!ws) {\n \t\t/* Emit just the prefix, then the rest. */\n \t\temit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n-\t\t\t    sign, \"\", 0);\n+\t\t\t    sign[0], \"\", 0);\n \t\temit_line_0(o, set, 0, reset, 0, line, len);\n \t} else if (blank_at_eof)\n \t\t/* Blank line at EOF - paint '+' as well */\n-\t\temit_line_0(o, ws, 0, reset, sign, line, len);\n+\t\temit_line_0(o, ws, 0, reset, sign[0], line, len);\n \telse {\n \t\t/* Emit just the prefix, then the rest. */\n \t\temit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n-\t\t\t    sign, \"\", 0);\n+\t\t\t    sign[0], \"\", 0);\n \t\tws_check_emit(line, len, ws_rule,\n \t\t\t      o->file, set, reset, ws);\n \t}\n@@ -1054,7 +1054,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\telse if (c == '-')\n \t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n \t\t}\n-\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, \" \",\n \t\t\t\t    flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);\n \t\tbreak;\n \tcase DIFF_SYMBOL_PLUS:\n@@ -1100,7 +1100,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\t\tlen--;\n \t\t\t}\n \t\t}\n-\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, \"+\",\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK,\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);\n \t\tbreak;\n@@ -1141,7 +1141,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\telse if (c != '-')\n \t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n \t\t}\n-\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '-',\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, \"-\",\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);\n \t\tbreak;\n \tcase DIFF_SYMBOL_WORDS_PORCELAIN:\n-- \n2.18.0.203.gfac676dfb9-goog\n\n"},{"id":"352175","messageId":"20180710195921.131548-1-sbeller@google.com","threadId":"48405","inReplyTo":"20180710174552.30123-3-sbeller@google.com","subject":"[PATCH] diff.c: clarify emit_line_0","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-10T19:59:21Z","receivedAt":"2018-07-10T19:59:27Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"emit_line_0 grew complicated again, so here is an attempt to make it\na bit simpler. emit_line_0 is called for all lines that are added,\nremoved or context lines, and it follows the format:\n\n <sign color> <sign> <main color> <content of length 'len'> <reset> \\\n    <CR> <LF>\n\nwith each of the components optional.\n\nAnother follow up cleanup (that also touches the tests) could be\na stricter check to consolidate with ws_check_emit (and not emit the\ncolor/reset twice).\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n \n oops wrong patch, this one should do.\n Now this passes all tests. \n\n diff.c                     | 84 +++++++++++++++++++-------------------\n t/t4015-diff-whitespace.sh | 10 ++---\n 2 files changed, 48 insertions(+), 46 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 028d7d9a59c..0b00df7b3c8 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -563,42 +563,48 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,\n }\n \n static void emit_line_0(struct diff_options *o,\n-\t\t\tconst char *set, unsigned reverse, const char *reset,\n-\t\t\tint first, const char *line, int len)\n+\t\t\tconst char *maincolor, const char *signcolor,\n+\t\t\tconst char *reset, const char *sign,\n+\t\t\tconst char *line, int len)\n {\n \tint has_trailing_newline, has_trailing_carriage_return;\n-\tint nofirst;\n \tFILE *file = o->file;\n \n-\tif (first)\n-\t\tfputs(diff_line_prefix(o), file);\n-\telse if (!len)\n-\t\treturn;\n+\tfputs(diff_line_prefix(o), file);\n \n-\tif (len == 0) {\n-\t\thas_trailing_newline = (first == '\\n');\n-\t\thas_trailing_carriage_return = (!has_trailing_newline &&\n-\t\t\t\t\t\t(first == '\\r'));\n-\t\tnofirst = has_trailing_newline || has_trailing_carriage_return;\n-\t} else {\n-\t\thas_trailing_newline = (len > 0 && line[len-1] == '\\n');\n-\t\tif (has_trailing_newline)\n-\t\t\tlen--;\n-\t\thas_trailing_carriage_return = (len > 0 && line[len-1] == '\\r');\n-\t\tif (has_trailing_carriage_return)\n-\t\t\tlen--;\n-\t\tnofirst = 0;\n-\t}\n+\thas_trailing_newline = (len > 0 && line[len-1] == '\\n');\n+\tif (has_trailing_newline)\n+\t\tlen--;\n+\thas_trailing_carriage_return = (len > 0 && line[len-1] == '\\r');\n+\tif (has_trailing_carriage_return)\n+\t\tlen--;\n+\n+\t/*\n+\t * Color the sign differently if requested, otherwise use the main\n+\t * color.\n+\t */\n+\tif (signcolor)\n+\t\tfputs(signcolor, file);\n+\telse if (maincolor)\n+\t\tfputs(maincolor, file);\n+\n+\tif (sign)\n+\t\tfputs(sign, file);\n+\n+\t/*\n+\t * Only put the main color here if it we did not color the sign the\n+\t * same way already\n+\t */\n+\tif (signcolor && maincolor && strcmp(signcolor, maincolor))\n+\t\tfputs(maincolor, file);\n \n-\tif (len || !nofirst) {\n-\t\tif (reverse && want_color(o->use_color))\n-\t\t\tfputs(GIT_COLOR_REVERSE, file);\n-\t\tfputs(set, file);\n-\t\tif (first && !nofirst)\n-\t\t\tfputc(first, file);\n+\tif (len)\n \t\tfwrite(line, len, 1, file);\n+\n+\tif (((maincolor && *maincolor) || (signcolor && *signcolor) || len > 0)\n+\t    && reset)\n \t\tfputs(reset, file);\n-\t}\n+\n \tif (has_trailing_carriage_return)\n \t\tfputc('\\r', file);\n \tif (has_trailing_newline)\n@@ -608,7 +614,7 @@ static void emit_line_0(struct diff_options *o,\n static void emit_line(struct diff_options *o, const char *set, const char *reset,\n \t\t      const char *line, int len)\n {\n-\temit_line_0(o, set, 0, reset, line[0], line+1, len-1);\n+\temit_line_0(o, set, NULL, reset, NULL, line, len);\n }\n \n enum diff_symbol {\n@@ -980,20 +986,16 @@ static void emit_line_ws_markup(struct diff_options *o,\n \t\t\tws = NULL;\n \t}\n \n-\tif (!ws && !set_sign)\n-\t\temit_line_0(o, set, 0, reset, sign[0], line, len);\n-\telse if (!ws) {\n-\t\t/* Emit just the prefix, then the rest. */\n-\t\temit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n-\t\t\t    sign[0], \"\", 0);\n-\t\temit_line_0(o, set, 0, reset, 0, line, len);\n-\t} else if (blank_at_eof)\n+\tif (!ws)\n+\t\temit_line_0(o, set, set_sign, reset,\n+\t\t\t    sign, line, len);\n+\telse if (blank_at_eof)\n \t\t/* Blank line at EOF - paint '+' as well */\n-\t\temit_line_0(o, ws, 0, reset, sign[0], line, len);\n+\t\temit_line_0(o, ws, set_sign, reset, sign, line, len);\n \telse {\n \t\t/* Emit just the prefix, then the rest. */\n-\t\temit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n-\t\t\t    sign[0], \"\", 0);\n+\t\temit_line_0(o, set, set_sign, reset,\n+\t\t\t    sign, \"\", 0);\n \t\tws_check_emit(line, len, ws_rule,\n \t\t\t      o->file, set, reset, ws);\n \t}\n@@ -1016,7 +1018,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\tcontext = diff_get_color_opt(o, DIFF_CONTEXT);\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n \t\tputc('\\n', o->file);\n-\t\temit_line_0(o, context, 0, reset, '\\\\',\n+\t\temit_line_0(o, context, 0, reset, \"\\\\\",\n \t\t\t    nneof, strlen(nneof));\n \t\tbreak;\n \tcase DIFF_SYMBOL_SUBMODULE_HEADER:\ndiff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh\nindex 17df491a3ab..95baf237a83 100755\n--- a/t/t4015-diff-whitespace.sh\n+++ b/t/t4015-diff-whitespace.sh\n@@ -945,7 +945,7 @@ test_expect_success 'ws-error-highlight test setup' '\n \t<BOLD>--- a/x<RESET>\n \t<BOLD>+++ b/x<RESET>\n \t<CYAN>@@ -1,2 +1,3 @@<RESET>\n-\t <RESET>0. blank-at-eol<RESET><BLUE> <RESET>\n+\t 0. blank-at-eol<RESET><BLUE> <RESET>\n \t<RED>-<RESET><RED>1. blank-at-eol<RESET><BLUE> <RESET>\n \t<GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET>\n \t<GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>\n@@ -1140,7 +1140,7 @@ test_expect_success 'detect malicious moved code, inside file' '\n \t<CYAN>@@ -5,13 +5,6 @@<RESET> <RESET>printf(\"Hello \");<RESET>\n \t printf(\"World\\n\");<RESET>\n \t }<RESET>\n-\t <RESET>\n+\t \n \t<BRED>-int secure_foo(struct user *u)<RESET>\n \t<BRED>-{<RESET>\n \t<BLUE>-if (!u->is_allowed_foo)<RESET>\n@@ -1158,7 +1158,7 @@ test_expect_success 'detect malicious moved code, inside file' '\n \t<CYAN>@@ -4,6 +4,13 @@<RESET> <RESET>int bar()<RESET>\n \t printf(\"Hello World, but different\\n\");<RESET>\n \t }<RESET>\n-\t <RESET>\n+\t \n \t<BGREEN>+<RESET><BGREEN>int secure_foo(struct user *u)<RESET>\n \t<BGREEN>+<RESET><BGREEN>{<RESET>\n \t<GREEN>+<RESET><GREEN>foo(u);<RESET>\n@@ -1189,7 +1189,7 @@ test_expect_success 'plain moved code, inside file' '\n \t<CYAN>@@ -5,13 +5,6 @@<RESET> <RESET>printf(\"Hello \");<RESET>\n \t printf(\"World\\n\");<RESET>\n \t }<RESET>\n-\t <RESET>\n+\t \n \t<BRED>-int secure_foo(struct user *u)<RESET>\n \t<BRED>-{<RESET>\n \t<BRED>-if (!u->is_allowed_foo)<RESET>\n@@ -1207,7 +1207,7 @@ test_expect_success 'plain moved code, inside file' '\n \t<CYAN>@@ -4,6 +4,13 @@<RESET> <RESET>int bar()<RESET>\n \t printf(\"Hello World, but different\\n\");<RESET>\n \t }<RESET>\n-\t <RESET>\n+\t \n \t<BGREEN>+<RESET><BGREEN>int secure_foo(struct user *u)<RESET>\n \t<BGREEN>+<RESET><BGREEN>{<RESET>\n \t<BGREEN>+<RESET><BGREEN>foo(u);<RESET>\n-- \n2.18.0.203.gfac676dfb9-goog\n\n"},{"id":"352185","messageId":"20180710215437.33904-1-sbeller@google.com","threadId":"48405","inReplyTo":"20180710195921.131548-1-sbeller@google.com","subject":"[PATCH] ws: do not reset and set color twice","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-10T21:54:37Z","receivedAt":"2018-07-10T21:54:47Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"When outputting lines that are checked for white space, we first use\nemit_line_0 to emit the prefix, and then the ws specific code. The code\nat each site carefully sets the color and then resets it, though it is\nthe same color.\n\nAvoid setting the color twice by passing a newly introduced flag that\nindicates if the color is already set.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n\n  The whole series is also available via\n  git fetch http://github.com/stefanbeller/git ws_cleanup-ontop-range-diff\n  and concludes my cleanup on top of the range-diff series for now.\n  \n  What is left is to refactor the diff-diff from the range-diff series\n  to utilize the marker at the beginning of the line that can be more than\n  one character. I left that as I do not want to collide with your work.\n  \n  Thanks,\n  Stefan\n  \n\n cache.h                    |   2 +-\n diff.c                     |   8 +--\n t/t4015-diff-whitespace.sh | 122 ++++++++++++++++++-------------------\n ws.c                       |  28 +++++++--\n 4 files changed, 89 insertions(+), 71 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex d49092d94d1..8f53a65fa36 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1807,7 +1807,7 @@ extern unsigned whitespace_rule_cfg;\n extern unsigned whitespace_rule(const char *);\n extern unsigned parse_whitespace_rule(const char *);\n extern unsigned ws_check(const char *line, int len, unsigned ws_rule);\n-extern void ws_check_emit(const char *line, int len, unsigned ws_rule, FILE *stream, const char *set, const char *reset, const char *ws);\n+extern void ws_check_emit(const char *line, int len, unsigned ws_rule, FILE *stream, const char *set, const char *reset, const char *ws, int already_set);\n extern char *whitespace_error_string(unsigned ws);\n extern void ws_fix_copy(struct strbuf *, const char *, int, unsigned, int *);\n extern int ws_blank_line(const char *line, int len, unsigned ws_rule);\ndiff --git a/diff.c b/diff.c\nindex 0b00df7b3c8..34d02f4095b 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -993,11 +993,11 @@ static void emit_line_ws_markup(struct diff_options *o,\n \t\t/* Blank line at EOF - paint '+' as well */\n \t\temit_line_0(o, ws, set_sign, reset, sign, line, len);\n \telse {\n-\t\t/* Emit just the prefix, then the rest. */\n-\t\temit_line_0(o, set, set_sign, reset,\n+\t\t/* Emit just the prefix (with no RESET), then the rest. */\n+\t\temit_line_0(o, set, set_sign, NULL,\n \t\t\t    sign, \"\", 0);\n \t\tws_check_emit(line, len, ws_rule,\n-\t\t\t      o->file, set, reset, ws);\n+\t\t\t      o->file, set, reset, ws, 1);\n \t}\n }\n \n@@ -2918,7 +2918,7 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)\n \t\tfree(err);\n \t\temit_line(data->o, set, reset, line, 1);\n \t\tws_check_emit(line + 1, len - 1, data->ws_rule,\n-\t\t\t      data->o->file, set, reset, ws);\n+\t\t\t      data->o->file, set, reset, ws, 0);\n \t} else if (line[0] == ' ') {\n \t\tdata->lineno++;\n \t} else if (line[0] == '@') {\ndiff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh\nindex 95baf237a83..8834f2040c0 100755\n--- a/t/t4015-diff-whitespace.sh\n+++ b/t/t4015-diff-whitespace.sh\n@@ -874,9 +874,9 @@ test_expect_success 'diff that introduces a line with only tabs' '\n \t<BOLD>+++ b/x<RESET>\n \t<CYAN>@@ -1 +1,4 @@<RESET>\n \t test<RESET>\n-\t<GREEN>+<RESET><GREEN>{<RESET>\n+\t<GREEN>+{<RESET>\n \t<GREEN>+<RESET><BLUE>\t<RESET>\n-\t<GREEN>+<RESET><GREEN>}<RESET>\n+\t<GREEN>+}<RESET>\n \tEOF\n \n \ttest_cmp expected current\n@@ -906,8 +906,8 @@ test_expect_success 'diff that introduces and removes ws breakages' '\n \t<CYAN>@@ -1,2 +1,3 @@<RESET>\n \t 0. blank-at-eol <RESET>\n \t<RED>-1. blank-at-eol <RESET>\n-\t<GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET>\n-\t<GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>\n+\t<GREEN>+1. still-blank-at-eol<RESET><BLUE> <RESET>\n+\t<GREEN>+2. and a new line<RESET><BLUE> <RESET>\n \tEOF\n \n \ttest_cmp expected current\n@@ -934,9 +934,9 @@ test_expect_success 'ws-error-highlight test setup' '\n \t<BOLD>+++ b/x<RESET>\n \t<CYAN>@@ -1,2 +1,3 @@<RESET>\n \t 0. blank-at-eol <RESET>\n-\t<RED>-<RESET><RED>1. blank-at-eol<RESET><BLUE> <RESET>\n-\t<GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET>\n-\t<GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>\n+\t<RED>-1. blank-at-eol<RESET><BLUE> <RESET>\n+\t<GREEN>+1. still-blank-at-eol<RESET><BLUE> <RESET>\n+\t<GREEN>+2. and a new line<RESET><BLUE> <RESET>\n \tEOF\n \n \tcat >expect.all <<-\\EOF &&\n@@ -946,9 +946,9 @@ test_expect_success 'ws-error-highlight test setup' '\n \t<BOLD>+++ b/x<RESET>\n \t<CYAN>@@ -1,2 +1,3 @@<RESET>\n \t 0. blank-at-eol<RESET><BLUE> <RESET>\n-\t<RED>-<RESET><RED>1. blank-at-eol<RESET><BLUE> <RESET>\n-\t<GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET>\n-\t<GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>\n+\t<RED>-1. blank-at-eol<RESET><BLUE> <RESET>\n+\t<GREEN>+1. still-blank-at-eol<RESET><BLUE> <RESET>\n+\t<GREEN>+2. and a new line<RESET><BLUE> <RESET>\n \tEOF\n \n \tcat >expect.none <<-\\EOF\n@@ -1038,11 +1038,11 @@ test_expect_success 'detect moved code, complete file' '\n \t<BOLD>--- /dev/null<RESET>\n \t<BOLD>+++ b/main.c<RESET>\n \t<CYAN>@@ -0,0 +1,5 @@<RESET>\n-\t<BGREEN>+<RESET><BGREEN>#include<stdio.h><RESET>\n-\t<BGREEN>+<RESET><BGREEN>main()<RESET>\n-\t<BGREEN>+<RESET><BGREEN>{<RESET>\n-\t<BGREEN>+<RESET><BGREEN>printf(\"Hello World\");<RESET>\n-\t<BGREEN>+<RESET><BGREEN>}<RESET>\n+\t<BGREEN>+#include<stdio.h><RESET>\n+\t<BGREEN>+main()<RESET>\n+\t<BGREEN>+{<RESET>\n+\t<BGREEN>+printf(\"Hello World\");<RESET>\n+\t<BGREEN>+}<RESET>\n \t<BOLD>diff --git a/test.c b/test.c<RESET>\n \t<BOLD>deleted file mode 100644<RESET>\n \t<BOLD>index a986c57..0000000<RESET>\n@@ -1159,12 +1159,12 @@ test_expect_success 'detect malicious moved code, inside file' '\n \t printf(\"Hello World, but different\\n\");<RESET>\n \t }<RESET>\n \t \n-\t<BGREEN>+<RESET><BGREEN>int secure_foo(struct user *u)<RESET>\n-\t<BGREEN>+<RESET><BGREEN>{<RESET>\n-\t<GREEN>+<RESET><GREEN>foo(u);<RESET>\n-\t<BGREEN>+<RESET><BGREEN>if (!u->is_allowed_foo)<RESET>\n-\t<BGREEN>+<RESET><BGREEN>return;<RESET>\n-\t<GREEN>+<RESET><GREEN>}<RESET>\n+\t<BGREEN>+int secure_foo(struct user *u)<RESET>\n+\t<BGREEN>+{<RESET>\n+\t<GREEN>+foo(u);<RESET>\n+\t<BGREEN>+if (!u->is_allowed_foo)<RESET>\n+\t<BGREEN>+return;<RESET>\n+\t<GREEN>+}<RESET>\n \t<GREEN>+<RESET>\n \t int another_function()<RESET>\n \t {<RESET>\n@@ -1208,12 +1208,12 @@ test_expect_success 'plain moved code, inside file' '\n \t printf(\"Hello World, but different\\n\");<RESET>\n \t }<RESET>\n \t \n-\t<BGREEN>+<RESET><BGREEN>int secure_foo(struct user *u)<RESET>\n-\t<BGREEN>+<RESET><BGREEN>{<RESET>\n-\t<BGREEN>+<RESET><BGREEN>foo(u);<RESET>\n-\t<BGREEN>+<RESET><BGREEN>if (!u->is_allowed_foo)<RESET>\n-\t<BGREEN>+<RESET><BGREEN>return;<RESET>\n-\t<BGREEN>+<RESET><BGREEN>}<RESET>\n+\t<BGREEN>+int secure_foo(struct user *u)<RESET>\n+\t<BGREEN>+{<RESET>\n+\t<BGREEN>+foo(u);<RESET>\n+\t<BGREEN>+if (!u->is_allowed_foo)<RESET>\n+\t<BGREEN>+return;<RESET>\n+\t<BGREEN>+}<RESET>\n \t<BGREEN>+<RESET>\n \t int another_function()<RESET>\n \t {<RESET>\n@@ -1288,12 +1288,12 @@ test_expect_success 'detect permutations inside moved code -- dimmed_zebra' '\n \t line 7<RESET>\n \t line 8<RESET>\n \t line 9<RESET>\n-\t<BCYAN>+<RESET><BCYAN>long line 1<RESET>\n-\t<BCYAN>+<RESET><BCYAN>long line 2<RESET>\n-\t<CYAN>+<RESET><CYAN>long line 3<RESET>\n-\t<YELLOW>+<RESET><YELLOW>long line 14<RESET>\n-\t<BYELLOW>+<RESET><BYELLOW>long line 15<RESET>\n-\t<BYELLOW>+<RESET><BYELLOW>long line 16<RESET>\n+\t<BCYAN>+long line 1<RESET>\n+\t<BCYAN>+long line 2<RESET>\n+\t<CYAN>+long line 3<RESET>\n+\t<YELLOW>+long line 14<RESET>\n+\t<BYELLOW>+long line 15<RESET>\n+\t<BYELLOW>+long line 16<RESET>\n \t line 10<RESET>\n \t line 11<RESET>\n \t line 12<RESET>\n@@ -1332,12 +1332,12 @@ test_expect_success 'cmd option assumes configured colored-moved' '\n \t line 7<RESET>\n \t line 8<RESET>\n \t line 9<RESET>\n-\t<CYAN>+<RESET><CYAN>long line 1<RESET>\n-\t<CYAN>+<RESET><CYAN>long line 2<RESET>\n-\t<CYAN>+<RESET><CYAN>long line 3<RESET>\n-\t<YELLOW>+<RESET><YELLOW>long line 14<RESET>\n-\t<YELLOW>+<RESET><YELLOW>long line 15<RESET>\n-\t<YELLOW>+<RESET><YELLOW>long line 16<RESET>\n+\t<CYAN>+long line 1<RESET>\n+\t<CYAN>+long line 2<RESET>\n+\t<CYAN>+long line 3<RESET>\n+\t<YELLOW>+long line 14<RESET>\n+\t<YELLOW>+long line 15<RESET>\n+\t<YELLOW>+long line 16<RESET>\n \t line 10<RESET>\n \t line 11<RESET>\n \t line 12<RESET>\n@@ -1467,10 +1467,10 @@ test_expect_success 'move detection ignoring whitespace changes' '\n \t<BOLD>--- a/lines.txt<RESET>\n \t<BOLD>+++ b/lines.txt<RESET>\n \t<CYAN>@@ -1,9 +1,9 @@<RESET>\n-\t<GREEN>+<RESET><GREEN>long\tline 6<RESET>\n-\t<GREEN>+<RESET><GREEN>long\tline 7<RESET>\n-\t<GREEN>+<RESET><GREEN>long\tline 8<RESET>\n-\t<GREEN>+<RESET><GREEN>long li\tne 9<RESET>\n+\t<GREEN>+long\tline 6<RESET>\n+\t<GREEN>+long\tline 7<RESET>\n+\t<GREEN>+long\tline 8<RESET>\n+\t<GREEN>+long li\tne 9<RESET>\n \t line 1<RESET>\n \t line 2<RESET>\n \t line 3<RESET>\n@@ -1491,10 +1491,10 @@ test_expect_success 'move detection ignoring whitespace changes' '\n \t<BOLD>--- a/lines.txt<RESET>\n \t<BOLD>+++ b/lines.txt<RESET>\n \t<CYAN>@@ -1,9 +1,9 @@<RESET>\n-\t<CYAN>+<RESET><CYAN>long\tline 6<RESET>\n-\t<CYAN>+<RESET><CYAN>long\tline 7<RESET>\n-\t<CYAN>+<RESET><CYAN>long\tline 8<RESET>\n-\t<GREEN>+<RESET><GREEN>long li\tne 9<RESET>\n+\t<CYAN>+long\tline 6<RESET>\n+\t<CYAN>+long\tline 7<RESET>\n+\t<CYAN>+long\tline 8<RESET>\n+\t<GREEN>+long li\tne 9<RESET>\n \t line 1<RESET>\n \t line 2<RESET>\n \t line 3<RESET>\n@@ -1534,10 +1534,10 @@ test_expect_success 'move detection ignoring whitespace at eol' '\n \t<BOLD>--- a/lines.txt<RESET>\n \t<BOLD>+++ b/lines.txt<RESET>\n \t<CYAN>@@ -1,9 +1,9 @@<RESET>\n-\t<GREEN>+<RESET><GREEN>long line 6\t<RESET>\n-\t<GREEN>+<RESET><GREEN>long line 7\t<RESET>\n-\t<GREEN>+<RESET><GREEN>long line 8\t<RESET>\n-\t<GREEN>+<RESET><GREEN>long\tline 9\t<RESET>\n+\t<GREEN>+long line 6\t<RESET>\n+\t<GREEN>+long line 7\t<RESET>\n+\t<GREEN>+long line 8\t<RESET>\n+\t<GREEN>+long\tline 9\t<RESET>\n \t line 1<RESET>\n \t line 2<RESET>\n \t line 3<RESET>\n@@ -1558,10 +1558,10 @@ test_expect_success 'move detection ignoring whitespace at eol' '\n \t<BOLD>--- a/lines.txt<RESET>\n \t<BOLD>+++ b/lines.txt<RESET>\n \t<CYAN>@@ -1,9 +1,9 @@<RESET>\n-\t<CYAN>+<RESET><CYAN>long line 6\t<RESET>\n-\t<CYAN>+<RESET><CYAN>long line 7\t<RESET>\n-\t<CYAN>+<RESET><CYAN>long line 8\t<RESET>\n-\t<GREEN>+<RESET><GREEN>long\tline 9\t<RESET>\n+\t<CYAN>+long line 6\t<RESET>\n+\t<CYAN>+long line 7\t<RESET>\n+\t<CYAN>+long line 8\t<RESET>\n+\t<GREEN>+long\tline 9\t<RESET>\n \t line 1<RESET>\n \t line 2<RESET>\n \t line 3<RESET>\n@@ -1605,7 +1605,7 @@ test_expect_success '--color-moved block at end of diff output respects MIN_ALNU\n \t<BOLD>--- a/bar<RESET>\n \t<BOLD>+++ b/bar<RESET>\n \t<CYAN>@@ -0,0 +1 @@<RESET>\n-\t<GREEN>+<RESET><GREEN>line1<RESET>\n+\t<GREEN>+line1<RESET>\n \t<BOLD>diff --git a/foo b/foo<RESET>\n \t<BOLD>--- a/foo<RESET>\n \t<BOLD>+++ b/foo<RESET>\n@@ -1644,8 +1644,8 @@ test_expect_success '--color-moved respects MIN_ALNUM_COUNT' '\n \t<BOLD>--- a/bar<RESET>\n \t<BOLD>+++ b/bar<RESET>\n \t<CYAN>@@ -0,0 +1,2 @@<RESET>\n-\t<BOLD;CYAN>+<RESET><BOLD;CYAN>twenty chars 234567890<RESET>\n-\t<GREEN>+<RESET><GREEN>nineteen chars 456789<RESET>\n+\t<BOLD;CYAN>+twenty chars 234567890<RESET>\n+\t<GREEN>+nineteen chars 456789<RESET>\n \t<BOLD>diff --git a/foo b/foo<RESET>\n \t<BOLD>--- a/foo<RESET>\n \t<BOLD>+++ b/foo<RESET>\n@@ -1685,9 +1685,9 @@ test_expect_success '--color-moved treats adjacent blocks as separate for MIN_AL\n \t<BOLD>--- a/bar<RESET>\n \t<BOLD>+++ b/bar<RESET>\n \t<CYAN>@@ -0,0 +1,3 @@<RESET>\n-\t<GREEN>+<RESET><GREEN>7charsB<RESET>\n-\t<GREEN>+<RESET><GREEN>7charsC<RESET>\n-\t<GREEN>+<RESET><GREEN>7charsA<RESET>\n+\t<GREEN>+7charsB<RESET>\n+\t<GREEN>+7charsC<RESET>\n+\t<GREEN>+7charsA<RESET>\n \t<BOLD>diff --git a/foo b/foo<RESET>\n \t<BOLD>--- a/foo<RESET>\n \t<BOLD>+++ b/foo<RESET>\ndiff --git a/ws.c b/ws.c\nindex a07caedd5a5..cb4a95c25dc 100644\n--- a/ws.c\n+++ b/ws.c\n@@ -139,10 +139,19 @@ char *whitespace_error_string(unsigned ws)\n \treturn strbuf_detach(&err, NULL);\n }\n \n+static inline void optional_reset(FILE *stream, const char *reset, int *already_set)\n+{\n+\tif (*already_set) {\n+\t\tfputs(reset, stream);\n+\t\t*already_set = 0;\n+\t}\n+}\n+\n /* If stream is non-NULL, emits the line after checking. */\n static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,\n \t\t\t\tFILE *stream, const char *set,\n-\t\t\t\tconst char *reset, const char *ws)\n+\t\t\t\tconst char *reset, const char *ws,\n+\t\t\t\tint already_set)\n {\n \tunsigned result = 0;\n \tint written = 0;\n@@ -186,6 +195,7 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,\n \t\tif ((ws_rule & WS_SPACE_BEFORE_TAB) && written < i) {\n \t\t\tresult |= WS_SPACE_BEFORE_TAB;\n \t\t\tif (stream) {\n+\t\t\t\toptional_reset(stream, reset, &already_set);\n \t\t\t\tfputs(ws, stream);\n \t\t\t\tfwrite(line + written, i - written, 1, stream);\n \t\t\t\tfputs(reset, stream);\n@@ -194,12 +204,14 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,\n \t\t} else if (ws_rule & WS_TAB_IN_INDENT) {\n \t\t\tresult |= WS_TAB_IN_INDENT;\n \t\t\tif (stream) {\n+\t\t\t\toptional_reset(stream, reset, &already_set);\n \t\t\t\tfwrite(line + written, i - written, 1, stream);\n \t\t\t\tfputs(ws, stream);\n \t\t\t\tfwrite(line + i, 1, 1, stream);\n \t\t\t\tfputs(reset, stream);\n \t\t\t}\n \t\t} else if (stream) {\n+\t\t\toptional_reset(stream, reset, &already_set);\n \t\t\tfwrite(line + written, i - written + 1, 1, stream);\n \t\t}\n \t\twritten = i + 1;\n@@ -209,6 +221,7 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,\n \tif ((ws_rule & WS_INDENT_WITH_NON_TAB) && i - written >= ws_tab_width(ws_rule)) {\n \t\tresult |= WS_INDENT_WITH_NON_TAB;\n \t\tif (stream) {\n+\t\t\toptional_reset(stream, reset, &already_set);\n \t\t\tfputs(ws, stream);\n \t\t\tfwrite(line + written, i - written, 1, stream);\n \t\t\tfputs(reset, stream);\n@@ -224,19 +237,24 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,\n \n \t\t/* Emit non-highlighted (middle) segment. */\n \t\tif (trailing_whitespace - written > 0) {\n-\t\t\tfputs(set, stream);\n+\t\t\tif (!already_set)\n+\t\t\t\tfputs(set, stream);\n \t\t\tfwrite(line + written,\n \t\t\t    trailing_whitespace - written, 1, stream);\n \t\t\tfputs(reset, stream);\n+\t\t\talready_set = 0;\n \t\t}\n \n \t\t/* Highlight errors in trailing whitespace. */\n \t\tif (trailing_whitespace != len) {\n+\t\t\toptional_reset(stream, reset, &already_set);\n \t\t\tfputs(ws, stream);\n \t\t\tfwrite(line + trailing_whitespace,\n \t\t\t    len - trailing_whitespace, 1, stream);\n \t\t\tfputs(reset, stream);\n \t\t}\n+\t\tif (already_set)\n+\t\t\tfputs(reset, stream);\n \t\tif (trailing_carriage_return)\n \t\t\tfputc('\\r', stream);\n \t\tif (trailing_newline)\n@@ -247,14 +265,14 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,\n \n void ws_check_emit(const char *line, int len, unsigned ws_rule,\n \t\t   FILE *stream, const char *set,\n-\t\t   const char *reset, const char *ws)\n+\t\t   const char *reset, const char *ws, int already_set)\n {\n-\t(void)ws_check_emit_1(line, len, ws_rule, stream, set, reset, ws);\n+\t(void)ws_check_emit_1(line, len, ws_rule, stream, set, reset, ws, already_set);\n }\n \n unsigned ws_check(const char *line, int len, unsigned ws_rule)\n {\n-\treturn ws_check_emit_1(line, len, ws_rule, NULL, NULL, NULL, NULL);\n+\treturn ws_check_emit_1(line, len, ws_rule, NULL, NULL, NULL, NULL, 0);\n }\n \n int ws_blank_line(const char *line, int len, unsigned ws_rule)\n-- \n2.18.0.203.gfac676dfb9-goog\n\n"},{"id":"352225","messageId":"20180711100717.3747-1-szeder.dev@gmail.com","threadId":"48405","inReplyTo":"39272eefcfe66de3ca1aa2ee43d6626ce558caae.1530617166.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2018-07-11T10:07:17Z","receivedAt":"2018-07-11T10:07:23Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"> diff --git a/linear-assignment.c b/linear-assignment.c\n> new file mode 100644\n> index 000000000..0b0344b5f\n> --- /dev/null\n> +++ b/linear-assignment.c\n> @@ -0,0 +1,203 @@\n> +/*\n> + * Based on: Jonker, R., & Volgenant, A. (1987). <i>A shortest augmenting path\n> + * algorithm for dense and sparse linear assignment problems</i>. Computing,\n> + * 38(4), 325-340.\n> + */\n> +#include \"cache.h\"\n> +#include \"linear-assignment.h\"\n> +\n> +#define COST(column, row) cost[(column) + column_count * (row)]\n> +\n> +/*\n> + * The parameter `cost` is the cost matrix: the cost to assign column j to row\n> + * i is `cost[j + column_count * i].\n> + */\n> +void compute_assignment(int column_count, int row_count, int *cost,\n> +\t\t\tint *column2row, int *row2column)\n> +{\n\n[...]\n\n> +update:\n> +\t\t/* updating of the column pieces */\n> +\t\tfor (k = 0; k < last; k++) {\n> +\t\t\tint j1 = col[k];\n> +\t\t\tv[j1] += d[j1] - min;\n> +\t\t}\n> +\n> +\t\t/* augmentation */\n> +\t\tdo {\n> +\t\t\tif (j < 0)\n> +\t\t\t\tBUG(\"negative j: %d\", j);\n> +\t\t\ti = pred[j];\n> +\t\t\tcolumn2row[j] = i;\n> +\t\t\tk = j;\n> +\t\t\tj = row2column[i];\n> +\t\t\trow2column[i] = k;\n\nCoccinelle suggests using SWAP(j, row2column[i]) instead of the last\nthree lines above.\nIt's more idiomatic, and it avoids (ab)using the 'k' variable\n(elsewhere used as loop variable) as a temporary variable.\n\n> +\t\t} while (i1 != i);\n> +\t}\n> +\n> +\tfree(col);\n> +\tfree(pred);\n> +\tfree(d);\n> +\tfree(v);\n> +\tfree(free_row);\n> +}\n"},{"id":"352264","messageId":"xmqqefg94uq1.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1807092342490.75@tvgsbejvaqbjf.bet","subject":"Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-11T16:12:38Z","receivedAt":"2018-07-11T16:12:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> To summarize, there are two commits recorded for that Message-Id, the\n> later one not mapped back, and neither is the correct commit that made it\n> into `master`.\n>\n> It would be nice to figure out what went wrong there, and how to fix it\n> for the future (and also to fix up the existing mis-mappings in `amlog`).\n\nI think what happened is that I used to have post-rewrite, but\nbecause it did not solve the real issue of multiple commits existing\nfor the same message ID (either because of amending, or because of\nrunning \"am\" multiple times while looking for the best base to\ncontruct a topic branch for the series that contains it) *and* the\none that will eventually used in the final history may not be the\nlast one (e.g. I may \"am\" twice to see if an older base I use in my\nsecond attempt is a better one than the base I originally used, and\nthe patches may even apply cleanly to the older history, but may\nturn out to need semantic adjustment, at which point I would discard\nthat second attempt and use the old commit from the first attempt\nthat built on a newer base), I stopped using it.\n\nThe mid-to-commit, for it to be relialble, needs to keep mapping for\nall the commits created from a single message, instead of being the\nlast-one-survives mapping.  I just didn't have that much interest\nback when I decided it was not worth and dropped the post-rewrite, I\nthink.\n\n"},{"id":"352345","messageId":"nycvar.QRO.7.76.6.1807121711170.75@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180711100717.3747-1-szeder.dev@gmail.com","subject":"Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-12T15:11:53Z","receivedAt":"2018-07-12T15:12:19Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Gábor,\n\nOn Wed, 11 Jul 2018, SZEDER Gábor wrote:\n\n> > diff --git a/linear-assignment.c b/linear-assignment.c\n> > new file mode 100644\n> > index 000000000..0b0344b5f\n> > --- /dev/null\n> > +++ b/linear-assignment.c\n> > @@ -0,0 +1,203 @@\n> > +/*\n> > + * Based on: Jonker, R., & Volgenant, A. (1987). <i>A shortest augmenting path\n> > + * algorithm for dense and sparse linear assignment problems</i>. Computing,\n> > + * 38(4), 325-340.\n> > + */\n> > +#include \"cache.h\"\n> > +#include \"linear-assignment.h\"\n> > +\n> > +#define COST(column, row) cost[(column) + column_count * (row)]\n> > +\n> > +/*\n> > + * The parameter `cost` is the cost matrix: the cost to assign column j to row\n> > + * i is `cost[j + column_count * i].\n> > + */\n> > +void compute_assignment(int column_count, int row_count, int *cost,\n> > +\t\t\tint *column2row, int *row2column)\n> > +{\n> \n> [...]\n> \n> > +update:\n> > +\t\t/* updating of the column pieces */\n> > +\t\tfor (k = 0; k < last; k++) {\n> > +\t\t\tint j1 = col[k];\n> > +\t\t\tv[j1] += d[j1] - min;\n> > +\t\t}\n> > +\n> > +\t\t/* augmentation */\n> > +\t\tdo {\n> > +\t\t\tif (j < 0)\n> > +\t\t\t\tBUG(\"negative j: %d\", j);\n> > +\t\t\ti = pred[j];\n> > +\t\t\tcolumn2row[j] = i;\n> > +\t\t\tk = j;\n> > +\t\t\tj = row2column[i];\n> > +\t\t\trow2column[i] = k;\n> \n> Coccinelle suggests using SWAP(j, row2column[i]) instead of the last\n> three lines above.\n> It's more idiomatic, and it avoids (ab)using the 'k' variable\n> (elsewhere used as loop variable) as a temporary variable.\n\nGood point.\n\nI audited the rest of the code in this file, and there are no more swap\noperations.\n\nThanks,\nDscho"},{"id":"352347","messageId":"nycvar.QRO.7.76.6.1807121720340.75@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"xmqqefg94uq1.fsf@gitster-ct.c.googlers.com","subject":"Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-12T15:23:24Z","receivedAt":"2018-07-12T15:23:49Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Wed, 11 Jul 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > To summarize, there are two commits recorded for that Message-Id, the\n> > later one not mapped back, and neither is the correct commit that made it\n> > into `master`.\n> >\n> > It would be nice to figure out what went wrong there, and how to fix it\n> > for the future (and also to fix up the existing mis-mappings in `amlog`).\n> \n> I think what happened is that I used to have post-rewrite, but\n> because it did not solve the real issue of multiple commits existing\n> for the same message ID (either because of amending, or because of\n> running \"am\" multiple times while looking for the best base to\n> contruct a topic branch for the series that contains it) *and* the\n> one that will eventually used in the final history may not be the\n> last one (e.g. I may \"am\" twice to see if an older base I use in my\n> second attempt is a better one than the base I originally used, and\n> the patches may even apply cleanly to the older history, but may\n> turn out to need semantic adjustment, at which point I would discard\n> that second attempt and use the old commit from the first attempt\n> that built on a newer base), I stopped using it.\n> \n> The mid-to-commit, for it to be relialble, needs to keep mapping for\n> all the commits created from a single message, instead of being the\n> last-one-survives mapping.  I just didn't have that much interest\n> back when I decided it was not worth and dropped the post-rewrite, I\n> think.\n\nI would like to ask you to reinstate the post-rewrite hook, as it still\nimproves the situation over the current one.\n\nOf course, it would be nice to get the automation into a shape where\nthe mappings in `refs/notes/amlog` of commits that hit `next` are fixed,\nif necessary, to stop referring to commits that did not make it into\n`next`.\n\nBecause the *concept* of `amlog` is quite useful, to put back at least\n*some* of the information we lost by transiting Git commits via mails\nwithout any connection to their original commits. It is still the most\nannoying thing when I contribute patches myself.\n\nCiao,\nDscho\n"},{"id":"352367","messageId":"xmqq8t6gz8xz.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1807121720340.75@tvgsbejvaqbjf.bet","subject":"Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-12T16:59:36Z","receivedAt":"2018-07-12T16:59:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> I would like to ask you to reinstate the post-rewrite hook, as it still\n> improves the situation over the current one.\n\nWithout post-rewrite I seem to be getting correct amlog entries for\ncommits created by \"git rebase\"; do our rebase--am backend still\ntrigger post-applypatch hook in its \"am\" phase to apply the patches\ncreated with \"format-patch\"?\n\n"},{"id":"352600","messageId":"CAPig+cQo1hM59P5eB4YTw=zvMAWh0OO8zsf0PMcwYoJtGwLKCQ@mail.gmail.com","threadId":"48405","inReplyTo":"076e1192d15562d868a8a6014f36155f360da83d.1530617166.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 03/20] range-diff: first rudimentary implementation","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-16T06:55:25Z","receivedAt":"2018-07-16T06:55:40Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Jul 3, 2018 at 7:27 AM Johannes Schindelin via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> At this stage, `git range-diff` can determine corresponding commits\n> of two related commit ranges. This makes use of the recently introduced\n> implementation of the Hungarian algorithm.\n\nDid you want s/Hungarian/Jonker-Volgenant/ here? (Not worth a re-roll.)\n\n> The core of this patch is a straight port of the ideas of tbdiff, the\n> apparently dormant project at https://github.com/trast/tbdiff.\n> [...]\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> diff --git a/builtin/range-diff.c b/builtin/range-diff.c\n> @@ -17,9 +18,49 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n> +       int res = 0;\n> +       struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n>\n> -       argc = parse_options(argc, argv, NULL, options,\n> -                            builtin_range_diff_usage, 0);\n> +       argc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,\n> +                            0);\n\nThis parse_options() change appears to be merely a re-wrapping of the\nline between patches 2 and 3.\n\n> -       return 0;\n> +       if (argc == 2) {\n> +               if (!strstr(argv[0], \"..\"))\n> +                       warning(_(\"no .. in range: '%s'\"), argv[0]);\n> +               strbuf_addstr(&range1, argv[0]);\n> +\n> +               if (!strstr(argv[1], \"..\"))\n> +                       warning(_(\"no .. in range: '%s'\"), argv[1]);\n> +               strbuf_addstr(&range2, argv[1]);\n\nShould these die() (like the \"...\" case below) rather than warning()?\nWarning and continuing doesn't seem like intended behavior. When I\ntest this with on git.git and omit the \"..\", git sits for a long, long\ntime consuming the CPU. I guess it's git-log'ing pretty much the\nentire history.\n\n    % GIT_TRACE=1 git range-diff v1 v2\n    warning: no .. in range: 'v1'\n    warning: no .. in range: 'v2'\n    trace: git log --no-color -p --no-merges --reverse \\\n        --date-order --decorate=no --no-abbrev-commit v1\n    ^C\n    %\n\n> +       } else if (argc == 3) {\n> +               strbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n> +               strbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n> +       } else if (argc == 1) {\n> +               const char *b = strstr(argv[0], \"...\"), *a = argv[0];\n> +               int a_len;\n> +\n> +               if (!b)\n> +                       die(_(\"single arg format requires a symmetric range\"));\n> diff --git a/range-diff.c b/range-diff.c\n> @@ -0,0 +1,307 @@\n> +static int read_patches(const char *range, struct string_list *list)\n> +{\n> +       while (strbuf_getline(&line, in) != EOF) {\n> +               if (skip_prefix(line.buf, \"commit \", &p)) {\n> +                       [...]\n> +                       in_header = 1;\n> +                       continue;\n> +               }\n> +               if (starts_with(line.buf, \"diff --git\")) {\n> +                       in_header = 0;\n> +                       [...]\n> +               } else if (in_header) {\n> +                       if (starts_with(line.buf, \"Author: \")) {\n> +                               [...]\n> +                       } else if (starts_with(line.buf, \"    \")) {\n> +                               [...]\n> +                       }\n> +                       continue;\n> +               } else if (starts_with(line.buf, \"@@ \"))\n> +                       strbuf_addstr(&buf, \"@@\");\n> +               else if (line.buf[0] && !starts_with(line.buf, \"index \"))\n> +                       /*\n> +                        * A completely blank (not ' \\n', which is context)\n> +                        * line is not valid in a diff.  We skip it\n> +                        * silently, because this neatly handles the blank\n> +                        * separator line between commits in git-log\n> +                        * output.\n> +                        */\n> +                       strbuf_addbuf(&buf, &line);\n\nThis comment had me confused for a bit since it doesn't seem to agree\nwith the 'then' part of the 'if', but rather applies more to the\n'else'.  Had it been split into two parts (one for 'then' and one for\n'else'), it might have been easier to digest. That is, something like:\n\n    else if (line.buf[0] && !starts_with(..., \"index \"))\n        /* A line we wish to keep. */\n        strbuf_addbuf(...);\n    else\n        /*\n         * A completely blank line between commits or\n         * or one in which we are otherwise not interested.\n         */\n        continue;\n\nor something. Structuring it a bit differently might have helped, as well:\n\n    else if (!line.buf[0])\n        /* A completely blank line between commits. */\n        continue;\n    else if (starts_with(..., \"index \"))\n        /* A line in which we are not interested. */\n        continue;\n    else\n        strbuf_addbuf(&buf, &line);\n\nNot at all worth a re-roll.\n\n> +               else\n> +                       continue;\n> +       if (util)\n> +               string_list_append(list, buf.buf)->util = util;\n\nSo, the parser is grabbing each commit and shoving all the\n\"interesting\" information about the commit in a 'patch_util'. It grabs\nthe OID, author, the commit message (indented), the \"diff --git\",\n\"+++\", \"---\" lines (but ignores \"index\" line), \"@@\" lines (but\nignoring the gunk after \"@@\"), and all context and patch lines.\n\nLooks good.\n\n> +       strbuf_release(&buf);\n> +\n> +       if (finish_command(&cp))\n> +               return -1;\n> +\n> +       return 0;\n> +}\n"},{"id":"352601","messageId":"CAPig+cTnRi=HuyZy+bMKeU9qutZb3K5C4qTb7gCQz7GyGN=FRw@mail.gmail.com","threadId":"48405","inReplyTo":"6b31cbf72c4752771965de333b3cb6e82cf90b2b.1530617166.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 09/20] range-diff: adjust the output of the commit pairs","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-16T07:21:29Z","receivedAt":"2018-07-16T07:21:42Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Jul 3, 2018 at 7:26 AM Johannes Schindelin via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> This change brings `git range-diff` yet another step closer to\n> feature parity with tbdiff: it now shows the oneline, too, and indicates\n> with `=` when the commits have identical diffs.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> diff --git a/range-diff.c b/range-diff.c\n> @@ -251,9 +253,57 @@ static void get_correspondences(struct string_list *a, struct string_list *b,\n> +static void output_pair_header(struct strbuf *buf,\n> +                              struct patch_util *a_util,\n> +                              struct patch_util *b_util)\n>  {\n> -       return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n> +       static char *dashes;\n> +       struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n> +       struct commit *commit;\n> +\n> +       if (!dashes) {\n> +               char *p;\n> +\n> +               dashes = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV));\n\nIt's nice to see that the bulk of the range-diff functionality has\nbeen libified in this re-roll (residing in range-diff.c rather than\nbuiltin/range-diff.c as in earlier versions), so it's somewhat\nsurprising to see libified code holding onto the 'dashes' buffer like\nthis in a static variable. An alternative would have been for the\ncaller to pass in the same buffer to output_pair_header() for re-use,\nand then dispose of it at the end of processing.\n\n> +               for (p = dashes; *p; p++)\n> +                       *p = '-';\n> +       }\n> +\n> +       strbuf_reset(buf);\n\n...much like 'buf' is allocated by the caller, passed in and re-used\nfor each invocation, then released by the caller at the end.\n"},{"id":"352602","messageId":"CAPig+cQB8p1Eo0qyfD78cfSY6N=N9i-KBw5UO2OULXfA8+A=tQ@mail.gmail.com","threadId":"48405","inReplyTo":"3d9e5b0ba383bab3a30b74a96a1d78557e168b7f.1530617166.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 11/20] range-diff: add tests","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-16T07:28:49Z","receivedAt":"2018-07-16T07:36:45Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Jul 3, 2018 at 7:26 AM Thomas Rast via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> These are essentially lifted from https://github.com/trast/tbdiff, with\n> light touch-ups to account for the command now being an option of `git\n> branch`.\n\nThe \"option of `git branch`\" mention is outdated. Perhaps just drop\neverything after \"...touch-ups\" (or mention \"range-diff\" instead).\n\n> Apart from renaming `tbdiff` to `range-diff`, only one test case needed\n> to be adjusted: 11 - 'changed message'.\n> [...]\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> diff --git a/t/.gitattributes b/t/.gitattributes\n> @@ -18,5 +18,6 @@ t[0-9][0-9][0-9][0-9]/* -whitespace\n>  /t7500/* eol=lf\n> +/t7910/* eol=lf\n\nDoes this need to be changed to t3206?\n\n>  /t8005/*.txt eol=lf\n> diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh\n> new file mode 100755\n> index 000000000..2237c7f4a\n> --- /dev/null\n> +++ b/t/t3206-range-diff.sh\n"},{"id":"352603","messageId":"CAPig+cSin5Em1m7uigSLnNg9wbz+fz8uy4sJOECq2Ke2+v+D4A@mail.gmail.com","threadId":"48405","inReplyTo":"799da25ef35d2b23dc0df1e6af0772e634f39f19.1530617166.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 17/20] range-diff: add a man page","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-16T08:01:03Z","receivedAt":"2018-07-16T08:01:16Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Jul 3, 2018 at 7:27 AM Johannes Schindelin via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> The bulk of this patch consists of a heavily butchered version of\n> tbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from\n> https://github.com/trast/tbdiff.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\n> @@ -0,0 +1,235 @@\n> +To that end, it first finds pairs of commits from both commit ranges\n> +that correspond with each other. Two commits are said to correspond when\n> +the diff between their patches (i.e. the author information, the commit\n> +message and the commit diff) is reasonably small compared to the\n> +patches' size. See ``Algorithm` below for details.\n\nUnbalanced number of backticks on \"Algorithm\".\n\n> +Finally, the list of matching commits is shown in the order of the\n> +second commit range, with unmatched commits being inserted just after\n> +all of their ancestors have been shown.\n"},{"id":"352604","messageId":"CAPig+cSyTKLOXbfGx3z_RWbHaTZU_EKWJrCqwCaw+urQph=4Tw@mail.gmail.com","threadId":"48405","inReplyTo":"4a68b95ce2a6463708c92d1bcc0208352c14f836.1530617166.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 20/20] range-diff: make --dual-color the default mode","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-16T08:06:33Z","receivedAt":"2018-07-16T08:06:46Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Jul 3, 2018 at 7:26 AM Johannes Schindelin via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> After using this command extensively for the last two months, this\n> developer came to the conclusion that even if the dual color mode still\n> leaves a lot of room for confusion what was actually changed, the\n\n\"...confusion _about_ what...\"\n\n> non-dual color mode is substantially worse in that regard.\n>\n> Therefore, we really want to make the dual color mode the default.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\n> @@ -31,11 +31,14 @@ all of their ancestors have been shown.\n> ---dual-color::\n> -       When the commit diffs differ, recreate the original diffs'\n> -       coloring, and add outer -/+ diff markers with the *background*\n> -       being red/green to make it easier to see e.g. when there was a\n> -       change in what exact lines were added.\n> +--no-dual-color::\n> +       When the commit diffs differ, `git range-diff` recreates the\n> +       original diffs' coloring, and add outer -/+ diff markers with\n\ns/add/adds/\n\n> +       the *background* being red/green to make it easier to see e.g.\n> +       when there was a change in what exact lines were added. This is\n> +       known to `range-diff` as \"dual coloring\". Use `--no-dual-color`\n> +       to revert to color all lines according to the outer diff markers\n> +       (and completely ignore the inner diff when it comes to color).\n"},{"id":"352756","messageId":"nycvar.QRO.7.76.6.1807162011210.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAPig+cQo1hM59P5eB4YTw=zvMAWh0OO8zsf0PMcwYoJtGwLKCQ@mail.gmail.com","subject":"Re: [PATCH v3 03/20] range-diff: first rudimentary implementation","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-17T09:53:29Z","receivedAt":"2018-07-17T09:53:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Mon, 16 Jul 2018, Eric Sunshine wrote:\n\n> On Tue, Jul 3, 2018 at 7:27 AM Johannes Schindelin via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> > At this stage, `git range-diff` can determine corresponding commits\n> > of two related commit ranges. This makes use of the recently introduced\n> > implementation of the Hungarian algorithm.\n> \n> Did you want s/Hungarian/Jonker-Volgenant/ here? (Not worth a re-roll.)\n\nIt is worth a new iteration, and I'd rather say \"linear assignment\" than\neither Hungarian or Jonker-Volgenant. Thanks for pointing this out.\n\n> > The core of this patch is a straight port of the ideas of tbdiff, the\n> > apparently dormant project at https://github.com/trast/tbdiff.\n> > [...]\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> > diff --git a/builtin/range-diff.c b/builtin/range-diff.c\n> > @@ -17,9 +18,49 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n> > +       int res = 0;\n> > +       struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n> >\n> > -       argc = parse_options(argc, argv, NULL, options,\n> > -                            builtin_range_diff_usage, 0);\n> > +       argc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,\n> > +                            0);\n> \n> This parse_options() change appears to be merely a re-wrapping of the\n> line between patches 2 and 3.\n\nTrue, and it is a bad change because it makes the line longer than 80\ncolumns.\n\nFixed.\n\n> > -       return 0;\n> > +       if (argc == 2) {\n> > +               if (!strstr(argv[0], \"..\"))\n> > +                       warning(_(\"no .. in range: '%s'\"), argv[0]);\n> > +               strbuf_addstr(&range1, argv[0]);\n> > +\n> > +               if (!strstr(argv[1], \"..\"))\n> > +                       warning(_(\"no .. in range: '%s'\"), argv[1]);\n> > +               strbuf_addstr(&range2, argv[1]);\n> \n> Should these die() (like the \"...\" case below) rather than warning()?\n> Warning and continuing doesn't seem like intended behavior. When I\n> test this with on git.git and omit the \"..\", git sits for a long, long\n> time consuming the CPU. I guess it's git-log'ing pretty much the\n> entire history.\n\nI had to go back to `git-tbdiff.py` to see how it handles this, and you\nare right: it should die().\n\nFixed.\n\n(Technically, it is conceivable that some user wants to compare two\nindependent commit histories, e.g. when a repository was imported from a\ndifferent SCM two times, independently. I guess when that happens, we can\nalways implement a `range-diff --root <tip1> <tip2>` or some such.)\n\n>     % GIT_TRACE=1 git range-diff v1 v2\n>     warning: no .. in range: 'v1'\n>     warning: no .. in range: 'v2'\n>     trace: git log --no-color -p --no-merges --reverse \\\n>         --date-order --decorate=no --no-abbrev-commit v1\n>     ^C\n>     %\n> \n> > +       } else if (argc == 3) {\n> > +               strbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n> > +               strbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n> > +       } else if (argc == 1) {\n> > +               const char *b = strstr(argv[0], \"...\"), *a = argv[0];\n> > +               int a_len;\n> > +\n> > +               if (!b)\n> > +                       die(_(\"single arg format requires a symmetric range\"));\n> > diff --git a/range-diff.c b/range-diff.c\n> > @@ -0,0 +1,307 @@\n> > +static int read_patches(const char *range, struct string_list *list)\n> > +{\n> > +       while (strbuf_getline(&line, in) != EOF) {\n> > +               if (skip_prefix(line.buf, \"commit \", &p)) {\n> > +                       [...]\n> > +                       in_header = 1;\n> > +                       continue;\n> > +               }\n> > +               if (starts_with(line.buf, \"diff --git\")) {\n> > +                       in_header = 0;\n> > +                       [...]\n> > +               } else if (in_header) {\n> > +                       if (starts_with(line.buf, \"Author: \")) {\n> > +                               [...]\n> > +                       } else if (starts_with(line.buf, \"    \")) {\n> > +                               [...]\n> > +                       }\n> > +                       continue;\n> > +               } else if (starts_with(line.buf, \"@@ \"))\n> > +                       strbuf_addstr(&buf, \"@@\");\n> > +               else if (line.buf[0] && !starts_with(line.buf, \"index \"))\n> > +                       /*\n> > +                        * A completely blank (not ' \\n', which is context)\n> > +                        * line is not valid in a diff.  We skip it\n> > +                        * silently, because this neatly handles the blank\n> > +                        * separator line between commits in git-log\n> > +                        * output.\n> > +                        */\n> > +                       strbuf_addbuf(&buf, &line);\n> \n> This comment had me confused for a bit since it doesn't seem to agree\n> with the 'then' part of the 'if', but rather applies more to the\n> 'else'.  Had it been split into two parts (one for 'then' and one for\n> 'else'), it might have been easier to digest. That is, something like:\n> \n>     else if (line.buf[0] && !starts_with(..., \"index \"))\n>         /* A line we wish to keep. */\n>         strbuf_addbuf(...);\n>     else\n>         /*\n>          * A completely blank line between commits or\n>          * or one in which we are otherwise not interested.\n>          */\n>         continue;\n> \n> or something. Structuring it a bit differently might have helped, as well:\n> \n>     else if (!line.buf[0])\n>         /* A completely blank line between commits. */\n>         continue;\n>     else if (starts_with(..., \"index \"))\n>         /* A line in which we are not interested. */\n>         continue;\n>     else\n>         strbuf_addbuf(&buf, &line);\n\nI like this much better, too.\n\n> Not at all worth a re-roll.\n\nI'll have to send a new iteration anyway, after digging into ws.c, I\nthink.\n\nAlso: I had this idea that dimming the \"old\" diff would make a ton of\nsense, so I want to try that.\n\n> > +               else\n> > +                       continue;\n> > +       if (util)\n> > +               string_list_append(list, buf.buf)->util = util;\n> \n> So, the parser is grabbing each commit and shoving all the\n> \"interesting\" information about the commit in a 'patch_util'. It grabs\n> the OID, author, the commit message (indented), the \"diff --git\",\n> \"+++\", \"---\" lines (but ignores \"index\" line), \"@@\" lines (but\n> ignoring the gunk after \"@@\"), and all context and patch lines.\n> \n> Looks good.\n\nCorrect.\n\n> > +       strbuf_release(&buf);\n> > +\n> > +       if (finish_command(&cp))\n> > +               return -1;\n> > +\n> > +       return 0;\n> > +}\n\nThank you for your suggestions!\nDscho\n"},{"id":"352790","messageId":"nycvar.QRO.7.76.6.1807171306380.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAPig+cTnRi=HuyZy+bMKeU9qutZb3K5C4qTb7gCQz7GyGN=FRw@mail.gmail.com","subject":"Re: [PATCH v3 09/20] range-diff: adjust the output of the commit pairs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-17T16:24:33Z","receivedAt":"2018-07-17T16:24:45Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Mon, 16 Jul 2018, Eric Sunshine wrote:\n\n> On Tue, Jul 3, 2018 at 7:26 AM Johannes Schindelin via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> > This change brings `git range-diff` yet another step closer to\n> > feature parity with tbdiff: it now shows the oneline, too, and indicates\n> > with `=` when the commits have identical diffs.\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> > diff --git a/range-diff.c b/range-diff.c\n> > @@ -251,9 +253,57 @@ static void get_correspondences(struct string_list *a, struct string_list *b,\n> > +static void output_pair_header(struct strbuf *buf,\n> > +                              struct patch_util *a_util,\n> > +                              struct patch_util *b_util)\n> >  {\n> > -       return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n> > +       static char *dashes;\n> > +       struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n> > +       struct commit *commit;\n> > +\n> > +       if (!dashes) {\n> > +               char *p;\n> > +\n> > +               dashes = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV));\n> \n> It's nice to see that the bulk of the range-diff functionality has\n> been libified in this re-roll (residing in range-diff.c rather than\n\nCan we *please* stop calling it \"re-roll\"? Thanks.\n\n(Or are you really \"never gonna give you up, never gonna let you down\"?)\n\n> builtin/range-diff.c as in earlier versions), so it's somewhat\n> surprising to see libified code holding onto the 'dashes' buffer like\n> this in a static variable. An alternative would have been for the\n> caller to pass in the same buffer to output_pair_header() for re-use,\n> and then dispose of it at the end of processing.\n\nSure, to be honest, I had completely forgotten about what I did there, and\nhad to read up on it to fix it.\n\n> \n> > +               for (p = dashes; *p; p++)\n> > +                       *p = '-';\n> > +       }\n> > +\n> > +       strbuf_reset(buf);\n> \n> ...much like 'buf' is allocated by the caller, passed in and re-used\n> for each invocation, then released by the caller at the end.\n\nYep, I now pass in another strbuf, `dashes`.\n\nThanks,\nDscho\n"},{"id":"352792","messageId":"nycvar.QRO.7.76.6.1807171827460.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAPig+cQB8p1Eo0qyfD78cfSY6N=N9i-KBw5UO2OULXfA8+A=tQ@mail.gmail.com","subject":"Re: [PATCH v3 11/20] range-diff: add tests","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-17T16:28:23Z","receivedAt":"2018-07-17T16:28:33Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Mon, 16 Jul 2018, Eric Sunshine wrote:\n\n> On Tue, Jul 3, 2018 at 7:26 AM Thomas Rast via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> > These are essentially lifted from https://github.com/trast/tbdiff, with\n> > light touch-ups to account for the command now being an option of `git\n> > branch`.\n> \n> The \"option of `git branch`\" mention is outdated. Perhaps just drop\n> everything after \"...touch-ups\" (or mention \"range-diff\" instead).\n\nAh, the line break made my `grep` fail :-(\n\n> > Apart from renaming `tbdiff` to `range-diff`, only one test case needed\n> > to be adjusted: 11 - 'changed message'.\n> > [...]\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> > diff --git a/t/.gitattributes b/t/.gitattributes\n> > @@ -18,5 +18,6 @@ t[0-9][0-9][0-9][0-9]/* -whitespace\n> >  /t7500/* eol=lf\n> > +/t7910/* eol=lf\n> \n> Does this need to be changed to t3206?\n\nAbsolutely.\n\n> \n> >  /t8005/*.txt eol=lf\n> > diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh\n> > new file mode 100755\n> > index 000000000..2237c7f4a\n> > --- /dev/null\n> > +++ b/t/t3206-range-diff.sh\n\nThanks for your thorough review,\nDscho\n"},{"id":"352793","messageId":"nycvar.QRO.7.76.6.1807171839010.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAPig+cSin5Em1m7uigSLnNg9wbz+fz8uy4sJOECq2Ke2+v+D4A@mail.gmail.com","subject":"Re: [PATCH v3 17/20] range-diff: add a man page","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-17T16:39:13Z","receivedAt":"2018-07-17T16:39:23Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Mon, 16 Jul 2018, Eric Sunshine wrote:\n\n> On Tue, Jul 3, 2018 at 7:27 AM Johannes Schindelin via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> > The bulk of this patch consists of a heavily butchered version of\n> > tbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from\n> > https://github.com/trast/tbdiff.\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> > diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\n> > @@ -0,0 +1,235 @@\n> > +To that end, it first finds pairs of commits from both commit ranges\n> > +that correspond with each other. Two commits are said to correspond when\n> > +the diff between their patches (i.e. the author information, the commit\n> > +message and the commit diff) is reasonably small compared to the\n> > +patches' size. See ``Algorithm` below for details.\n> \n> Unbalanced number of backticks on \"Algorithm\".\n\nOf course!\n\nThanks,\nDscho\n\n> \n> > +Finally, the list of matching commits is shown in the order of the\n> > +second commit range, with unmatched commits being inserted just after\n> > +all of their ancestors have been shown.\n> \n"},{"id":"352795","messageId":"nycvar.QRO.7.76.6.1807171840160.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAPig+cSyTKLOXbfGx3z_RWbHaTZU_EKWJrCqwCaw+urQph=4Tw@mail.gmail.com","subject":"Re: [PATCH v3 20/20] range-diff: make --dual-color the default mode","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-17T16:40:28Z","receivedAt":"2018-07-17T16:40:39Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Mon, 16 Jul 2018, Eric Sunshine wrote:\n\n> On Tue, Jul 3, 2018 at 7:26 AM Johannes Schindelin via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> > After using this command extensively for the last two months, this\n> > developer came to the conclusion that even if the dual color mode still\n> > leaves a lot of room for confusion what was actually changed, the\n> \n> \"...confusion _about_ what...\"\n> \n> > non-dual color mode is substantially worse in that regard.\n> >\n> > Therefore, we really want to make the dual color mode the default.\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> > diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\n> > @@ -31,11 +31,14 @@ all of their ancestors have been shown.\n> > ---dual-color::\n> > -       When the commit diffs differ, recreate the original diffs'\n> > -       coloring, and add outer -/+ diff markers with the *background*\n> > -       being red/green to make it easier to see e.g. when there was a\n> > -       change in what exact lines were added.\n> > +--no-dual-color::\n> > +       When the commit diffs differ, `git range-diff` recreates the\n> > +       original diffs' coloring, and add outer -/+ diff markers with\n> \n> s/add/adds/\n> \n> > +       the *background* being red/green to make it easier to see e.g.\n> > +       when there was a change in what exact lines were added. This is\n> > +       known to `range-diff` as \"dual coloring\". Use `--no-dual-color`\n> > +       to revert to color all lines according to the outer diff markers\n> > +       (and completely ignore the inner diff when it comes to color).\n\nYep, thank you very much!\nDscho\n"},{"id":"352807","messageId":"CAGZ79kaft-8pHGwyqAK0yNL3p5sP0VyKNn29dxoZ0wFGWGEHPA@mail.gmail.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1807171306380.71@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 09/20] range-diff: adjust the output of the commit pairs","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-17T17:47:21Z","receivedAt":"2018-07-17T17:47:34Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Hi Johannes,\n\n> > It's nice to see that the bulk of the range-diff functionality has\n> > been libified in this re-roll (residing in range-diff.c rather than\n>\n> Can we *please* stop calling it \"re-roll\"? Thanks.\n\nFun fact of the day:\n\nFirst appearance of \"reroll\" in the public archive is (09 Dec 2007)\nhttps://public-inbox.org/git/7vy7c3ogu2.fsf@gitster.siamese.dyndns.org/\nwhich is predated by \"re-roll\" (05 May 2006)\nhttps://public-inbox.org/git/7vr738w8t4.fsf@assigned-by-dhcp.cox.net/\n\nStefan\n"},{"id":"353043","messageId":"xmqqa7qngnon.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"xmqq8t6gz8xz.fsf@gitster-ct.c.googlers.com","subject":"Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-19T17:06:32Z","receivedAt":"2018-07-19T17:06:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>\n>> I would like to ask you to reinstate the post-rewrite hook, as it still\n>> improves the situation over the current one.\n>\n> Without post-rewrite I seem to be getting correct amlog entries for\n> commits created by \"git rebase\"; do our rebase--am backend still\n> trigger post-applypatch hook in its \"am\" phase to apply the patches\n> created with \"format-patch\"?\n\nThat was a wrong line of thought that led to a dead end.  format-patch\nwon't recreate Message-Id to its output from notes/amlog, so even if\nthe \"format-patch --stdout | am\" pipeline inside rebase-am triggered\nthe post-applypatch hook, it would not have a chance to carry the\nnotes forward that way.\n\nWhat was really happening was I have\n\n\t$ git config --list | grep amlog\n\tnotes.rewriteref=refs/notes/amlog\n\nand that ought to be sufficient to carry \"commit-to-original-msg-id\"\nentries across rebases.  And it seems to correctly work.  I however\nsuspect that \"cherry-pick A..B\" may lose the notes, but I haven't\nchecked.\n\n\n\n\n"},{"id":"353167","messageId":"nycvar.QRO.7.76.6.1807202049540.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"xmqqa7qngnon.fsf@gitster-ct.c.googlers.com","subject":"Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-20T18:51:54Z","receivedAt":"2018-07-20T18:52:24Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Thu, 19 Jul 2018, Junio C Hamano wrote:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> >\n> >> I would like to ask you to reinstate the post-rewrite hook, as it still\n> >> improves the situation over the current one.\n> >\n> > Without post-rewrite I seem to be getting correct amlog entries for\n> > commits created by \"git rebase\"; do our rebase--am backend still\n> > trigger post-applypatch hook in its \"am\" phase to apply the patches\n> > created with \"format-patch\"?\n> \n> That was a wrong line of thought that led to a dead end.  format-patch\n> won't recreate Message-Id to its output from notes/amlog, so even if\n> the \"format-patch --stdout | am\" pipeline inside rebase-am triggered\n> the post-applypatch hook, it would not have a chance to carry the\n> notes forward that way.\n> \n> What was really happening was I have\n> \n> \t$ git config --list | grep amlog\n> \tnotes.rewriteref=refs/notes/amlog\n> \n> and that ought to be sufficient to carry \"commit-to-original-msg-id\"\n> entries across rebases.  And it seems to correctly work.  I however\n> suspect that \"cherry-pick A..B\" may lose the notes, but I haven't\n> checked.\n\nAFAICT there is at least one scenario where you run `rebase -i`, the notes\nget updated, and of course the *reverse mapping* does *not* get updated:\nyou have a mapping both from commit to Message-Id *and crucially* from\nMessage-Id to commit. The automatic rewrite of commit notes in `rebase -i`\ntackles only the commit notes, obviously, not the reverse.\n\nHence the post-rewrite hook I think I already suggested at least once in a\nprevious reply.\n\nCiao,\nDscho\n"},{"id":"353168","messageId":"nycvar.QRO.7.76.6.1807202052350.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAGZ79kaft-8pHGwyqAK0yNL3p5sP0VyKNn29dxoZ0wFGWGEHPA@mail.gmail.com","subject":"Re: [PATCH v3 09/20] range-diff: adjust the output of the commit pairs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-20T18:57:43Z","receivedAt":"2018-07-20T18:58:09Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Tue, 17 Jul 2018, Stefan Beller wrote:\n\n> > > It's nice to see that the bulk of the range-diff functionality has\n> > > been libified in this re-roll (residing in range-diff.c rather than\n> >\n> > Can we *please* stop calling it \"re-roll\"? Thanks.\n> \n> Fun fact of the day:\n> \n> First appearance of \"reroll\" in the public archive is (09 Dec 2007)\n> https://public-inbox.org/git/7vy7c3ogu2.fsf@gitster.siamese.dyndns.org/\n> which is predated by \"re-roll\" (05 May 2006)\n> https://public-inbox.org/git/7vr738w8t4.fsf@assigned-by-dhcp.cox.net/\n\nReal fun fact of the day:\n\nhttps://en.wiktionary.org/wiki/reroll says\n\nVerb\n\nreroll (third-person singular simple present rerolls, present participle\nrerolling, simple past and past participle rerolled)\n\n    1. To roll again.\n\n        A player who rolls two sixes can reroll the dice for an additional\n\tturn.\n\n    2. (programming) To convert (an unrolled instruction sequence) back into\n       a loop. quotations ▼\n\nNoun\n\nreroll (plural rerolls)\n\n    (dice games) A situation in the rules of certain dice games where a\n    player is given the option to reroll an undesirable roll of the dice.\n\n\nYou will notice how this does not list *any* hint at referring to\nsomething that Junio calls \"reroll\".\n\nLikewise, I have to admit that Wiktionary's idea of an \"iteration\"\ndisagrees with *my* use of the term.\n\nThe correct term would be \"revision\"\n(https://en.wiktionary.org/wiki/revision). But we, the core Git\ncontributors, in our collective infinite wisdom, chose to use that term\nas yet another way to refer to a commit [*1*].\n\nSo we got it all wrong, believe it or not.\n\nCiao,\nDscho\n\nFootnote *1*: https://en.wiktionary.org/wiki/commit#Noun does not even\nbother to acknowledge our use of referring to a snapshot of a source code\nbase as a \"commit\"."},{"id":"353174","messageId":"CAGZ79kZzHN+HKYeezyeNwfe2+dTGHnOzs3okJTVrfm=AFwPbnQ@mail.gmail.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1807202052350.71@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 09/20] range-diff: adjust the output of the commit pairs","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-20T19:16:44Z","receivedAt":"2018-07-20T19:16:58Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":">     1. To roll again.\n>\n>         A player who rolls two sixes can reroll the dice for an additional\n>         turn.\n\nThis is where I had my AHA moment!\n(Consider my software development process as chaotic as a dice roll\nSo rerolling is really just rolling the dice again to \"get my patch\naccepted\" ;-)\n\n>     2. (programming) To convert (an unrolled instruction sequence) back into\n>        a loop. quotations ▼\n\nWe do not have unrolled loops?\nThis was good back in the day where the cost of each instruction weighted\nheavy on the CPU, such that the JMPs that are needed (and the loop\nvariable check that might have had a bad branch prediction) for the loop were\nslowing down the execution.\n\nNowadays (when I was studying 5 years ago) the branch prediction and individual\ninstruction execution are really good, but the bottleneck that I measured\n(when I had a lot of time at my disposal and attending a class/project on micro\narchitectures), was the CPU instruction cache size, i.e. loop unrolling made the\ncode *slower* than keeping tight loops loaded in memory.\nhttps://stackoverflow.com/questions/24196076/is-gcc-loop-unrolling-flag-really-effective\n\n> Noun\n>\n> reroll (plural rerolls)\n>\n>     (dice games) A situation in the rules of certain dice games where a\n>     player is given the option to reroll an undesirable roll of the dice.\n>\n>\n> You will notice how this does not list *any* hint at referring to\n> something that Junio calls \"reroll\".\n\nWe have undesirable patches that were 'rolled' onto the mailing list,\nso they have to be rerolled?\n\n> Footnote *1*: https://en.wiktionary.org/wiki/commit#Noun does not even\n> bother to acknowledge our use of referring to a snapshot of a source code\n> base as a \"commit\".\n\nWhen Git was a content addressable file system, a commit was precisely\n\"a database transaction, [...] making it a permanent change.\"\n\nSide note:\nI was just giving a talk to my colleagues about diff aglorithms\n(and eventually describing a bug in the histogram diff algorithm)\nand we got really riled up with \"Longest Common Subsequence\",\nas the mathematical definition is different than what the code\nor I (after studying the code) had in mind.\n\nNaming things is hard, and sometimes the collective wisdom got\nit wrong, but changing it would be very costly in the short/medium\nterm.\n\nAnother note about \"rolling things\": At $DAYJOB I review changes\nthat are committed to the another revision control system w.r.t. its\ncompliance of open source licenses (hence I am exposed to a lot\nof different projects), and some of those changes are titled\n\"Roll up to version $X\" which I found strange, but knew\nwhat was meant.\n\nStefan\n"},{"id":"353178","messageId":"xmqq1sbxbt0e.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1807202049540.71@tvgsbejvaqbjf.bet","subject":"Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-20T19:34:57Z","receivedAt":"2018-07-20T19:35:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> AFAICT there is at least one scenario where you run `rebase -i`, the notes\n> get updated, and of course the *reverse mapping* does *not* get updated:\n\nIt turns out that I never had a rewrite hook; the notes.rewriteref\nmechanism is the only thing that has been used to maintain amlog.\n\nI've stopped populating the reverse mapping, by the way.  The script\nthat I feed a message from gmane or public-inbox when I need to\nlearn the set of commits that resulted from the message instead uses\n\"git grep $message-id notes/amlog\".  And that is fast enough for my\npurpose.\n\nThere is no good reason to abuse the notes mechanism to map a random\nobject-name looking string (i.e. hash result of message id), other\nthan the ease of \"quick access\" when somebody is making a lot of\ninquiry, but that \"database\" does not have to be stored in notes.\nIt certainly does not belong to cycles worth spending by me *while*\nI work during the say with various history reshaping tools to record\nand/or update the reverse mapping and that is why my post-applypatch\nhook no longer has the \"reverse map\" hack.\n\nIt is not like anybody (including me) needs realtime up-to-date\nreverse mapping from amlog while I run my \"commit --amend\", \"rebase\n-i\", etc. and the reverse map is constructable by reversing the\nforward map, obviously, with a postprocessing.  And I think that is\na reasonably way forward if anybody wants to have a reverse mapping.\nThe postprocessing can be done either by me before pushing out the\namlog ref, or done by any consumer after fetching the amlog ref from\nme.  If I did the postprocessing and refuse to use rewrite hook you\nwouldn't even know ;-)\n\n"},{"id":"353186","messageId":"CAGZ79kaLTJDmWhS8Y8R1cQh9TRXLawdoWHEzVC6PLHBN+VQekg@mail.gmail.com","threadId":"48405","inReplyTo":"xmqq1sbxbt0e.fsf@gitster-ct.c.googlers.com","subject":"Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-20T21:20:04Z","receivedAt":"2018-07-20T21:20:18Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Fri, Jul 20, 2018 at 12:35 PM Junio C Hamano <gitster@pobox.com> wrote:\n\n> It is not like anybody (including me) needs realtime up-to-date\n\nI thought the same for a long time, but contributing to other projects\nshowed me that this is not necessarily the case. Having a real time\nupdate, even if it would be just \"your patch is labeled 'under discussion'\"\nis beneficial as I would know where it is \"in the system\".\n\nIn a way I'd compare our contribution process to having an\nincredible fine grained paper map. Most of the world moved\non to digital maps, that zoom in on-demand.\n(C.f. spelling out \"See banned.h for banned functions\" in\nDocumentation/CodingGuidelines is a fine grained detail\nthat is not relevant for *most* of the contributions, but just\nburdens the bearer of the paper map with weight; if this hint\nis given dynamically by the compiler or build system at relevant\ntimes, it is much better;\n\nRegarding the real time aspect here, it is also very good\ncomparison to maps: While I know how to read paper maps\n(or offline maps) and how to navigate my way, it sure is easier\nto just follow the online up-to-date navigation service, that\ntells me what to do. )\n"},{"id":"353187","messageId":"xmqqo9f1a9ct.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"CAGZ79kaLTJDmWhS8Y8R1cQh9TRXLawdoWHEzVC6PLHBN+VQekg@mail.gmail.com","subject":"Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-20T21:24:50Z","receivedAt":"2018-07-20T21:24:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> On Fri, Jul 20, 2018 at 12:35 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n>> It is not like anybody (including me) needs realtime up-to-date\n>\n> I thought the same for a long time, but contributing to other projects\n> showed me that this is not necessarily the case. Having a real time\n> update, even if it would be just \"your patch is labeled 'under discussion'\"\n> is beneficial as I would know where it is \"in the system\".\n\nWell, you wouldn't have an access to the up-to-date amlog maintained\nby me *UNTIL* I push it out at the end of the day.  So by\ndefinition, you do not have real-time access to the up-to-date\nstate.\n\nAnd also by definition, you do not *NEED* such an access, because\nyou won't see newly created or rewritten commits, whose originating\nMessage-Id is not in the copy of amlog you have (yet), until I push\nthe day's integration result out *AND* you fetch what I pushed out.\n\n"},{"id":"353190","messageId":"CAGZ79kYBRj-YLM-eCkHF1oMmBfTAeOQk6hTAD47HuoLa4QZjvA@mail.gmail.com","threadId":"48405","inReplyTo":"CAPc5daW-KoyUX3i7M5YbdQC2mFKAmVBS42-XT84hpm30VFcZ1g@mail.gmail.com","subject":"Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-20T21:30:50Z","receivedAt":"2018-07-20T21:31:03Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"+cc list\nOn Fri, Jul 20, 2018 at 2:29 PM Junio C Hamano <gitster@pobox.com> wrote:\n> ... which means that it does not matter if I have an elaborate rewrite hook\n> that constantly updates the reverse mapping or if the reverse mapping is\n> made immediately before I push out. You wouldn't even be able to tell any\n> difference.\n>\n> And for that matter, it could even be made on the receiving end by you\n> after you fetch from me before you need the reverse map information.\n"},{"id":"353256","messageId":"nycvar.QRO.7.76.6.1807212309340.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180710174552.30123-1-sbeller@google.com","subject":"Re: [PATCH 0/2] Re: [PATCH v3 14/20] diff: add an internal option to dual-color diffs of diffs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-21T21:13:12Z","receivedAt":"2018-07-21T21:13:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Tue, 10 Jul 2018, Stefan Beller wrote:\n\n> This is developed on top of 4a68b95ce2a6 (your series here)\n> \n> This is an attempt to explain the previous email better,\n> specially the second (yet unfinished) patch, but the resulting\n> emit_line_0 is way clearer in my mind, dropping the 'first' character\n> and instead having a 'char *sign' that (a) we can color differently for\n> dual color and (b) can have multiple chars, the refactoring for the multiple\n> chars would need to happen at a slightly higher level.\n> \n> Feel free to draw inspiration from here, but if not that is fine, too\n> (as I did not fully understand the word diffing problem yet, this may add a\n> burden instead of just taking these patches).\n> I can send up a cleanup series after yours lands, as well.\n\nWe discussed this on IRC a couple of days ago, and I think that this patch\npair was designed under the assumption that the dual color mode would use\ndiff markers of the same color, always. However, the dual color mode is\nabout *inverting* the color of the first marker (and using the marker\nitself to determine the color) and then using a potentially *different*\ncolor on the second marker (using *that* marker to determine the color).\nExample:\n\n\t<background red>-</background><foreground green>+ Hello</foreground>\n\nSo I allowed myself to focus on trying to wrap my head around the way the\nwhitespace flags work, and how to adjust the code in cache.h/diff.c/diff.h\nto replace the relatively simple workaround by a full blown correct patch\n(which is sadly a *lot* larger).\n\nCiao,\nDscho\n"},{"id":"353257","messageId":"nycvar.QRO.7.76.6.1807212314390.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAGZ79kbG0QZZSstC85pqSPS1awXq44vsBSvn_gfgP=22fdpzcA@mail.gmail.com","subject":"Re: [PATCH v3 16/20] range-diff --dual-color: work around bogus white-space warning","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-21T21:44:59Z","receivedAt":"2018-07-21T21:45:56Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Tue, 10 Jul 2018, Stefan Beller wrote:\n\n> On Tue, Jul 10, 2018 at 3:08 AM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> >\n> > On Mon, 9 Jul 2018, Junio C Hamano wrote:\n> >\n> > > I also wonder if we should be feeding the context lines to ws.c\n> > > machinery in the first place though.\n> >\n> > It *is* confusing, I know. The entire \"diff of diffs\" concept *is*\n> > confusing. I just don't know about a better alternative.\n> \n> I agree, but I am sure we'll get used to it quickly.\n\nMaybe you. Not me, though, I use range-diff extensively, and I still got\nconfused quite a bit.\n\nUntil, that is, I implemented the change where the \"old-only\" changes are\ndimmed and the \"new-only\" changes are displayed in bold.\n\n(The colors stay the same, it's just that the brightness indicates whether\nthis is a change that was made obsolete, a change that stayed the same, or\na change that was introduced in the latest iteration.)\n\nWith this change, I am quite confident that I read the range-diffs\ncorrectly all the time.\n\n> > So hear me out, because there is a big misconception here: there are\n> > *two* levels of diffs. The outer one and the inner one.\n> \n> Yes, the inner diff is just input that was generated before because it\n> is so convenient to generate. Recently when using this too (back then\n> when it was called branch-diff), I came across the following:\n> \n> Patch 1 looked like:\n> \n>     line 1\n> +    new line\n>     line 2\n>     line 3\n> \n> and in the next iteration it looked like:\n>     line 1\n>     line 2\n> +    new line\n>     line 3\n> \n> such that the diff of diffs showed the move correctly, but as the inner diffs\n> had different context ranges, other lines looked like added/removed\n> in the outer diff, though it was both context.\n> So I wonder if eventually (not in this series) we want to tweak the context\n> lines, generate more than needed in the inner diffs and cut them off in\n> the outer diff \"at the same line\".\n\nThat would be a welcome improvement, although I fear that it will be\nrelatively intrusive. For one, you can forget about using different\ndiff consumers (such as word-diff) if you hack up the diff of diffs\ngeneration at *such* a low level.\n\n> I digress again w.r.t. white space.\n> \n> > Context lines of the outer diffs have no problem [*1*].\n> >\n> > The problem arises when the outer diff shows a - or + line (i.e. the line\n> > is present *either* in the old patch set or in the new patch set, but not\n> > both), *and* that line is *not* a context line of the inner diff.\n> \n> So an actual change in the patches; an incremental reviewer would want\n> to spend most care on these.\n\nPrecisely.\n\nWith above-mentioned dimming/brightening, there is a strong visual cue to\nfocus on those parts.\n\n> > Let's illustrate this via an example. Let's assume that both the old patch\n> > set and the new patch set add a comment to a statement, and that the\n> > context of that statement changed between old and new patch set. Something\n> > like this would be in the old patch set:\n> >\n> > ```diff\n> >         int quiet = 0;\n> > +       /* This is only needed for the reflog message */\n> >         const char *branch = \"HEAD\";\n> > ```\n> >\n> > And this would be in the new patch set:\n> >\n> > ```diff\n> >         int quiet = 0, try_harder = 0;\n> > +       /* This is only needed for the reflog message */\n> >         const char *branch = \"HEAD\";\n> > ```\n> >\n> > So as you see, both old and new revision of the same patch add that\n> > comment, and it is just a context line that changed, which a regular\n> > reviewer would want to *not* consider a \"real\" change between the patch\n> > set iterations.\n> >\n> > Now, let's look at the \"diff of diffs\":\n> >\n> > ```diff\n> > -       int quiet = 0;\n> > +       int quiet = 0, try_harder = 0;\n> >  +      /* This is only needed for the reflog message */\n> >         const char *branch = \"HEAD\";\n> > ```\n> >\n> > Please understand that in the dual color mode:\n> >\n> > - The first line's `-` would have a red background color, the rest of that\n> >   line would be uncolored (because it is a context line of the inner\n> >   diff),\n> >\n> > - the second line's `+` would have a green background color, the rest\n> >   would be just as uncolored as the rest of the first line,\n> >\n> > - the third line would be a context line of the outer diff, but a `+` line\n> >   of the inner diff, therefore that rest of the line would be green, and\n> >\n> > - the fourth line is completely uncolored; It is a context line both of\n> >   the inner and the outer diff.\n> >\n> > That's it for the diff colors. Now for the white space: The first two\n> > lines start with a `-` and a `+` respectively (outer diff marker), and\n> > then most crucially continue with a space to indicate the inner diff's\n> > context line, *and then continue with a horizontal tab*.\n> >\n> > As far as the inner diff is concerned, this *is* a context line.\n> >\n> > As far as the outer diff is concerned, this is *not* a context line.\n> >\n> > And that is the conundrum: the whitespace checker is called because the\n> > outer diff claims that the second line is a `+` line and the whitespace\n> > checker has no idea that it should treat it as a context line instead.\n> \n> Spelled out this way, we might want to add more symbols to\n> enum diff_symbol, such as\n>     DIFF_SYMBOL_DUAL_DIFF_PLUS_PLUS\n>     DIFF_SYMBOL_DUAL_DIFF_PLUS_MINUS\n>     DIFF_SYMBOL_PLUS_MINUS\n> or so.\n> \n> These would need to get generated when we create the diff of diffs\n> in emit_{del,add,context}_line or even fn_out_consume; and then have\n> their own treatment regarding white spaces in emit_diff_symbol_from_struct.\n> \n> I am not sure if that would help for the series as-is, as I am thinking\n> already how to move these diff-diffs in-core (as that would help a lot\n> with the context line cutting mentioned above).\n\nI settled on _DIM and _BOLD versions for CONTEXT, FILE_OLD and FILE_NEW.\n\n> > I'll try to find some time this afternoon to study Stefan's reply, as I\n> > have a hunch that there is a deep insight hidden that helps me to figure\n> > out the proper path ahead (because I do not want to uglify the `diff.c`\n> > code the way my current iteration does, and I'd rather have a way to color\n> > the diff more intelligently myself, in a function in `range-diff.c`).\n> \n> I considered trying a cleanup on top of your series as I had the impression\n> the move detection added some ugliness as well.\n\nI will be glad to review the patches after this coming week. Should I\nforget, please remind me.\n\nThanks,\nDscho\n"},{"id":"353258","messageId":"nycvar.QRO.7.76.6.1807212347330.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"xmqq1sbxbt0e.fsf@gitster-ct.c.googlers.com","subject":"Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-21T21:56:06Z","receivedAt":"2018-07-21T21:56:28Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Fri, 20 Jul 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > AFAICT there is at least one scenario where you run `rebase -i`, the notes\n> > get updated, and of course the *reverse mapping* does *not* get updated:\n> \n> It turns out that I never had a rewrite hook; the notes.rewriteref\n> mechanism is the only thing that has been used to maintain amlog.\n> \n> I've stopped populating the reverse mapping, by the way.\n\nThat's just great. I ask you to make my life easier by keeping the\ninformation correct, and now you just drop it altogether? Just great.\n\nSeriously, I am trying to *improve* something here, because I really do\ncare about contributors, and how hard we make it on them. I would not have\nexpected such a backlash against that.\n\n> The script that I feed a message from gmane or public-inbox when I need\n> to learn the set of commits that resulted from the message instead uses\n> \"git grep $message-id notes/amlog\".  And that is fast enough for my\n> purpose.\n\nAwesome. You might want to make sure that Peff stops advertising the amlog\nnotes, then, though.\n\n> There is no good reason to abuse the notes mechanism to map a random\n> object-name looking string (i.e. hash result of message id), other\n> than the ease of \"quick access\" when somebody is making a lot of\n> inquiry, but that \"database\" does not have to be stored in notes.\n\nRight. And it does not have to be stored anywhere, because nobody used it\nanyway, right?\n\nWell, I hate to break it to you: I just found a really excellent use case,\nand you are making it very, very hard for me. Deliberately so. I don't\nknow how I deserve that.\n\n> It certainly does not belong to cycles worth spending by me *while*\n> I work during the say with various history reshaping tools to record\n> and/or update the reverse mapping and that is why my post-applypatch\n> hook no longer has the \"reverse map\" hack.\n> \n> It is not like anybody (including me) needs realtime up-to-date\n> reverse mapping from amlog while I run my \"commit --amend\", \"rebase\n> -i\", etc. and the reverse map is constructable by reversing the\n> forward map, obviously, with a postprocessing.  And I think that is\n> a reasonably way forward if anybody wants to have a reverse mapping.\n> The postprocessing can be done either by me before pushing out the\n> amlog ref, or done by any consumer after fetching the amlog ref from\n> me.  If I did the postprocessing and refuse to use rewrite hook you\n> wouldn't even know ;-)\n\nThe idea that you publish the amlog notes just for your own use cases,\nsounds a bit strange to me.\n\nSo to reiterate: the information you have in amlog is useful, if faulty.\nRather than \"fixing\" it by stopping the useful reverse-mapping, it would\nmake a ton more sense to instate that post-rewrite hook I already drafted\nfor you.\n\nBesides, while you spent all of that time to make things harder for me,\nyou still did not look into the most worrisome of my findings: there are\napparently Message-Id mappings where *none* of the commits returned by\nsaid `git grep` you mentioned above are valid. Not a single one. I will\ndig out the mail for you on Monday, because I care that much, where I\nprovided one example of a Message-Id with two commits that match in amlog,\nnone of which is actually reachable from any of your public branches, and\nI also provided the commit that *actually* corresponds to that Message-Id,\nand it is not annotated.\n\nSo at least in this case *even you* should have a vested interest in\nfiguring out what goes wrong because even your own use case is affected by\nit.\n\nCiao,\nDscho\n"},{"id":"353259","messageId":"nycvar.QRO.7.76.6.1807212358090.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAGZ79kYBRj-YLM-eCkHF1oMmBfTAeOQk6hTAD47HuoLa4QZjvA@mail.gmail.com","subject":"Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-21T22:02:15Z","receivedAt":"2018-07-21T22:02:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Fri, 20 Jul 2018, Stefan Beller wrote:\n\n> +cc list\n> On Fri, Jul 20, 2018 at 2:29 PM Junio C Hamano <gitster@pobox.com> wrote:\n> > ... which means that it does not matter if I have an elaborate rewrite hook\n> > that constantly updates the reverse mapping or if the reverse mapping is\n> > made immediately before I push out. You wouldn't even be able to tell any\n> > difference.\n> >\n> > And for that matter, it could even be made on the receiving end by you\n> > after you fetch from me before you need the reverse map information.\n\nI refuse to believe that the suggestion to go back to the equivalent of\npencil and paper is sincere. We are developing a state of the art source\ncode management tool here, not some hodge podge project of somebody who is\ntrying to teach themselves C.\n\nThe current state is that there is no reliable \"paper trail\" of code\ncontributions. The solution to that is absolutely not to abolish what\nlittle of a paper trail we *do* have. The solution is to step up the game\nand correct that automated record.\n\nAnd I would have expected a lot better from the inventor of the pickaxe\noptions: why care so much about source code \"archeology\" on the one hand,\nand then burning the library on the other hand? It just does not make\nsense.\n\nCiao,\nDscho\n"},{"id":"353260","messageId":"pull.1.v4.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:04:44Z","receivedAt":"2018-07-21T22:04:50Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The incredibly useful [`git-tbdiff`](https://github.com/trast/tbdiff) tool to compare patch series (say, to see what changed between two iterations sent to the Git mailing list) is slightly less useful for this developer due to the fact that it requires the `hungarian` and `numpy` Python packages which are for some reason really hard to build in MSYS2. So hard that I even had to give up, because it was simply easier to re-implement the whole shebang as a builtin command.\n\nThe project at https://github.com/trast/tbdiff seems to be dormant, anyway. Funny (and true) story: I looked at the open Pull Requests to see how active that project is, only to find to my surprise that I had submitted one in August 2015, and that it was still unanswered let alone merged.\n\nWhile at it, I forward-ported AEvar's patch to force `--decorate=no` because `git -p tbdiff` would fail otherwise.\n\nSide note: I work on implementing range-diff not only to make life easier for reviewers who have to suffer through v2, v3, ... of my patch series, but also to verify my changes before submitting a new iteration. And also, maybe even more importantly, I plan to use it to verify my merging-rebases of Git for\nWindows (for which I previously used to redirect the pre-rebase/post-rebase diffs vs upstream and then compare them using `git diff --no-index`). And of course any interested person can see what changes were necessary e.g. in the merging-rebase of Git for Windows onto v2.17.0 by running a command like:\n\n```sh\n        base=^{/Start.the.merging-rebase}\n        tag=v2.17.0.windows.1\n        pre=$tag$base^2\n        git range-diff $pre$base..$pre $tag$base..$tag\n```\n\nThe command uses what it calls the \"dual color mode\" (can be disabled via `--no-dual-color`) which helps identifying what *actually* changed: it prefixes lines with a `-` (and red background) that correspond to the first commit range, and with a `+` (and green background) that correspond to the second range. The rest of the lines will be colored according to the original diffs.\n\nChanges since v3:\n\n- The cover letter was adjusted to reflect the new reality (the command is called `range-diff` now, not `branch-diff`, and `--dual-color` is the default).\n- The documentation was adjusted a bit more in the patch that makes `--dual-color` the default.\n- Clarified the calculation of the cost matrix, as per Stefan Beller's request.\n- The man page now spells out that merge *commits* are ignored in the commit ranges (not merges per se).\n- The code in `linear-assignment.c` was adjusted to use the `SWAP()` macro.\n- The commit message of the patch introducing the first rudimentary implementation no longer talks about the \"Hungarian\" algorithm, but about the \"linear assignment algorithm\" instead.\n- A bogus indentation change was backed out from the patch introducing the first rudimentary implementation.\n- Instead of merely warning about missing `..` in the 2-parameter invocation, we now exit with the error message.\n- The `diff_opt_parse()` function is allowed to return a value larger than 1, indicating that more than just one command-line parameter was parsed. We now advance by the indicated value instead of always advancing exactly 1 (which is still correct much of the time).\n- A lengthy `if...else if...else if...else` was simplified (from a logical point of view) by reordering it.\n- The unnecessarily `static` variable `dashes` was turned into a local variable of the caller.\n- The commit message talking about the new man page still referred to `git branch --diff`, which has been fixed.\n- A forgotten t7910 reference was changed to t3206.\n- An unbalanced double-tick was fixed in the man page.\n- Fixed grammar both of the commit message and the description of the `--no-dual-color` option.\n- To fix the build, a blank man page is now introduced together with the new `range-diff` command, even if it is populated for real only at a later patch (i.e. at the same time as before).\n- The headaches Junio fears would be incurred by that simple workaround to avoid bogus white-space error reporting are fended off: a more complex patch is now in place that adds (and uses) a new white-space flag. Sadly, as is all too common when Junio \"encourages\" me to replace a simple workaround by something \"proper\", it caused all kinds of headaches to get this right, so I am rather less certain that the \"proper\" fix will cause us less headaches than the simple workaround would have done. But whatever.\n- The dual color mode now also dims the changes that are exclusively in the first specified commit range, and uses bold face on the changes exclusively in the second one. This matches the intuition when using `range-diff` to compare an older iteration of a patch series to a newer one: the changes from the previous iteration that were replaced by new ones \"fade\", while the changes that replace them are \"shiny new\".\n\nChanges since v2:\n\n- Right-aligned the patch numbers in the commit pairs.\n- Used ALLOC_ARRAY() in hungarian.c instead of xmalloc(sizeof()*size).\n- Changed compute_assignment()s return type from int to void, as it always succeeds.\n- Changed the Hungarian Algorithm to use an integer cost matrix.\n- Changed the --creation-weight <double> option to --creation-factor <percent> where <percent> is an integer.\n- Retitled 1/19 and 2/19 to better conform with the current conventions, as pointed out (and suggested) by Junio.\n- Shut up Coverity, and at the same time avoided passing the unnecessary `i` and `j` parameters to output_pair_header().\n- Removed support for the `--no-patches` option: we inherit diff_options' support for `-s` already (and much more).\n- Removed the ugly `_INV` enum values, and introduced a beautiful GIT_COLOR_REVERSE instead. This way, whatever the user configured as color.diff.new (or .old) will be used in reverse in the dual color mode.\n- Instead of overriding the fragment header color, the dual color mode will now reverse the \"outer\" fragment headers, too.\n- Turned the stand-alone branch-diff command into the `--diff` option of `git branch`. Adjusted pretty much *all* commit messages to account for this. This change should no longer be visible: see below.\n- Pretty much re-wrote the completion, to support the new --diff mode of git-branch. See below: it was reverted for range-diff.\n- Renamed t7910 to t3206, to be closer to the git-branch tests.\n- Ensured that git_diff_ui_config() gets called, and therefore color.diff.* respected.\n- Avoided leaking `four_spaces`.\n- Fixed a declaration in a for (;;) statement (which Junio had as a fixup! that I almost missed).\n- Renamed `branch --diff`, which had been renamed from `branch-diff` (which was picked to avoid re-using `tbdiff`) to `range-diff`.\n- Renamed `hungarian.c` and its header to `linear-assignment.c`\n- Made `--dual-color` the default, and changed it to still auto-detect whether color should be used rather than forcing it\n\nJohannes Schindelin (20):\n  linear-assignment: a function to solve least-cost assignment problems\n  Introduce `range-diff` to compare iterations of a topic branch\n  range-diff: first rudimentary implementation\n  range-diff: improve the order of the shown commits\n  range-diff: also show the diff between patches\n  range-diff: right-trim commit messages\n  range-diff: indent the diffs just like tbdiff\n  range-diff: suppress the diff headers\n  range-diff: adjust the output of the commit pairs\n  range-diff: do not show \"function names\" in hunk headers\n  range-diff: use color for the commit pairs\n  color: add the meta color GIT_COLOR_REVERSE\n  diff: add an internal option to dual-color diffs of diffs\n  range-diff: offer to dual-color the diffs\n  range-diff --dual-color: fix bogus white-space warning\n  range-diff: populate the man page\n  completion: support `git range-diff`\n  range-diff: left-pad patch numbers\n  range-diff: make --dual-color the default mode\n  range-diff: use dim/bold cues to improve dual color mode\n\nThomas Rast (1):\n  range-diff: add tests\n\n .gitignore                             |   1 +\n Documentation/config.txt               |   6 +-\n Documentation/git-range-diff.txt       | 252 +++++++++++\n Makefile                               |   3 +\n builtin.h                              |   1 +\n builtin/range-diff.c                   | 106 +++++\n cache.h                                |   3 +-\n color.h                                |   7 +\n command-list.txt                       |   1 +\n contrib/completion/git-completion.bash |  14 +\n diff.c                                 | 119 ++++-\n diff.h                                 |  16 +-\n git.c                                  |   1 +\n linear-assignment.c                    | 201 ++++++++\n linear-assignment.h                    |  22 +\n range-diff.c                           | 440 ++++++++++++++++++\n range-diff.h                           |   9 +\n t/.gitattributes                       |   1 +\n t/t3206-range-diff.sh                  | 145 ++++++\n t/t3206/history.export                 | 604 +++++++++++++++++++++++++\n ws.c                                   |  11 +-\n 21 files changed, 1932 insertions(+), 31 deletions(-)\n create mode 100644 Documentation/git-range-diff.txt\n create mode 100644 builtin/range-diff.c\n create mode 100644 linear-assignment.c\n create mode 100644 linear-assignment.h\n create mode 100644 range-diff.c\n create mode 100644 range-diff.h\n create mode 100755 t/t3206-range-diff.sh\n create mode 100644 t/t3206/history.export\n\n\nbase-commit: b7bd9486b055c3f967a870311e704e3bb0654e4f\nPublished-As: https://github.com/gitgitgadget/git/releases/tags/pr-1%2Fdscho%2Fbranch-diff-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1/dscho/branch-diff-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/1\n\nRange-diff vs v3:\n\n  1:  39272eefc !  1:  f7e70689e linear-assignment: a function to solve least-cost assignment problems\n     @@ -223,9 +223,7 @@\n      +\t\t\t\tBUG(\"negative j: %d\", j);\n      +\t\t\ti = pred[j];\n      +\t\t\tcolumn2row[j] = i;\n     -+\t\t\tk = j;\n     -+\t\t\tj = row2column[i];\n     -+\t\t\trow2column[i] = k;\n     ++\t\t\tSWAP(j, row2column[i]);\n      +\t\t} while (i1 != i);\n      +\t}\n      +\n  2:  7f15b26d4 !  2:  88134121d Introduce `range-diff` to compare iterations of a topic branch\n     @@ -10,6 +10,10 @@\n          At this point, we ignore tbdiff's color options, as they will all be\n          implemented later using diff_options.\n      \n     +    Since f318d739159 (generate-cmds.sh: export all commands to\n     +    command-list.h, 2018-05-10), every new command *requires* a man page to\n     +    build right away, so let's also add a blank man page, too.\n     +\n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n      diff --git a/.gitignore b/.gitignore\n     @@ -24,6 +28,22 @@\n       /git-rebase\n       /git-rebase--am\n      \n     +diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\n     +new file mode 100644\n     +--- /dev/null\n     ++++ b/Documentation/git-range-diff.txt\n     +@@\n     ++git-range-diff(1)\n     ++==================\n     ++\n     ++NAME\n     ++----\n     ++git-range-diff - Compare two commit ranges (e.g. two versions of a branch)\n     ++\n     ++GIT\n     ++---\n     ++Part of the linkgit:git[1] suite\n     +\n      diff --git a/Makefile b/Makefile\n      --- a/Makefile\n      +++ b/Makefile\n  3:  076e1192d !  3:  4e3fb47a1 range-diff: first rudimentary implementation\n     @@ -4,7 +4,7 @@\n      \n          At this stage, `git range-diff` can determine corresponding commits\n          of two related commit ranges. This makes use of the recently introduced\n     -    implementation of the Hungarian algorithm.\n     +    implementation of the linear assignment algorithm.\n      \n          The core of this patch is a straight port of the ideas of tbdiff, the\n          apparently dormant project at https://github.com/trast/tbdiff.\n     @@ -51,19 +51,17 @@\n      +\tint res = 0;\n      +\tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n       \n     --\targc = parse_options(argc, argv, NULL, options,\n     --\t\t\t     builtin_range_diff_usage, 0);\n     -+\targc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,\n     -+\t\t\t     0);\n     + \targc = parse_options(argc, argv, NULL, options,\n     + \t\t\t     builtin_range_diff_usage, 0);\n       \n      -\treturn 0;\n      +\tif (argc == 2) {\n      +\t\tif (!strstr(argv[0], \"..\"))\n     -+\t\t\twarning(_(\"no .. in range: '%s'\"), argv[0]);\n     ++\t\t\tdie(_(\"no .. in range: '%s'\"), argv[0]);\n      +\t\tstrbuf_addstr(&range1, argv[0]);\n      +\n      +\t\tif (!strstr(argv[1], \"..\"))\n     -+\t\t\twarning(_(\"no .. in range: '%s'\"), argv[1]);\n     ++\t\t\tdie(_(\"no .. in range: '%s'\"), argv[1]);\n      +\t\tstrbuf_addstr(&range2, argv[1]);\n      +\t} else if (argc == 3) {\n      +\t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n     @@ -195,17 +193,21 @@\n      +\t\t\tcontinue;\n      +\t\t} else if (starts_with(line.buf, \"@@ \"))\n      +\t\t\tstrbuf_addstr(&buf, \"@@\");\n     -+\t\telse if (line.buf[0] && !starts_with(line.buf, \"index \"))\n     ++\t\telse if (!line.buf[0] || starts_with(line.buf, \"index \"))\n      +\t\t\t/*\n      +\t\t\t * A completely blank (not ' \\n', which is context)\n      +\t\t\t * line is not valid in a diff.  We skip it\n      +\t\t\t * silently, because this neatly handles the blank\n      +\t\t\t * separator line between commits in git-log\n      +\t\t\t * output.\n     ++\t\t\t *\n     ++\t\t\t * We also want to ignore the diff's `index` lines\n     ++\t\t\t * because they contain exact blob hashes in which\n     ++\t\t\t * we are not interested.\n      +\t\t\t */\n     -+\t\t\tstrbuf_addbuf(&buf, &line);\n     -+\t\telse\n      +\t\t\tcontinue;\n     ++\t\telse\n     ++\t\t\tstrbuf_addbuf(&buf, &line);\n      +\n      +\t\tstrbuf_addch(&buf, '\\n');\n      +\t\tutil->diffsize++;\n  4:  e98489c8c =  4:  47bee09b0 range-diff: improve the order of the shown commits\n  5:  935cad180 !  5:  94afaeaf2 range-diff: also show the diff between patches\n     @@ -55,21 +55,22 @@\n      +\tint i, j, res = 0;\n       \tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n       \n     --\targc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,\n     --\t\t\t     0);\n      +\tgit_config(git_diff_ui_config, NULL);\n      +\n      +\tdiff_setup(&diffopt);\n      +\tdiffopt.output_format = DIFF_FORMAT_PATCH;\n      +\n     -+\targc = parse_options(argc, argv, NULL, options,\n     -+\t\t\tbuiltin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n     + \targc = parse_options(argc, argv, NULL, options,\n     +-\t\t\t     builtin_range_diff_usage, 0);\n     ++\t\t\t     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n      +\n     -+\tfor (i = j = 0; i < argc; i++) {\n     ++\tfor (i = j = 0; i < argc; ) {\n      +\t\tint c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);\n      +\n      +\t\tif (!c)\n     -+\t\t\targv[j++] = argv[i];\n     ++\t\t\targv[j++] = argv[i++];\n     ++\t\telse\n     ++\t\t\ti += c;\n      +\t}\n      +\targc = j;\n      +\tdiff_setup_done(&diffopt);\n  6:  93ac1931d =  6:  41ab875a3 range-diff: right-trim commit messages\n  7:  ca5282815 !  7:  a3dd99509 range-diff: indent the diffs just like tbdiff\n     @@ -39,7 +39,7 @@\n      +\tdiffopt.output_prefix_data = &four_spaces;\n       \n       \targc = parse_options(argc, argv, NULL, options,\n     - \t\t\tbuiltin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n     + \t\t\t     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n      @@\n       \n       \tstrbuf_release(&range1);\n  8:  80622685f =  8:  61b2ff2f7 range-diff: suppress the diff headers\n  9:  6b31cbf72 !  9:  9641ab5c0 range-diff: adjust the output of the commit pairs\n     @@ -26,25 +26,22 @@\n       \n      -static const char *short_oid(struct patch_util *util)\n      +static void output_pair_header(struct strbuf *buf,\n     ++\t\t\t       struct strbuf *dashes,\n      +\t\t\t       struct patch_util *a_util,\n      +\t\t\t       struct patch_util *b_util)\n       {\n      -\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n     -+\tstatic char *dashes;\n      +\tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n      +\tstruct commit *commit;\n      +\n     -+\tif (!dashes) {\n     -+\t\tchar *p;\n     -+\n     -+\t\tdashes = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV));\n     -+\t\tfor (p = dashes; *p; p++)\n     -+\t\t\t*p = '-';\n     -+\t}\n     ++\tif (!dashes->len)\n     ++\t\tstrbuf_addchars(dashes, '-',\n     ++\t\t\t\tstrlen(find_unique_abbrev(oid,\n     ++\t\t\t\t\t\t\t  DEFAULT_ABBREV)));\n      +\n      +\tstrbuf_reset(buf);\n      +\tif (!a_util)\n     -+\t\tstrbuf_addf(buf, \"-:  %s \", dashes);\n     ++\t\tstrbuf_addf(buf, \"-:  %s \", dashes->buf);\n      +\telse\n      +\t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n      +\t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n     @@ -59,7 +56,7 @@\n      +\t\tstrbuf_addch(buf, '=');\n      +\n      +\tif (!b_util)\n     -+\t\tstrbuf_addf(buf, \" -:  %s\", dashes);\n     ++\t\tstrbuf_addf(buf, \" -:  %s\", dashes->buf);\n      +\telse\n      +\t\tstrbuf_addf(buf, \" %d:  %s\", b_util->i + 1,\n      +\t\t\t    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));\n     @@ -84,7 +81,7 @@\n       static void output(struct string_list *a, struct string_list *b,\n       \t\t   struct diff_options *diffopt)\n       {\n     -+\tstruct strbuf buf = STRBUF_INIT;\n     ++\tstruct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;\n       \tint i = 0, j = 0;\n       \n       \t/*\n     @@ -94,7 +91,7 @@\n       \t\tif (i < a->nr && a_util->matching < 0) {\n      -\t\t\tprintf(\"%d: %s < -: --------\\n\",\n      -\t\t\t       i + 1, short_oid(a_util));\n     -+\t\t\toutput_pair_header(&buf, a_util, NULL);\n     ++\t\t\toutput_pair_header(&buf, &dashes, a_util, NULL);\n       \t\t\ti++;\n       \t\t\tcontinue;\n       \t\t}\n     @@ -103,7 +100,7 @@\n       \t\twhile (j < b->nr && b_util->matching < 0) {\n      -\t\t\tprintf(\"-: -------- > %d: %s\\n\",\n      -\t\t\t       j + 1, short_oid(b_util));\n     -+\t\t\toutput_pair_header(&buf, NULL, b_util);\n     ++\t\t\toutput_pair_header(&buf, &dashes, NULL, b_util);\n       \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n       \t\t}\n       \n     @@ -113,7 +110,7 @@\n      -\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n      -\t\t\t       b_util->matching + 1, short_oid(a_util),\n      -\t\t\t       j + 1, short_oid(b_util));\n     -+\t\t\toutput_pair_header(&buf, a_util, b_util);\n     ++\t\t\toutput_pair_header(&buf, &dashes, a_util, b_util);\n       \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n       \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n       \t\t\t\t\t   b->items[j].string, diffopt);\n     @@ -122,6 +119,7 @@\n       \t\t}\n       \t}\n      +\tstrbuf_release(&buf);\n     ++\tstrbuf_release(&dashes);\n       }\n       \n       int show_range_diff(const char *range1, const char *range2,\n 10:  ef997bb8b = 10:  0a52f8878 range-diff: do not show \"function names\" in hunk headers\n 11:  3d9e5b0ba ! 11:  2b8d09020 range-diff: add tests\n     @@ -3,8 +3,8 @@\n          range-diff: add tests\n      \n          These are essentially lifted from https://github.com/trast/tbdiff, with\n     -    light touch-ups to account for the command now being an option of `git\n     -    branch`.\n     +    light touch-ups to account for the command now being names `git\n     +    range-diff`.\n      \n          Apart from renaming `tbdiff` to `range-diff`, only one test case needed\n          to be adjusted: 11 - 'changed message'.\n     @@ -22,12 +22,13 @@\n      --- a/t/.gitattributes\n      +++ b/t/.gitattributes\n      @@\n     - /t5515/* eol=lf\n     - /t556x_common eol=lf\n     - /t7500/* eol=lf\n     -+/t7910/* eol=lf\n     - /t8005/*.txt eol=lf\n     - /t9*/*.dump eol=lf\n     + t[0-9][0-9][0-9][0-9]/* -whitespace\n     + /diff-lib/* eol=lf\n     + /t0110/url-* binary\n     ++/t3206/* eol=lf\n     + /t3900/*.txt eol=lf\n     + /t3901/*.txt eol=lf\n     + /t4034/*/* eol=lf\n      \n      diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh\n      new file mode 100755\n 12:  7273cc647 ! 12:  fb83ce71a range-diff: use color for the commit pairs\n     @@ -20,11 +20,12 @@\n       }\n       \n      -static void output_pair_header(struct strbuf *buf,\n     -+static void output_pair_header(struct diff_options *diffopt, struct strbuf *buf,\n     ++static void output_pair_header(struct diff_options *diffopt,\n     ++\t\t\t       struct strbuf *buf,\n     + \t\t\t       struct strbuf *dashes,\n       \t\t\t       struct patch_util *a_util,\n       \t\t\t       struct patch_util *b_util)\n       {\n     - \tstatic char *dashes;\n       \tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n       \tstruct commit *commit;\n      +\tchar status;\n     @@ -34,11 +35,10 @@\n      +\tconst char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT);\n      +\tconst char *color;\n       \n     - \tif (!dashes) {\n     - \t\tchar *p;\n     -@@\n     - \t\t\t*p = '-';\n     - \t}\n     + \tif (!dashes->len)\n     + \t\tstrbuf_addchars(dashes, '-',\n     + \t\t\t\tstrlen(find_unique_abbrev(oid,\n     + \t\t\t\t\t\t\t  DEFAULT_ABBREV)));\n       \n      +\tif (!b_util) {\n      +\t\tcolor = color_old;\n     @@ -57,7 +57,7 @@\n       \tstrbuf_reset(buf);\n      +\tstrbuf_addstr(buf, status == '!' ? color_old : color);\n       \tif (!a_util)\n     - \t\tstrbuf_addf(buf, \"-:  %s \", dashes);\n     + \t\tstrbuf_addf(buf, \"-:  %s \", dashes->buf);\n       \telse\n       \t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n       \t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n     @@ -77,7 +77,7 @@\n      +\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color_new);\n       \n       \tif (!b_util)\n     - \t\tstrbuf_addf(buf, \" -:  %s\", dashes);\n     + \t\tstrbuf_addf(buf, \" -:  %s\", dashes->buf);\n      @@\n       \t\tconst char *commit_buffer = get_commit_buffer(commit, NULL);\n       \t\tconst char *subject;\n     @@ -99,24 +99,27 @@\n       \n       \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n       \t\tif (i < a->nr && a_util->matching < 0) {\n     --\t\t\toutput_pair_header(&buf, a_util, NULL);\n     -+\t\t\toutput_pair_header(diffopt, &buf, a_util, NULL);\n     +-\t\t\toutput_pair_header(&buf, &dashes, a_util, NULL);\n     ++\t\t\toutput_pair_header(diffopt,\n     ++\t\t\t\t\t   &buf, &dashes, a_util, NULL);\n       \t\t\ti++;\n       \t\t\tcontinue;\n       \t\t}\n       \n       \t\t/* Show unmatched RHS commits. */\n       \t\twhile (j < b->nr && b_util->matching < 0) {\n     --\t\t\toutput_pair_header(&buf, NULL, b_util);\n     -+\t\t\toutput_pair_header(diffopt, &buf, NULL, b_util);\n     +-\t\t\toutput_pair_header(&buf, &dashes, NULL, b_util);\n     ++\t\t\toutput_pair_header(diffopt,\n     ++\t\t\t\t\t   &buf, &dashes, NULL, b_util);\n       \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n       \t\t}\n       \n       \t\t/* Show matching LHS/RHS pair. */\n       \t\tif (j < b->nr) {\n       \t\t\ta_util = a->items[b_util->matching].util;\n     --\t\t\toutput_pair_header(&buf, a_util, b_util);\n     -+\t\t\toutput_pair_header(diffopt, &buf, a_util, b_util);\n     +-\t\t\toutput_pair_header(&buf, &dashes, a_util, b_util);\n     ++\t\t\toutput_pair_header(diffopt,\n     ++\t\t\t\t\t   &buf, &dashes, a_util, b_util);\n       \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n       \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n       \t\t\t\t\t   b->items[j].string, diffopt);\n 13:  96a3073fb = 13:  9ccb9516a color: add the meta color GIT_COLOR_REVERSE\n 14:  6be4baf60 = 14:  9de5bd229 diff: add an internal option to dual-color diffs of diffs\n 15:  02e13c0c6 ! 15:  21b2f9e4b range-diff: offer to dual-color the diffs\n     @@ -40,4 +40,4 @@\n      +\n       \tif (argc == 2) {\n       \t\tif (!strstr(argv[0], \"..\"))\n     - \t\t\twarning(_(\"no .. in range: '%s'\"), argv[0]);\n     + \t\t\tdie(_(\"no .. in range: '%s'\"), argv[0]);\n 16:  dfa7b1e71 <  -:  --------- range-diff --dual-color: work around bogus white-space warning\n  -:  --------- > 16:  f4252f2b2 range-diff --dual-color: fix bogus white-space warning\n 17:  799da25ef ! 17:  9e09c6be6 range-diff: add a man page\n     @@ -1,6 +1,6 @@\n      Author: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -    range-diff: add a man page\n     +    range-diff: populate the man page\n      \n          The bulk of this patch consists of a heavily butchered version of\n          tbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from\n     @@ -9,17 +9,12 @@\n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n      diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\n     -new file mode 100644\n     ---- /dev/null\n     +--- a/Documentation/git-range-diff.txt\n      +++ b/Documentation/git-range-diff.txt\n      @@\n     -+git-range-diff(1)\n     -+==================\n     -+\n     -+NAME\n     -+----\n     -+git-range-diff - Compare two commit ranges (e.g. two versions of a branch)\n     -+\n     + ----\n     + git-range-diff - Compare two commit ranges (e.g. two versions of a branch)\n     + \n      +SYNOPSIS\n      +--------\n      +[verse]\n     @@ -31,13 +26,13 @@\n      +-----------\n      +\n      +This command shows the differences between two versions of a patch\n     -+series, or more generally, two commit ranges (ignoring merges).\n     ++series, or more generally, two commit ranges (ignoring merge commits).\n      +\n      +To that end, it first finds pairs of commits from both commit ranges\n      +that correspond with each other. Two commits are said to correspond when\n      +the diff between their patches (i.e. the author information, the commit\n      +message and the commit diff) is reasonably small compared to the\n     -+patches' size. See ``Algorithm` below for details.\n     ++patches' size. See ``Algorithm`` below for details.\n      +\n      +Finally, the list of matching commits is shown in the order of the\n      +second commit range, with unmatched commits being inserted just after\n     @@ -150,6 +145,10 @@\n      +The general idea is this: we generate a cost matrix between the commits\n      +in both commit ranges, then solve the least-cost assignment.\n      +\n     ++The cost matrix is populated thusly: for each pair of commits, both\n     ++diffs are generated and the \"diff of diffs\" is generated, with 3 context\n     ++lines, then the number of lines in that diff is used as cost.\n     ++\n      +To avoid false positives (e.g. when a patch has been removed, and an\n      +unrelated patch has been added between two iterations of the same patch\n      +series), the cost matrix is extended to allow for that, by adding\n     @@ -245,6 +244,6 @@\n      +--------\n      +linkgit:git-log[1]\n      +\n     -+GIT\n     -+---\n     -+Part of the linkgit:git[1] suite\n     + GIT\n     + ---\n     + Part of the linkgit:git[1] suite\n 18:  d05b54c60 ! 18:  9b3632324 completion: support `git range-diff`\n     @@ -4,17 +4,11 @@\n      \n          Tab completion of `git range-diff` is very convenient, especially\n          given that the revision arguments to specify the commit ranges to\n     -    compare are typically more complex than, say, your grandfather's `git\n     -    log` arguments.\n     +    compare are typically more complex than, say, what is normally passed\n     +    to `git log`.\n      \n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     -    squash! WIP completion: support `git range-diff`\n     -\n     -    Revert \"WIP completion: support `git range-diff`\"\n     -\n     -    This reverts commit 2e7af652af9e53a19fd947f8ebe37a78043afa49.\n     -\n      diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n      --- a/contrib/completion/git-completion.bash\n      +++ b/contrib/completion/git-completion.bash\n 19:  144363006 <  -:  --------- range-diff: left-pad patch numbers\n  -:  --------- > 19:  07ec215e8 range-diff: left-pad patch numbers\n 20:  4a68b95ce ! 20:  b370468e7 range-diff: make --dual-color the default mode\n     @@ -4,7 +4,7 @@\n      \n          After using this command extensively for the last two months, this\n          developer came to the conclusion that even if the dual color mode still\n     -    leaves a lot of room for confusion what was actually changed, the\n     +    leaves a lot of room for confusion about what was actually changed, the\n          non-dual color mode is substantially worse in that regard.\n      \n          Therefore, we really want to make the dual color mode the default.\n     @@ -14,6 +14,15 @@\n      diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\n      --- a/Documentation/git-range-diff.txt\n      +++ b/Documentation/git-range-diff.txt\n     +@@\n     + --------\n     + [verse]\n     + 'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]\n     +-\t[--dual-color] [--creation-factor=<factor>]\n     ++\t[--no-dual-color] [--creation-factor=<factor>]\n     + \t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n     + \n     + DESCRIPTION\n      @@\n       \n       OPTIONS\n     @@ -25,7 +34,7 @@\n      -\tchange in what exact lines were added.\n      +--no-dual-color::\n      +\tWhen the commit diffs differ, `git range-diff` recreates the\n     -+\toriginal diffs' coloring, and add outer -/+ diff markers with\n     ++\toriginal diffs' coloring, and adds outer -/+ diff markers with\n      +\tthe *background* being red/green to make it easier to see e.g.\n      +\twhen there was a change in what exact lines were added. This is\n      +\tknown to `range-diff` as \"dual coloring\". Use `--no-dual-color`\n     @@ -34,6 +43,31 @@\n       \n       --creation-factor=<percent>::\n       \tSet the creation/deletion cost fudge factor to `<percent>`.\n     +@@\n     + show`'s output, and the third line colors the old commit red, the new\n     + one green and the rest like `git show`'s commit header.\n     + \n     +-The color-coded diff is actually a bit hard to read, though, as it\n     +-colors the entire lines red or green. The line that added \"What is\n     +-unexpected\" in the old commit, for example, is completely red, even if\n     +-the intent of the old commit was to add something.\n     ++A naive color-coded diff of diffs is actually a bit hard to read,\n     ++though, as it colors the entire lines red or green. The line that added\n     ++\"What is unexpected\" in the old commit, for example, is completely red,\n     ++even if the intent of the old commit was to add something.\n     + \n     +-To help with that, use the `--dual-color` mode. In this mode, the diff\n     +-of diffs will retain the original diff colors, and prefix the lines with\n     +--/+ markers that have their *background* red or green, to make it more\n     +-obvious that they describe how the diff itself changed.\n     ++To help with that, `range` uses the `--dual-color` mode by default. In\n     ++this mode, the diff of diffs will retain the original diff colors, and\n     ++prefix the lines with -/+ markers that have their *background* red or\n     ++green, to make it more obvious that they describe how the diff itself\n     ++changed.\n     + \n     + \n     + Algorithm\n      \n      diff --git a/builtin/range-diff.c b/builtin/range-diff.c\n      --- a/builtin/range-diff.c\n  -:  --------- > 21:  d8498fb32 range-diff: use dim/bold cues to improve dual color mode\n\n-- \ngitgitgadget\n"},{"id":"353261","messageId":"f7e70689efcbeb8341c19fa3940c818142a2cddf.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 01/21] linear-assignment: a function to solve least-cost assignment problems","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:04:46Z","receivedAt":"2018-07-21T22:04:50Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe problem solved by the code introduced in this commit goes like this:\ngiven two sets of items, and a cost matrix which says how much it\n\"costs\" to assign any given item of the first set to any given item of\nthe second, assign all items (except when the sets have different size)\nin the cheapest way.\n\nWe use the Jonker-Volgenant algorithm to solve the assignment problem to\nanswer questions such as: given two different versions of a topic branch\n(or iterations of a patch series), what is the best pairing of\ncommits/patches between the different versions?\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Makefile            |   1 +\n linear-assignment.c | 201 ++++++++++++++++++++++++++++++++++++++++++++\n linear-assignment.h |  22 +++++\n 3 files changed, 224 insertions(+)\n create mode 100644 linear-assignment.c\n create mode 100644 linear-assignment.h\n\ndiff --git a/Makefile b/Makefile\nindex 08e5c5454..56326ab2b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -868,6 +868,7 @@ LIB_OBJS += gpg-interface.o\n LIB_OBJS += graph.o\n LIB_OBJS += grep.o\n LIB_OBJS += hashmap.o\n+LIB_OBJS += linear-assignment.o\n LIB_OBJS += help.o\n LIB_OBJS += hex.o\n LIB_OBJS += ident.o\ndiff --git a/linear-assignment.c b/linear-assignment.c\nnew file mode 100644\nindex 000000000..9b3e56e28\n--- /dev/null\n+++ b/linear-assignment.c\n@@ -0,0 +1,201 @@\n+/*\n+ * Based on: Jonker, R., & Volgenant, A. (1987). <i>A shortest augmenting path\n+ * algorithm for dense and sparse linear assignment problems</i>. Computing,\n+ * 38(4), 325-340.\n+ */\n+#include \"cache.h\"\n+#include \"linear-assignment.h\"\n+\n+#define COST(column, row) cost[(column) + column_count * (row)]\n+\n+/*\n+ * The parameter `cost` is the cost matrix: the cost to assign column j to row\n+ * i is `cost[j + column_count * i].\n+ */\n+void compute_assignment(int column_count, int row_count, int *cost,\n+\t\t\tint *column2row, int *row2column)\n+{\n+\tint *v, *d;\n+\tint *free_row, free_count = 0, saved_free_count, *pred, *col;\n+\tint i, j, phase;\n+\n+\tmemset(column2row, -1, sizeof(int) * column_count);\n+\tmemset(row2column, -1, sizeof(int) * row_count);\n+\tALLOC_ARRAY(v, column_count);\n+\n+\t/* column reduction */\n+\tfor (j = column_count - 1; j >= 0; j--) {\n+\t\tint i1 = 0;\n+\n+\t\tfor (i = 1; i < row_count; i++)\n+\t\t\tif (COST(j, i1) > COST(j, i))\n+\t\t\t\ti1 = i;\n+\t\tv[j] = COST(j, i1);\n+\t\tif (row2column[i1] == -1) {\n+\t\t\t/* row i1 unassigned */\n+\t\t\trow2column[i1] = j;\n+\t\t\tcolumn2row[j] = i1;\n+\t\t} else {\n+\t\t\tif (row2column[i1] >= 0)\n+\t\t\t\trow2column[i1] = -2 - row2column[i1];\n+\t\t\tcolumn2row[j] = -1;\n+\t\t}\n+\t}\n+\n+\t/* reduction transfer */\n+\tALLOC_ARRAY(free_row, row_count);\n+\tfor (i = 0; i < row_count; i++) {\n+\t\tint j1 = row2column[i];\n+\t\tif (j1 == -1)\n+\t\t\tfree_row[free_count++] = i;\n+\t\telse if (j1 < -1)\n+\t\t\trow2column[i] = -2 - j1;\n+\t\telse {\n+\t\t\tint min = COST(!j1, i) - v[!j1];\n+\t\t\tfor (j = 1; j < column_count; j++)\n+\t\t\t\tif (j != j1 && min > COST(j, i) - v[j])\n+\t\t\t\t\tmin = COST(j, i) - v[j];\n+\t\t\tv[j1] -= min;\n+\t\t}\n+\t}\n+\n+\tif (free_count ==\n+\t    (column_count < row_count ? row_count - column_count : 0)) {\n+\t\tfree(v);\n+\t\tfree(free_row);\n+\t\treturn;\n+\t}\n+\n+\t/* augmenting row reduction */\n+\tfor (phase = 0; phase < 2; phase++) {\n+\t\tint k = 0;\n+\n+\t\tsaved_free_count = free_count;\n+\t\tfree_count = 0;\n+\t\twhile (k < saved_free_count) {\n+\t\t\tint u1, u2;\n+\t\t\tint j1 = 0, j2, i0;\n+\n+\t\t\ti = free_row[k++];\n+\t\t\tu1 = COST(j1, i) - v[j1];\n+\t\t\tj2 = -1;\n+\t\t\tu2 = INT_MAX;\n+\t\t\tfor (j = 1; j < column_count; j++) {\n+\t\t\t\tint c = COST(j, i) - v[j];\n+\t\t\t\tif (u2 > c) {\n+\t\t\t\t\tif (u1 < c) {\n+\t\t\t\t\t\tu2 = c;\n+\t\t\t\t\t\tj2 = j;\n+\t\t\t\t\t} else {\n+\t\t\t\t\t\tu2 = u1;\n+\t\t\t\t\t\tu1 = c;\n+\t\t\t\t\t\tj2 = j1;\n+\t\t\t\t\t\tj1 = j;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tif (j2 < 0) {\n+\t\t\t\tj2 = j1;\n+\t\t\t\tu2 = u1;\n+\t\t\t}\n+\n+\t\t\ti0 = column2row[j1];\n+\t\t\tif (u1 < u2)\n+\t\t\t\tv[j1] -= u2 - u1;\n+\t\t\telse if (i0 >= 0) {\n+\t\t\t\tj1 = j2;\n+\t\t\t\ti0 = column2row[j1];\n+\t\t\t}\n+\n+\t\t\tif (i0 >= 0) {\n+\t\t\t\tif (u1 < u2)\n+\t\t\t\t\tfree_row[--k] = i0;\n+\t\t\t\telse\n+\t\t\t\t\tfree_row[free_count++] = i0;\n+\t\t\t}\n+\t\t\trow2column[i] = j1;\n+\t\t\tcolumn2row[j1] = i;\n+\t\t}\n+\t}\n+\n+\t/* augmentation */\n+\tsaved_free_count = free_count;\n+\tALLOC_ARRAY(d, column_count);\n+\tALLOC_ARRAY(pred, column_count);\n+\tALLOC_ARRAY(col, column_count);\n+\tfor (free_count = 0; free_count < saved_free_count; free_count++) {\n+\t\tint i1 = free_row[free_count], low = 0, up = 0, last, k;\n+\t\tint min, c, u1;\n+\n+\t\tfor (j = 0; j < column_count; j++) {\n+\t\t\td[j] = COST(j, i1) - v[j];\n+\t\t\tpred[j] = i1;\n+\t\t\tcol[j] = j;\n+\t\t}\n+\n+\t\tj = -1;\n+\t\tdo {\n+\t\t\tlast = low;\n+\t\t\tmin = d[col[up++]];\n+\t\t\tfor (k = up; k < column_count; k++) {\n+\t\t\t\tj = col[k];\n+\t\t\t\tc = d[j];\n+\t\t\t\tif (c <= min) {\n+\t\t\t\t\tif (c < min) {\n+\t\t\t\t\t\tup = low;\n+\t\t\t\t\t\tmin = c;\n+\t\t\t\t\t}\n+\t\t\t\t\tcol[k] = col[up];\n+\t\t\t\t\tcol[up++] = j;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tfor (k = low; k < up; k++)\n+\t\t\t\tif (column2row[col[k]] == -1)\n+\t\t\t\t\tgoto update;\n+\n+\t\t\t/* scan a row */\n+\t\t\tdo {\n+\t\t\t\tint j1 = col[low++];\n+\n+\t\t\t\ti = column2row[j1];\n+\t\t\t\tu1 = COST(j1, i) - v[j1] - min;\n+\t\t\t\tfor (k = up; k < column_count; k++) {\n+\t\t\t\t\tj = col[k];\n+\t\t\t\t\tc = COST(j, i) - v[j] - u1;\n+\t\t\t\t\tif (c < d[j]) {\n+\t\t\t\t\t\td[j] = c;\n+\t\t\t\t\t\tpred[j] = i;\n+\t\t\t\t\t\tif (c == min) {\n+\t\t\t\t\t\t\tif (column2row[j] == -1)\n+\t\t\t\t\t\t\t\tgoto update;\n+\t\t\t\t\t\t\tcol[k] = col[up];\n+\t\t\t\t\t\t\tcol[up++] = j;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t} while (low != up);\n+\t\t} while (low == up);\n+\n+update:\n+\t\t/* updating of the column pieces */\n+\t\tfor (k = 0; k < last; k++) {\n+\t\t\tint j1 = col[k];\n+\t\t\tv[j1] += d[j1] - min;\n+\t\t}\n+\n+\t\t/* augmentation */\n+\t\tdo {\n+\t\t\tif (j < 0)\n+\t\t\t\tBUG(\"negative j: %d\", j);\n+\t\t\ti = pred[j];\n+\t\t\tcolumn2row[j] = i;\n+\t\t\tSWAP(j, row2column[i]);\n+\t\t} while (i1 != i);\n+\t}\n+\n+\tfree(col);\n+\tfree(pred);\n+\tfree(d);\n+\tfree(v);\n+\tfree(free_row);\n+}\ndiff --git a/linear-assignment.h b/linear-assignment.h\nnew file mode 100644\nindex 000000000..fc4c502c8\n--- /dev/null\n+++ b/linear-assignment.h\n@@ -0,0 +1,22 @@\n+#ifndef HUNGARIAN_H\n+#define HUNGARIAN_H\n+\n+/*\n+ * Compute an assignment of columns -> rows (and vice versa) such that every\n+ * column is assigned to at most one row (and vice versa) minimizing the\n+ * overall cost.\n+ *\n+ * The parameter `cost` is the cost matrix: the cost to assign column j to row\n+ * i is `cost[j + column_count * i].\n+ *\n+ * The arrays column2row and row2column will be populated with the respective\n+ * assignments (-1 for unassigned, which can happen only if column_count !=\n+ * row_count).\n+ */\n+void compute_assignment(int column_count, int row_count, int *cost,\n+\t\t\tint *column2row, int *row2column);\n+\n+/* The maximal cost in the cost matrix (to prevent integer overflows). */\n+#define COST_MAX (1<<16)\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"353262","messageId":"88134121d2af514c6c5f976c1cde0017c447c43d.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 02/21] Introduce `range-diff` to compare iterations of a topic branch","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:04:47Z","receivedAt":"2018-07-21T22:04:50Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis command does not do a whole lot so far, apart from showing a usage\nthat is oddly similar to that of `git tbdiff`. And for a good reason:\nthe next commits will turn `range-branch` into a full-blown replacement\nfor `tbdiff`.\n\nAt this point, we ignore tbdiff's color options, as they will all be\nimplemented later using diff_options.\n\nSince f318d739159 (generate-cmds.sh: export all commands to\ncommand-list.h, 2018-05-10), every new command *requires* a man page to\nbuild right away, so let's also add a blank man page, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n .gitignore                       |  1 +\n Documentation/git-range-diff.txt | 10 ++++++++++\n Makefile                         |  1 +\n builtin.h                        |  1 +\n builtin/range-diff.c             | 25 +++++++++++++++++++++++++\n command-list.txt                 |  1 +\n git.c                            |  1 +\n 7 files changed, 40 insertions(+)\n create mode 100644 Documentation/git-range-diff.txt\n create mode 100644 builtin/range-diff.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 3284a1e9b..cc0ad74b4 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -113,6 +113,7 @@\n /git-pull\n /git-push\n /git-quiltimport\n+/git-range-diff\n /git-read-tree\n /git-rebase\n /git-rebase--am\ndiff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\nnew file mode 100644\nindex 000000000..de0ca5df4\n--- /dev/null\n+++ b/Documentation/git-range-diff.txt\n@@ -0,0 +1,10 @@\n+git-range-diff(1)\n+==================\n+\n+NAME\n+----\n+git-range-diff - Compare two commit ranges (e.g. two versions of a branch)\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex 56326ab2b..45c9dea1b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1059,6 +1059,7 @@ BUILTIN_OBJS += builtin/prune-packed.o\n BUILTIN_OBJS += builtin/prune.o\n BUILTIN_OBJS += builtin/pull.o\n BUILTIN_OBJS += builtin/push.o\n+BUILTIN_OBJS += builtin/range-diff.o\n BUILTIN_OBJS += builtin/read-tree.o\n BUILTIN_OBJS += builtin/rebase--helper.o\n BUILTIN_OBJS += builtin/receive-pack.o\ndiff --git a/builtin.h b/builtin.h\nindex 0362f1ce2..99206df4b 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -201,6 +201,7 @@ extern int cmd_prune(int argc, const char **argv, const char *prefix);\n extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);\n extern int cmd_pull(int argc, const char **argv, const char *prefix);\n extern int cmd_push(int argc, const char **argv, const char *prefix);\n+extern int cmd_range_diff(int argc, const char **argv, const char *prefix);\n extern int cmd_read_tree(int argc, const char **argv, const char *prefix);\n extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix);\n extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nnew file mode 100644\nindex 000000000..36788ea4f\n--- /dev/null\n+++ b/builtin/range-diff.c\n@@ -0,0 +1,25 @@\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"parse-options.h\"\n+\n+static const char * const builtin_range_diff_usage[] = {\n+N_(\"git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n+N_(\"git range-diff [<options>] <old-tip>...<new-tip>\"),\n+N_(\"git range-diff [<options>] <base> <old-tip> <new-tip>\"),\n+NULL\n+};\n+\n+int cmd_range_diff(int argc, const char **argv, const char *prefix)\n+{\n+\tint creation_factor = 60;\n+\tstruct option options[] = {\n+\t\tOPT_INTEGER(0, \"creation-factor\", &creation_factor,\n+\t\t\t    N_(\"Percentage by which creation is weighted\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, options,\n+\t\t\t     builtin_range_diff_usage, 0);\n+\n+\treturn 0;\n+}\ndiff --git a/command-list.txt b/command-list.txt\nindex e1c26c1bb..a9dda3b8a 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -139,6 +139,7 @@ git-prune-packed                        plumbingmanipulators\n git-pull                                mainporcelain           remote\n git-push                                mainporcelain           remote\n git-quiltimport                         foreignscminterface\n+git-range-diff                          mainporcelain\n git-read-tree                           plumbingmanipulators\n git-rebase                              mainporcelain           history\n git-receive-pack                        synchelpers\ndiff --git a/git.c b/git.c\nindex 3fded7451..6901cf328 100644\n--- a/git.c\n+++ b/git.c\n@@ -517,6 +517,7 @@ static struct cmd_struct commands[] = {\n \t{ \"prune-packed\", cmd_prune_packed, RUN_SETUP },\n \t{ \"pull\", cmd_pull, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"push\", cmd_push, RUN_SETUP },\n+\t{ \"range-diff\", cmd_range_diff, RUN_SETUP | USE_PAGER },\n \t{ \"read-tree\", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX},\n \t{ \"rebase--helper\", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"receive-pack\", cmd_receive_pack },\n-- \ngitgitgadget\n\n"},{"id":"353263","messageId":"4e3fb47a1dcef96780bd536032b81dd99387f2db.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 03/21] range-diff: first rudimentary implementation","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:04:49Z","receivedAt":"2018-07-21T22:04:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nAt this stage, `git range-diff` can determine corresponding commits\nof two related commit ranges. This makes use of the recently introduced\nimplementation of the linear assignment algorithm.\n\nThe core of this patch is a straight port of the ideas of tbdiff, the\napparently dormant project at https://github.com/trast/tbdiff.\n\nThe output does not at all match `tbdiff`'s output yet, as this patch\nreally concentrates on getting the patch matching part right.\n\nNote: due to differences in the diff algorithm (`tbdiff` uses the Python\nmodule `difflib`, Git uses its xdiff fork), the cost matrix calculated\nby `range-diff` is different (but very similar) to the one calculated\nby `tbdiff`. Therefore, it is possible that they find different matching\ncommits in corner cases (e.g. when a patch was split into two patches of\nroughly equal length).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Makefile             |   1 +\n builtin/range-diff.c |  43 +++++-\n range-diff.c         | 311 +++++++++++++++++++++++++++++++++++++++++++\n range-diff.h         |   7 +\n 4 files changed, 361 insertions(+), 1 deletion(-)\n create mode 100644 range-diff.c\n create mode 100644 range-diff.h\n\ndiff --git a/Makefile b/Makefile\nindex 45c9dea1b..41b93689a 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -921,6 +921,7 @@ LIB_OBJS += progress.o\n LIB_OBJS += prompt.o\n LIB_OBJS += protocol.o\n LIB_OBJS += quote.o\n+LIB_OBJS += range-diff.o\n LIB_OBJS += reachable.o\n LIB_OBJS += read-cache.o\n LIB_OBJS += reflog-walk.o\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 36788ea4f..3881da246 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -1,6 +1,7 @@\n #include \"cache.h\"\n #include \"builtin.h\"\n #include \"parse-options.h\"\n+#include \"range-diff.h\"\n \n static const char * const builtin_range_diff_usage[] = {\n N_(\"git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n@@ -17,9 +18,49 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"Percentage by which creation is weighted\")),\n \t\tOPT_END()\n \t};\n+\tint res = 0;\n+\tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n \n \targc = parse_options(argc, argv, NULL, options,\n \t\t\t     builtin_range_diff_usage, 0);\n \n-\treturn 0;\n+\tif (argc == 2) {\n+\t\tif (!strstr(argv[0], \"..\"))\n+\t\t\tdie(_(\"no .. in range: '%s'\"), argv[0]);\n+\t\tstrbuf_addstr(&range1, argv[0]);\n+\n+\t\tif (!strstr(argv[1], \"..\"))\n+\t\t\tdie(_(\"no .. in range: '%s'\"), argv[1]);\n+\t\tstrbuf_addstr(&range2, argv[1]);\n+\t} else if (argc == 3) {\n+\t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n+\t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n+\t} else if (argc == 1) {\n+\t\tconst char *b = strstr(argv[0], \"...\"), *a = argv[0];\n+\t\tint a_len;\n+\n+\t\tif (!b)\n+\t\t\tdie(_(\"single arg format requires a symmetric range\"));\n+\n+\t\ta_len = (int)(b - a);\n+\t\tif (!a_len) {\n+\t\t\ta = \"HEAD\";\n+\t\t\ta_len = strlen(a);\n+\t\t}\n+\t\tb += 3;\n+\t\tif (!*b)\n+\t\t\tb = \"HEAD\";\n+\t\tstrbuf_addf(&range1, \"%s..%.*s\", b, a_len, a);\n+\t\tstrbuf_addf(&range2, \"%.*s..%s\", a_len, a, b);\n+\t} else {\n+\t\terror(_(\"need two commit ranges\"));\n+\t\tusage_with_options(builtin_range_diff_usage, options);\n+\t}\n+\n+\tres = show_range_diff(range1.buf, range2.buf, creation_factor);\n+\n+\tstrbuf_release(&range1);\n+\tstrbuf_release(&range2);\n+\n+\treturn res;\n }\ndiff --git a/range-diff.c b/range-diff.c\nnew file mode 100644\nindex 000000000..15d418afa\n--- /dev/null\n+++ b/range-diff.c\n@@ -0,0 +1,311 @@\n+#include \"cache.h\"\n+#include \"range-diff.h\"\n+#include \"string-list.h\"\n+#include \"run-command.h\"\n+#include \"argv-array.h\"\n+#include \"hashmap.h\"\n+#include \"xdiff-interface.h\"\n+#include \"linear-assignment.h\"\n+\n+struct patch_util {\n+\t/* For the search for an exact match */\n+\tstruct hashmap_entry e;\n+\tconst char *diff, *patch;\n+\n+\tint i;\n+\tint diffsize;\n+\tsize_t diff_offset;\n+\t/* the index of the matching item in the other branch, or -1 */\n+\tint matching;\n+\tstruct object_id oid;\n+};\n+\n+/*\n+ * Reads the patches into a string list, with the `util` field being populated\n+ * as struct object_id (will need to be free()d).\n+ */\n+static int read_patches(const char *range, struct string_list *list)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tFILE *in;\n+\tstruct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;\n+\tstruct patch_util *util = NULL;\n+\tint in_header = 1;\n+\n+\targv_array_pushl(&cp.args, \"log\", \"--no-color\", \"-p\", \"--no-merges\",\n+\t\t\t\"--reverse\", \"--date-order\", \"--decorate=no\",\n+\t\t\t\"--no-abbrev-commit\", range,\n+\t\t\tNULL);\n+\tcp.out = -1;\n+\tcp.no_stdin = 1;\n+\tcp.git_cmd = 1;\n+\n+\tif (start_command(&cp))\n+\t\treturn error_errno(_(\"could not start `log`\"));\n+\tin = fdopen(cp.out, \"r\");\n+\tif (!in) {\n+\t\terror_errno(_(\"could not read `log` output\"));\n+\t\tfinish_command(&cp);\n+\t\treturn -1;\n+\t}\n+\n+\twhile (strbuf_getline(&line, in) != EOF) {\n+\t\tconst char *p;\n+\n+\t\tif (skip_prefix(line.buf, \"commit \", &p)) {\n+\t\t\tif (util) {\n+\t\t\t\tstring_list_append(list, buf.buf)->util = util;\n+\t\t\t\tstrbuf_reset(&buf);\n+\t\t\t}\n+\t\t\tutil = xcalloc(sizeof(*util), 1);\n+\t\t\tif (get_oid(p, &util->oid)) {\n+\t\t\t\terror(_(\"could not parse commit '%s'\"), p);\n+\t\t\t\tfree(util);\n+\t\t\t\tstring_list_clear(list, 1);\n+\t\t\t\tstrbuf_release(&buf);\n+\t\t\t\tstrbuf_release(&line);\n+\t\t\t\tfclose(in);\n+\t\t\t\tfinish_command(&cp);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t\tutil->matching = -1;\n+\t\t\tin_header = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (starts_with(line.buf, \"diff --git\")) {\n+\t\t\tin_header = 0;\n+\t\t\tstrbuf_addch(&buf, '\\n');\n+\t\t\tif (!util->diff_offset)\n+\t\t\t\tutil->diff_offset = buf.len;\n+\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\t} else if (in_header) {\n+\t\t\tif (starts_with(line.buf, \"Author: \")) {\n+\t\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\t\t\tstrbuf_addstr(&buf, \"\\n\\n\");\n+\t\t\t} else if (starts_with(line.buf, \"    \")) {\n+\t\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\t\t\tstrbuf_addch(&buf, '\\n');\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t} else if (starts_with(line.buf, \"@@ \"))\n+\t\t\tstrbuf_addstr(&buf, \"@@\");\n+\t\telse if (!line.buf[0] || starts_with(line.buf, \"index \"))\n+\t\t\t/*\n+\t\t\t * A completely blank (not ' \\n', which is context)\n+\t\t\t * line is not valid in a diff.  We skip it\n+\t\t\t * silently, because this neatly handles the blank\n+\t\t\t * separator line between commits in git-log\n+\t\t\t * output.\n+\t\t\t *\n+\t\t\t * We also want to ignore the diff's `index` lines\n+\t\t\t * because they contain exact blob hashes in which\n+\t\t\t * we are not interested.\n+\t\t\t */\n+\t\t\tcontinue;\n+\t\telse\n+\t\t\tstrbuf_addbuf(&buf, &line);\n+\n+\t\tstrbuf_addch(&buf, '\\n');\n+\t\tutil->diffsize++;\n+\t}\n+\tfclose(in);\n+\tstrbuf_release(&line);\n+\n+\tif (util)\n+\t\tstring_list_append(list, buf.buf)->util = util;\n+\tstrbuf_release(&buf);\n+\n+\tif (finish_command(&cp))\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+static int patch_util_cmp(const void *dummy, const struct patch_util *a,\n+\t\t     const struct patch_util *b, const char *keydata)\n+{\n+\treturn strcmp(a->diff, keydata ? keydata : b->diff);\n+}\n+\n+static void find_exact_matches(struct string_list *a, struct string_list *b)\n+{\n+\tstruct hashmap map;\n+\tint i;\n+\n+\thashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);\n+\n+\t/* First, add the patches of a to a hash map */\n+\tfor (i = 0; i < a->nr; i++) {\n+\t\tstruct patch_util *util = a->items[i].util;\n+\n+\t\tutil->i = i;\n+\t\tutil->patch = a->items[i].string;\n+\t\tutil->diff = util->patch + util->diff_offset;\n+\t\thashmap_entry_init(util, strhash(util->diff));\n+\t\thashmap_add(&map, util);\n+\t}\n+\n+\t/* Now try to find exact matches in b */\n+\tfor (i = 0; i < b->nr; i++) {\n+\t\tstruct patch_util *util = b->items[i].util, *other;\n+\n+\t\tutil->i = i;\n+\t\tutil->patch = b->items[i].string;\n+\t\tutil->diff = util->patch + util->diff_offset;\n+\t\thashmap_entry_init(util, strhash(util->diff));\n+\t\tother = hashmap_remove(&map, util, NULL);\n+\t\tif (other) {\n+\t\t\tif (other->matching >= 0)\n+\t\t\t\tBUG(\"already assigned!\");\n+\n+\t\t\tother->matching = i;\n+\t\t\tutil->matching = other->i;\n+\t\t}\n+\t}\n+\n+\thashmap_free(&map, 0);\n+}\n+\n+static void diffsize_consume(void *data, char *line, unsigned long len)\n+{\n+\t(*(int *)data)++;\n+}\n+\n+static int diffsize(const char *a, const char *b)\n+{\n+\txpparam_t pp = { 0 };\n+\txdemitconf_t cfg = { 0 };\n+\tmmfile_t mf1, mf2;\n+\tint count = 0;\n+\n+\tmf1.ptr = (char *)a;\n+\tmf1.size = strlen(a);\n+\tmf2.ptr = (char *)b;\n+\tmf2.size = strlen(b);\n+\n+\tcfg.ctxlen = 3;\n+\tif (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))\n+\t\treturn count;\n+\n+\terror(_(\"failed to generate diff\"));\n+\treturn COST_MAX;\n+}\n+\n+static void get_correspondences(struct string_list *a, struct string_list *b,\n+\t\t\t\tint creation_factor)\n+{\n+\tint n = a->nr + b->nr;\n+\tint *cost, c, *a2b, *b2a;\n+\tint i, j;\n+\n+\tALLOC_ARRAY(cost, st_mult(n, n));\n+\tALLOC_ARRAY(a2b, n);\n+\tALLOC_ARRAY(b2a, n);\n+\n+\tfor (i = 0; i < a->nr; i++) {\n+\t\tstruct patch_util *a_util = a->items[i].util;\n+\n+\t\tfor (j = 0; j < b->nr; j++) {\n+\t\t\tstruct patch_util *b_util = b->items[j].util;\n+\n+\t\t\tif (a_util->matching == j)\n+\t\t\t\tc = 0;\n+\t\t\telse if (a_util->matching < 0 && b_util->matching < 0)\n+\t\t\t\tc = diffsize(a_util->diff, b_util->diff);\n+\t\t\telse\n+\t\t\t\tc = COST_MAX;\n+\t\t\tcost[i + n * j] = c;\n+\t\t}\n+\n+\t\tc = a_util->matching < 0 ?\n+\t\t\ta_util->diffsize * creation_factor / 100 : COST_MAX;\n+\t\tfor (j = b->nr; j < n; j++)\n+\t\t\tcost[i + n * j] = c;\n+\t}\n+\n+\tfor (j = 0; j < b->nr; j++) {\n+\t\tstruct patch_util *util = b->items[j].util;\n+\n+\t\tc = util->matching < 0 ?\n+\t\t\tutil->diffsize * creation_factor / 100 : COST_MAX;\n+\t\tfor (i = a->nr; i < n; i++)\n+\t\t\tcost[i + n * j] = c;\n+\t}\n+\n+\tfor (i = a->nr; i < n; i++)\n+\t\tfor (j = b->nr; j < n; j++)\n+\t\t\tcost[i + n * j] = 0;\n+\n+\tcompute_assignment(n, n, cost, a2b, b2a);\n+\n+\tfor (i = 0; i < a->nr; i++)\n+\t\tif (a2b[i] >= 0 && a2b[i] < b->nr) {\n+\t\t\tstruct patch_util *a_util = a->items[i].util;\n+\t\t\tstruct patch_util *b_util = b->items[a2b[i]].util;\n+\n+\t\t\ta_util->matching = a2b[i];\n+\t\t\tb_util->matching = i;\n+\t\t}\n+\n+\tfree(cost);\n+\tfree(a2b);\n+\tfree(b2a);\n+}\n+\n+static const char *short_oid(struct patch_util *util)\n+{\n+\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n+}\n+\n+static void output(struct string_list *a, struct string_list *b)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < b->nr; i++) {\n+\t\tstruct patch_util *util = b->items[i].util, *prev;\n+\n+\t\tif (util->matching < 0)\n+\t\t\tprintf(\"-: -------- > %d: %s\\n\",\n+\t\t\t\t\ti + 1, short_oid(util));\n+\t\telse {\n+\t\t\tprev = a->items[util->matching].util;\n+\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n+\t\t\t       util->matching + 1, short_oid(prev),\n+\t\t\t       i + 1, short_oid(util));\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i < a->nr; i++) {\n+\t\tstruct patch_util *util = a->items[i].util;\n+\n+\t\tif (util->matching < 0)\n+\t\t\tprintf(\"%d: %s < -: --------\\n\",\n+\t\t\t       i + 1, short_oid(util));\n+\t}\n+}\n+\n+int show_range_diff(const char *range1, const char *range2,\n+\t\t    int creation_factor)\n+{\n+\tint res = 0;\n+\n+\tstruct string_list branch1 = STRING_LIST_INIT_DUP;\n+\tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n+\n+\tif (read_patches(range1, &branch1))\n+\t\tres = error(_(\"could not parse log for '%s'\"), range1);\n+\tif (!res && read_patches(range2, &branch2))\n+\t\tres = error(_(\"could not parse log for '%s'\"), range2);\n+\n+\tif (!res) {\n+\t\tfind_exact_matches(&branch1, &branch2);\n+\t\tget_correspondences(&branch1, &branch2, creation_factor);\n+\t\toutput(&branch1, &branch2);\n+\t}\n+\n+\tstring_list_clear(&branch1, 1);\n+\tstring_list_clear(&branch2, 1);\n+\n+\treturn res;\n+}\ndiff --git a/range-diff.h b/range-diff.h\nnew file mode 100644\nindex 000000000..dd30449c4\n--- /dev/null\n+++ b/range-diff.h\n@@ -0,0 +1,7 @@\n+#ifndef BRANCH_DIFF_H\n+#define BRANCH_DIFF_H\n+\n+int show_range_diff(const char *range1, const char *range2,\n+\t\t    int creation_factor);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"353264","messageId":"47bee09b059745ed2dcc19f05ea0fc087f67d236.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 04/21] range-diff: improve the order of the shown commits","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:04:50Z","receivedAt":"2018-07-21T22:04:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis patch lets `git range-diff` use the same order as tbdiff.\n\nThe idea is simple: for left-to-right readers, it is natural to assume\nthat the `git range-diff` is performed between an older vs a newer\nversion of the branch. As such, the user is probably more interested in\nthe question \"where did this come from?\" rather than \"where did that one\ngo?\".\n\nTo that end, we list the commits in the order of the second commit range\n(\"the newer version\"), inserting the unmatched commits of the first\ncommit range as soon as all their predecessors have been shown.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 59 +++++++++++++++++++++++++++++++++++-----------------\n 1 file changed, 40 insertions(+), 19 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 15d418afa..2d94200d3 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -12,7 +12,7 @@ struct patch_util {\n \tstruct hashmap_entry e;\n \tconst char *diff, *patch;\n \n-\tint i;\n+\tint i, shown;\n \tint diffsize;\n \tsize_t diff_offset;\n \t/* the index of the matching item in the other branch, or -1 */\n@@ -260,28 +260,49 @@ static const char *short_oid(struct patch_util *util)\n \n static void output(struct string_list *a, struct string_list *b)\n {\n-\tint i;\n-\n-\tfor (i = 0; i < b->nr; i++) {\n-\t\tstruct patch_util *util = b->items[i].util, *prev;\n+\tint i = 0, j = 0;\n+\n+\t/*\n+\t * We assume the user is really more interested in the second argument\n+\t * (\"newer\" version). To that end, we print the output in the order of\n+\t * the RHS (the `b` parameter). To put the LHS (the `a` parameter)\n+\t * commits that are no longer in the RHS into a good place, we place\n+\t * them once we have shown all of their predecessors in the LHS.\n+\t */\n+\n+\twhile (i < a->nr || j < b->nr) {\n+\t\tstruct patch_util *a_util, *b_util;\n+\t\ta_util = i < a->nr ? a->items[i].util : NULL;\n+\t\tb_util = j < b->nr ? b->items[j].util : NULL;\n+\n+\t\t/* Skip all the already-shown commits from the LHS. */\n+\t\twhile (i < a->nr && a_util->shown)\n+\t\t\ta_util = ++i < a->nr ? a->items[i].util : NULL;\n+\n+\t\t/* Show unmatched LHS commit whose predecessors were shown. */\n+\t\tif (i < a->nr && a_util->matching < 0) {\n+\t\t\tprintf(\"%d: %s < -: --------\\n\",\n+\t\t\t       i + 1, short_oid(a_util));\n+\t\t\ti++;\n+\t\t\tcontinue;\n+\t\t}\n \n-\t\tif (util->matching < 0)\n+\t\t/* Show unmatched RHS commits. */\n+\t\twhile (j < b->nr && b_util->matching < 0) {\n \t\t\tprintf(\"-: -------- > %d: %s\\n\",\n-\t\t\t\t\ti + 1, short_oid(util));\n-\t\telse {\n-\t\t\tprev = a->items[util->matching].util;\n-\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n-\t\t\t       util->matching + 1, short_oid(prev),\n-\t\t\t       i + 1, short_oid(util));\n+\t\t\t       j + 1, short_oid(b_util));\n+\t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n-\t}\n-\n-\tfor (i = 0; i < a->nr; i++) {\n-\t\tstruct patch_util *util = a->items[i].util;\n \n-\t\tif (util->matching < 0)\n-\t\t\tprintf(\"%d: %s < -: --------\\n\",\n-\t\t\t       i + 1, short_oid(util));\n+\t\t/* Show matching LHS/RHS pair. */\n+\t\tif (j < b->nr) {\n+\t\t\ta_util = a->items[b_util->matching].util;\n+\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n+\t\t\t       b_util->matching + 1, short_oid(a_util),\n+\t\t\t       j + 1, short_oid(b_util));\n+\t\t\ta_util->shown = 1;\n+\t\t\tj++;\n+\t\t}\n \t}\n }\n \n-- \ngitgitgadget\n\n"},{"id":"353265","messageId":"94afaeaf224563effda7b3c0b8939567302d2ba1.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 05/21] range-diff: also show the diff between patches","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:04:51Z","receivedAt":"2018-07-21T22:04:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nJust like tbdiff, we now show the diff between matching patches. This is\na \"diff of two diffs\", so it can be a bit daunting to read for the\nbeginner.\n\nAn alternative would be to display an interdiff, i.e. the hypothetical\ndiff which is the result of first reverting the old diff and then\napplying the new diff.\n\nEspecially when rebasing often, an interdiff is often not feasible,\nthough: if the old diff cannot be applied in reverse (due to a moving\nupstream), an interdiff can simply not be inferred.\n\nThis commit brings `range-diff` closer to feature parity with regard\nto tbdiff.\n\nTo make `git range-diff` respect e.g. color.diff.* settings, we have\nto adjust git_branch_config() accordingly.\n\nNote: while we now parse diff options such as --color, the effect is not\nyet the same as in tbdiff, where also the commit pairs would be colored.\nThis is left for a later commit.\n\nNote also: while tbdiff accepts the `--no-patches` option to suppress\nthese diffs between patches, we prefer the `-s` option that is\nautomatically supported via our use of diff_opt_parse().\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/range-diff.c | 25 ++++++++++++++++++++++---\n range-diff.c         | 34 +++++++++++++++++++++++++++++++---\n range-diff.h         |  4 +++-\n 3 files changed, 56 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 3881da246..093202117 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -2,6 +2,7 @@\n #include \"builtin.h\"\n #include \"parse-options.h\"\n #include \"range-diff.h\"\n+#include \"config.h\"\n \n static const char * const builtin_range_diff_usage[] = {\n N_(\"git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n@@ -13,16 +14,33 @@ NULL\n int cmd_range_diff(int argc, const char **argv, const char *prefix)\n {\n \tint creation_factor = 60;\n+\tstruct diff_options diffopt = { NULL };\n \tstruct option options[] = {\n \t\tOPT_INTEGER(0, \"creation-factor\", &creation_factor,\n \t\t\t    N_(\"Percentage by which creation is weighted\")),\n \t\tOPT_END()\n \t};\n-\tint res = 0;\n+\tint i, j, res = 0;\n \tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n \n+\tgit_config(git_diff_ui_config, NULL);\n+\n+\tdiff_setup(&diffopt);\n+\tdiffopt.output_format = DIFF_FORMAT_PATCH;\n+\n \targc = parse_options(argc, argv, NULL, options,\n-\t\t\t     builtin_range_diff_usage, 0);\n+\t\t\t     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n+\n+\tfor (i = j = 0; i < argc; ) {\n+\t\tint c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);\n+\n+\t\tif (!c)\n+\t\t\targv[j++] = argv[i++];\n+\t\telse\n+\t\t\ti += c;\n+\t}\n+\targc = j;\n+\tdiff_setup_done(&diffopt);\n \n \tif (argc == 2) {\n \t\tif (!strstr(argv[0], \"..\"))\n@@ -57,7 +75,8 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(builtin_range_diff_usage, options);\n \t}\n \n-\tres = show_range_diff(range1.buf, range2.buf, creation_factor);\n+\tres = show_range_diff(range1.buf, range2.buf, creation_factor,\n+\t\t\t      &diffopt);\n \n \tstrbuf_release(&range1);\n \tstrbuf_release(&range2);\ndiff --git a/range-diff.c b/range-diff.c\nindex 2d94200d3..71883a4b7 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -6,6 +6,7 @@\n #include \"hashmap.h\"\n #include \"xdiff-interface.h\"\n #include \"linear-assignment.h\"\n+#include \"diffcore.h\"\n \n struct patch_util {\n \t/* For the search for an exact match */\n@@ -258,7 +259,31 @@ static const char *short_oid(struct patch_util *util)\n \treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n }\n \n-static void output(struct string_list *a, struct string_list *b)\n+static struct diff_filespec *get_filespec(const char *name, const char *p)\n+{\n+\tstruct diff_filespec *spec = alloc_filespec(name);\n+\n+\tfill_filespec(spec, &null_oid, 0, 0644);\n+\tspec->data = (char *)p;\n+\tspec->size = strlen(p);\n+\tspec->should_munmap = 0;\n+\tspec->is_stdin = 1;\n+\n+\treturn spec;\n+}\n+\n+static void patch_diff(const char *a, const char *b,\n+\t\t\t      struct diff_options *diffopt)\n+{\n+\tdiff_queue(&diff_queued_diff,\n+\t\t   get_filespec(\"a\", a), get_filespec(\"b\", b));\n+\n+\tdiffcore_std(diffopt);\n+\tdiff_flush(diffopt);\n+}\n+\n+static void output(struct string_list *a, struct string_list *b,\n+\t\t   struct diff_options *diffopt)\n {\n \tint i = 0, j = 0;\n \n@@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)\n \t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n \t\t\t       b_util->matching + 1, short_oid(a_util),\n \t\t\t       j + 1, short_oid(b_util));\n+\t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n+\t\t\t\tpatch_diff(a->items[b_util->matching].string,\n+\t\t\t\t\t   b->items[j].string, diffopt);\n \t\t\ta_util->shown = 1;\n \t\t\tj++;\n \t\t}\n@@ -307,7 +335,7 @@ static void output(struct string_list *a, struct string_list *b)\n }\n \n int show_range_diff(const char *range1, const char *range2,\n-\t\t    int creation_factor)\n+\t\t    int creation_factor, struct diff_options *diffopt)\n {\n \tint res = 0;\n \n@@ -322,7 +350,7 @@ int show_range_diff(const char *range1, const char *range2,\n \tif (!res) {\n \t\tfind_exact_matches(&branch1, &branch2);\n \t\tget_correspondences(&branch1, &branch2, creation_factor);\n-\t\toutput(&branch1, &branch2);\n+\t\toutput(&branch1, &branch2, diffopt);\n \t}\n \n \tstring_list_clear(&branch1, 1);\ndiff --git a/range-diff.h b/range-diff.h\nindex dd30449c4..aea9d43f3 100644\n--- a/range-diff.h\n+++ b/range-diff.h\n@@ -1,7 +1,9 @@\n #ifndef BRANCH_DIFF_H\n #define BRANCH_DIFF_H\n \n+#include \"diff.h\"\n+\n int show_range_diff(const char *range1, const char *range2,\n-\t\t    int creation_factor);\n+\t\t    int creation_factor, struct diff_options *diffopt);\n \n #endif\n-- \ngitgitgadget\n\n"},{"id":"353266","messageId":"41ab875a39f86fe1f386f0f4fd4e52f95e03cc76.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 06/21] range-diff: right-trim commit messages","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:04:53Z","receivedAt":"2018-07-21T22:04:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen comparing commit messages, we need to keep in mind that they are\nindented by four spaces. That is, empty lines are no longer empty, but\nhave \"trailing whitespace\". When displaying them in color, that results\nin those nagging red lines.\n\nLet's just right-trim the lines in the commit message, it's not like\ntrailing white-space in the commit messages are important enough to care\nabout in `git range-diff`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 71883a4b7..1ecee2c09 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -85,6 +85,7 @@ static int read_patches(const char *range, struct string_list *list)\n \t\t\t\tstrbuf_addbuf(&buf, &line);\n \t\t\t\tstrbuf_addstr(&buf, \"\\n\\n\");\n \t\t\t} else if (starts_with(line.buf, \"    \")) {\n+\t\t\t\tstrbuf_rtrim(&line);\n \t\t\t\tstrbuf_addbuf(&buf, &line);\n \t\t\t\tstrbuf_addch(&buf, '\\n');\n \t\t\t}\n-- \ngitgitgadget\n\n"},{"id":"353267","messageId":"61b2ff2f7e28b685b50c1599fa80d462e1e236d9.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 08/21] range-diff: suppress the diff headers","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:04:56Z","receivedAt":"2018-07-21T22:04:59Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen showing the diff between corresponding patches of the two branch\nversions, we have to make up a fake filename to run the diff machinery.\n\nThat filename does not carry any meaningful information, hence tbdiff\nsuppresses it. So we should, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/range-diff.c | 1 +\n diff.c               | 5 ++++-\n diff.h               | 1 +\n 3 files changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 96e8bf841..10065315d 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -33,6 +33,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \n \tdiff_setup(&diffopt);\n \tdiffopt.output_format = DIFF_FORMAT_PATCH;\n+\tdiffopt.flags.suppress_diff_headers = 1;\n \tdiffopt.output_prefix = output_prefix_cb;\n \tstrbuf_addstr(&four_spaces, \"    \");\n \tdiffopt.output_prefix_data = &four_spaces;\ndiff --git a/diff.c b/diff.c\nindex dc53a19ba..a94a8214f 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -3190,13 +3190,16 @@ static void builtin_diff(const char *name_a,\n \t\tmemset(&xpp, 0, sizeof(xpp));\n \t\tmemset(&xecfg, 0, sizeof(xecfg));\n \t\tmemset(&ecbdata, 0, sizeof(ecbdata));\n+\t\tif (o->flags.suppress_diff_headers)\n+\t\t\tlbl[0] = NULL;\n \t\tecbdata.label_path = lbl;\n \t\tecbdata.color_diff = want_color(o->use_color);\n \t\tecbdata.ws_rule = whitespace_rule(name_b);\n \t\tif (ecbdata.ws_rule & WS_BLANK_AT_EOF)\n \t\t\tcheck_blank_at_eof(&mf1, &mf2, &ecbdata);\n \t\tecbdata.opt = o;\n-\t\tecbdata.header = header.len ? &header : NULL;\n+\t\tif (header.len && !o->flags.suppress_diff_headers)\n+\t\t\tecbdata.header = &header;\n \t\txpp.flags = o->xdl_opts;\n \t\txpp.anchors = o->anchors;\n \t\txpp.anchors_nr = o->anchors_nr;\ndiff --git a/diff.h b/diff.h\nindex dedac472c..928f48995 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -94,6 +94,7 @@ struct diff_flags {\n \tunsigned funccontext:1;\n \tunsigned default_follow_renames:1;\n \tunsigned stat_with_summary:1;\n+\tunsigned suppress_diff_headers:1;\n };\n \n static inline void diff_flags_or(struct diff_flags *a,\n-- \ngitgitgadget\n\n"},{"id":"353268","messageId":"9641ab5c0df984f5e7ea9c49debffffe2a929095.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 09/21] range-diff: adjust the output of the commit pairs","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:04:57Z","receivedAt":"2018-07-21T22:05:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis change brings `git range-diff` yet another step closer to\nfeature parity with tbdiff: it now shows the oneline, too, and indicates\nwith `=` when the commits have identical diffs.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 64 ++++++++++++++++++++++++++++++++++++++++++++--------\n 1 file changed, 55 insertions(+), 9 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 1ecee2c09..8329f52e7 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -7,6 +7,8 @@\n #include \"xdiff-interface.h\"\n #include \"linear-assignment.h\"\n #include \"diffcore.h\"\n+#include \"commit.h\"\n+#include \"pretty.h\"\n \n struct patch_util {\n \t/* For the search for an exact match */\n@@ -255,9 +257,54 @@ static void get_correspondences(struct string_list *a, struct string_list *b,\n \tfree(b2a);\n }\n \n-static const char *short_oid(struct patch_util *util)\n+static void output_pair_header(struct strbuf *buf,\n+\t\t\t       struct strbuf *dashes,\n+\t\t\t       struct patch_util *a_util,\n+\t\t\t       struct patch_util *b_util)\n {\n-\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n+\tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n+\tstruct commit *commit;\n+\n+\tif (!dashes->len)\n+\t\tstrbuf_addchars(dashes, '-',\n+\t\t\t\tstrlen(find_unique_abbrev(oid,\n+\t\t\t\t\t\t\t  DEFAULT_ABBREV)));\n+\n+\tstrbuf_reset(buf);\n+\tif (!a_util)\n+\t\tstrbuf_addf(buf, \"-:  %s \", dashes->buf);\n+\telse\n+\t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n+\t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n+\n+\tif (!a_util)\n+\t\tstrbuf_addch(buf, '>');\n+\telse if (!b_util)\n+\t\tstrbuf_addch(buf, '<');\n+\telse if (strcmp(a_util->patch, b_util->patch))\n+\t\tstrbuf_addch(buf, '!');\n+\telse\n+\t\tstrbuf_addch(buf, '=');\n+\n+\tif (!b_util)\n+\t\tstrbuf_addf(buf, \" -:  %s\", dashes->buf);\n+\telse\n+\t\tstrbuf_addf(buf, \" %d:  %s\", b_util->i + 1,\n+\t\t\t    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));\n+\n+\tcommit = lookup_commit_reference(oid);\n+\tif (commit) {\n+\t\tconst char *commit_buffer = get_commit_buffer(commit, NULL);\n+\t\tconst char *subject;\n+\n+\t\tfind_commit_subject(commit_buffer, &subject);\n+\t\tstrbuf_addch(buf, ' ');\n+\t\tformat_subject(buf, subject, \" \");\n+\t\tunuse_commit_buffer(commit, commit_buffer);\n+\t}\n+\tstrbuf_addch(buf, '\\n');\n+\n+\tfwrite(buf->buf, buf->len, 1, stdout);\n }\n \n static struct diff_filespec *get_filespec(const char *name, const char *p)\n@@ -286,6 +333,7 @@ static void patch_diff(const char *a, const char *b,\n static void output(struct string_list *a, struct string_list *b,\n \t\t   struct diff_options *diffopt)\n {\n+\tstruct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;\n \tint i = 0, j = 0;\n \n \t/*\n@@ -307,25 +355,21 @@ static void output(struct string_list *a, struct string_list *b,\n \n \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n \t\tif (i < a->nr && a_util->matching < 0) {\n-\t\t\tprintf(\"%d: %s < -: --------\\n\",\n-\t\t\t       i + 1, short_oid(a_util));\n+\t\t\toutput_pair_header(&buf, &dashes, a_util, NULL);\n \t\t\ti++;\n \t\t\tcontinue;\n \t\t}\n \n \t\t/* Show unmatched RHS commits. */\n \t\twhile (j < b->nr && b_util->matching < 0) {\n-\t\t\tprintf(\"-: -------- > %d: %s\\n\",\n-\t\t\t       j + 1, short_oid(b_util));\n+\t\t\toutput_pair_header(&buf, &dashes, NULL, b_util);\n \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n \n \t\t/* Show matching LHS/RHS pair. */\n \t\tif (j < b->nr) {\n \t\t\ta_util = a->items[b_util->matching].util;\n-\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n-\t\t\t       b_util->matching + 1, short_oid(a_util),\n-\t\t\t       j + 1, short_oid(b_util));\n+\t\t\toutput_pair_header(&buf, &dashes, a_util, b_util);\n \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n \t\t\t\t\t   b->items[j].string, diffopt);\n@@ -333,6 +377,8 @@ static void output(struct string_list *a, struct string_list *b,\n \t\t\tj++;\n \t\t}\n \t}\n+\tstrbuf_release(&buf);\n+\tstrbuf_release(&dashes);\n }\n \n int show_range_diff(const char *range1, const char *range2,\n-- \ngitgitgadget\n\n"},{"id":"353269","messageId":"0a52f887887cae039ee84b90cc05a6396242a744.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 10/21] range-diff: do not show \"function names\" in hunk headers","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:04:58Z","receivedAt":"2018-07-21T22:05:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWe are comparing complete, formatted commit messages with patches. There\nare no function names here, so stop looking for them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 8329f52e7..3fc3a4018 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -9,6 +9,7 @@\n #include \"diffcore.h\"\n #include \"commit.h\"\n #include \"pretty.h\"\n+#include \"userdiff.h\"\n \n struct patch_util {\n \t/* For the search for an exact match */\n@@ -307,6 +308,10 @@ static void output_pair_header(struct strbuf *buf,\n \tfwrite(buf->buf, buf->len, 1, stdout);\n }\n \n+static struct userdiff_driver no_func_name = {\n+\t.funcname = { \"$^\", 0 }\n+};\n+\n static struct diff_filespec *get_filespec(const char *name, const char *p)\n {\n \tstruct diff_filespec *spec = alloc_filespec(name);\n@@ -316,6 +321,7 @@ static struct diff_filespec *get_filespec(const char *name, const char *p)\n \tspec->size = strlen(p);\n \tspec->should_munmap = 0;\n \tspec->is_stdin = 1;\n+\tspec->driver = &no_func_name;\n \n \treturn spec;\n }\n-- \ngitgitgadget\n\n"},{"id":"353270","messageId":"a3dd9950982626f5cf5f64e0f76ed9e1f223f0ea.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 07/21] range-diff: indent the diffs just like tbdiff","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:04:54Z","receivedAt":"2018-07-21T22:05:03Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe main information in the `range-diff` view comes from the list of\nmatching and non-matching commits, the diffs are additional information.\nIndenting them helps with the reading flow.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/range-diff.c | 10 ++++++++++\n 1 file changed, 10 insertions(+)\n\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 093202117..96e8bf841 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -11,6 +11,11 @@ N_(\"git range-diff [<options>] <base> <old-tip> <new-tip>\"),\n NULL\n };\n \n+static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)\n+{\n+\treturn data;\n+}\n+\n int cmd_range_diff(int argc, const char **argv, const char *prefix)\n {\n \tint creation_factor = 60;\n@@ -21,12 +26,16 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \tint i, j, res = 0;\n+\tstruct strbuf four_spaces = STRBUF_INIT;\n \tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n \n \tgit_config(git_diff_ui_config, NULL);\n \n \tdiff_setup(&diffopt);\n \tdiffopt.output_format = DIFF_FORMAT_PATCH;\n+\tdiffopt.output_prefix = output_prefix_cb;\n+\tstrbuf_addstr(&four_spaces, \"    \");\n+\tdiffopt.output_prefix_data = &four_spaces;\n \n \targc = parse_options(argc, argv, NULL, options,\n \t\t\t     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n@@ -80,6 +89,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \n \tstrbuf_release(&range1);\n \tstrbuf_release(&range2);\n+\tstrbuf_release(&four_spaces);\n \n \treturn res;\n }\n-- \ngitgitgadget\n\n"},{"id":"353271","messageId":"2b8d09020fff0ac220c1878c65b47290c5245cb9.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 11/21] range-diff: add tests","fromName":"Thomas Rast via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:05:00Z","receivedAt":"2018-07-21T22:05:04Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"From: Thomas Rast <tr@thomasrast.ch>\n\nThese are essentially lifted from https://github.com/trast/tbdiff, with\nlight touch-ups to account for the command now being names `git\nrange-diff`.\n\nApart from renaming `tbdiff` to `range-diff`, only one test case needed\nto be adjusted: 11 - 'changed message'.\n\nThe underlying reason it had to be adjusted is that diff generation is\nsometimes ambiguous. In this case, a comment line and an empty line are\nadded, but it is ambiguous whether they were added after the existing\nempty line, or whether an empty line and the comment line are added\n*before* the existing empty line. And apparently xdiff picks a different\noption here than Python's difflib.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/.gitattributes       |   1 +\n t/t3206-range-diff.sh  | 145 ++++++++++\n t/t3206/history.export | 604 +++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 750 insertions(+)\n create mode 100755 t/t3206-range-diff.sh\n create mode 100644 t/t3206/history.export\n\ndiff --git a/t/.gitattributes b/t/.gitattributes\nindex 3bd959ae5..b17bf71b8 100644\n--- a/t/.gitattributes\n+++ b/t/.gitattributes\n@@ -1,6 +1,7 @@\n t[0-9][0-9][0-9][0-9]/* -whitespace\n /diff-lib/* eol=lf\n /t0110/url-* binary\n+/t3206/* eol=lf\n /t3900/*.txt eol=lf\n /t3901/*.txt eol=lf\n /t4034/*/* eol=lf\ndiff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh\nnew file mode 100755\nindex 000000000..2237c7f4a\n--- /dev/null\n+++ b/t/t3206-range-diff.sh\n@@ -0,0 +1,145 @@\n+#!/bin/sh\n+\n+test_description='range-diff tests'\n+\n+. ./test-lib.sh\n+\n+# Note that because of the range-diff's heuristics, test_commit does more\n+# harm than good.  We need some real history.\n+\n+test_expect_success 'setup' '\n+\tgit fast-import < \"$TEST_DIRECTORY\"/t3206/history.export\n+'\n+\n+test_expect_success 'simple A..B A..C (unmodified)' '\n+\tgit range-diff --no-color master..topic master..unmodified \\\n+\t\t>actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  35b9b25 s/5/A/\n+\t2:  fccce22 = 2:  de345ab s/4/A/\n+\t3:  147e64e = 3:  9af6654 s/11/B/\n+\t4:  a63e992 = 4:  2901f77 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'simple B...C (unmodified)' '\n+\tgit range-diff --no-color topic...unmodified >actual &&\n+\t# same \"expected\" as above\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'simple A B C (unmodified)' '\n+\tgit range-diff --no-color master topic unmodified >actual &&\n+\t# same \"expected\" as above\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'trivial reordering' '\n+\tgit range-diff --no-color master topic reordered >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  aca177a s/5/A/\n+\t3:  147e64e = 2:  14ad629 s/11/B/\n+\t4:  a63e992 = 3:  ee58208 s/12/B/\n+\t2:  fccce22 = 4:  307b27a s/4/A/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'removed a commit' '\n+\tgit range-diff --no-color master topic removed >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  7657159 s/5/A/\n+\t2:  fccce22 < -:  ------- s/4/A/\n+\t3:  147e64e = 2:  43d84d3 s/11/B/\n+\t4:  a63e992 = 3:  a740396 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'added a commit' '\n+\tgit range-diff --no-color master topic added >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  2716022 s/5/A/\n+\t2:  fccce22 = 2:  b62accd s/4/A/\n+\t-:  ------- > 3:  df46cfa s/6/A/\n+\t3:  147e64e = 4:  3e64548 s/11/B/\n+\t4:  a63e992 = 5:  12b4063 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'new base, A B C' '\n+\tgit range-diff --no-color master topic rebased >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  cc9c443 s/5/A/\n+\t2:  fccce22 = 2:  c5d9641 s/4/A/\n+\t3:  147e64e = 3:  28cc2b6 s/11/B/\n+\t4:  a63e992 = 4:  5628ab7 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'new base, B...C' '\n+\t# this syntax includes the commits from master!\n+\tgit range-diff --no-color topic...rebased >actual &&\n+\tcat >expected <<-EOF &&\n+\t-:  ------- > 1:  a31b12e unrelated\n+\t1:  4de457d = 2:  cc9c443 s/5/A/\n+\t2:  fccce22 = 3:  c5d9641 s/4/A/\n+\t3:  147e64e = 4:  28cc2b6 s/11/B/\n+\t4:  a63e992 = 5:  5628ab7 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'changed commit' '\n+\tgit range-diff --no-color topic...changed >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  a4b3333 s/5/A/\n+\t2:  fccce22 = 2:  f51d370 s/4/A/\n+\t3:  147e64e ! 3:  0559556 s/11/B/\n+\t    @@ -10,7 +10,7 @@\n+\t      9\n+\t      10\n+\t     -11\n+\t    -+B\n+\t    ++BB\n+\t      12\n+\t      13\n+\t      14\n+\t4:  a63e992 ! 4:  d966c5c s/12/B/\n+\t    @@ -8,7 +8,7 @@\n+\t     @@\n+\t      9\n+\t      10\n+\t    - B\n+\t    + BB\n+\t     -12\n+\t     +B\n+\t      13\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'changed message' '\n+\tgit range-diff --no-color topic...changed-message >actual &&\n+\tsed s/Z/\\ /g >expected <<-EOF &&\n+\t1:  4de457d = 1:  f686024 s/5/A/\n+\t2:  fccce22 ! 2:  4ab067d s/4/A/\n+\t    @@ -2,6 +2,8 @@\n+\t    Z\n+\t    Z    s/4/A/\n+\t    Z\n+\t    +    Also a silly comment here!\n+\t    +\n+\t    Zdiff --git a/file b/file\n+\t    Z--- a/file\n+\t    Z+++ b/file\n+\t3:  147e64e = 3:  b9cb956 s/11/B/\n+\t4:  a63e992 = 4:  8add5f1 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_done\ndiff --git a/t/t3206/history.export b/t/t3206/history.export\nnew file mode 100644\nindex 000000000..b8ffff094\n--- /dev/null\n+++ b/t/t3206/history.export\n@@ -0,0 +1,604 @@\n+blob\n+mark :1\n+data 51\n+1\n+2\n+3\n+4\n+5\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+reset refs/heads/removed\n+commit refs/heads/removed\n+mark :2\n+author Thomas Rast <trast@inf.ethz.ch> 1374424921 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374484724 +0200\n+data 8\n+initial\n+M 100644 :1 file\n+\n+blob\n+mark :3\n+data 51\n+1\n+2\n+3\n+4\n+A\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :4\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+blob\n+mark :5\n+data 51\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :6\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+data 7\n+s/4/A/\n+from :4\n+M 100644 :5 file\n+\n+blob\n+mark :7\n+data 50\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :8\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+data 8\n+s/11/B/\n+from :6\n+M 100644 :7 file\n+\n+blob\n+mark :9\n+data 49\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :10\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+data 8\n+s/12/B/\n+from :8\n+M 100644 :9 file\n+\n+blob\n+mark :11\n+data 10\n+unrelated\n+\n+commit refs/heads/master\n+mark :12\n+author Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200\n+data 10\n+unrelated\n+from :2\n+M 100644 :11 otherfile\n+\n+commit refs/heads/rebased\n+mark :13\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485137 +0200\n+data 7\n+s/5/A/\n+from :12\n+M 100644 :3 file\n+\n+commit refs/heads/rebased\n+mark :14\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n+data 7\n+s/4/A/\n+from :13\n+M 100644 :5 file\n+\n+commit refs/heads/rebased\n+mark :15\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n+data 8\n+s/11/B/\n+from :14\n+M 100644 :7 file\n+\n+commit refs/heads/rebased\n+mark :16\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n+data 8\n+s/12/B/\n+from :15\n+M 100644 :9 file\n+\n+commit refs/heads/added\n+mark :17\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/added\n+mark :18\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 7\n+s/4/A/\n+from :17\n+M 100644 :5 file\n+\n+blob\n+mark :19\n+data 51\n+1\n+2\n+3\n+A\n+A\n+A\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/added\n+mark :20\n+author Thomas Rast <trast@inf.ethz.ch> 1374485186 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 7\n+s/6/A/\n+from :18\n+M 100644 :19 file\n+\n+blob\n+mark :21\n+data 50\n+1\n+2\n+3\n+A\n+A\n+A\n+7\n+8\n+9\n+10\n+B\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/added\n+mark :22\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 8\n+s/11/B/\n+from :20\n+M 100644 :21 file\n+\n+blob\n+mark :23\n+data 49\n+1\n+2\n+3\n+A\n+A\n+A\n+7\n+8\n+9\n+10\n+B\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/added\n+mark :24\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 8\n+s/12/B/\n+from :22\n+M 100644 :23 file\n+\n+commit refs/heads/reordered\n+mark :25\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+blob\n+mark :26\n+data 50\n+1\n+2\n+3\n+4\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/reordered\n+mark :27\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 8\n+s/11/B/\n+from :25\n+M 100644 :26 file\n+\n+blob\n+mark :28\n+data 49\n+1\n+2\n+3\n+4\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/reordered\n+mark :29\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 8\n+s/12/B/\n+from :27\n+M 100644 :28 file\n+\n+commit refs/heads/reordered\n+mark :30\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 7\n+s/4/A/\n+from :29\n+M 100644 :9 file\n+\n+commit refs/heads/changed\n+mark :31\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/changed\n+mark :32\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 7\n+s/4/A/\n+from :31\n+M 100644 :5 file\n+\n+blob\n+mark :33\n+data 51\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+BB\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/changed\n+mark :34\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 8\n+s/11/B/\n+from :32\n+M 100644 :33 file\n+\n+blob\n+mark :35\n+data 50\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+BB\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/changed\n+mark :36\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 8\n+s/12/B/\n+from :34\n+M 100644 :35 file\n+\n+commit refs/heads/changed-message\n+mark :37\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/changed-message\n+mark :38\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200\n+data 35\n+s/4/A/\n+\n+Also a silly comment here!\n+from :37\n+M 100644 :5 file\n+\n+commit refs/heads/changed-message\n+mark :39\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200\n+data 8\n+s/11/B/\n+from :38\n+M 100644 :7 file\n+\n+commit refs/heads/changed-message\n+mark :40\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200\n+data 8\n+s/12/B/\n+from :39\n+M 100644 :9 file\n+\n+commit refs/heads/unmodified\n+mark :41\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/unmodified\n+mark :42\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200\n+data 7\n+s/4/A/\n+from :41\n+M 100644 :5 file\n+\n+commit refs/heads/unmodified\n+mark :43\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200\n+data 8\n+s/11/B/\n+from :42\n+M 100644 :7 file\n+\n+commit refs/heads/unmodified\n+mark :44\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200\n+data 8\n+s/12/B/\n+from :43\n+M 100644 :9 file\n+\n+commit refs/heads/removed\n+mark :45\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/removed\n+mark :46\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n+data 8\n+s/11/B/\n+from :45\n+M 100644 :26 file\n+\n+commit refs/heads/removed\n+mark :47\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n+data 8\n+s/12/B/\n+from :46\n+M 100644 :28 file\n+\n+reset refs/heads/removed\n+from :47\n+\n-- \ngitgitgadget\n\n"},{"id":"353272","messageId":"9ccb9516ae1fcded3e9000b795a780b8b189cb33.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 13/21] color: add the meta color GIT_COLOR_REVERSE","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:05:02Z","receivedAt":"2018-07-21T22:05:05Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis \"color\" simply reverts background and foreground. It will be used\nin the upcoming \"dual color\" mode of `git range-diff`, where we will\nreverse colors for the -/+ markers and the fragment headers of the\n\"outer\" diff.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n color.h | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/color.h b/color.h\nindex 5b744e1bc..33e786342 100644\n--- a/color.h\n+++ b/color.h\n@@ -44,6 +44,7 @@ struct strbuf;\n #define GIT_COLOR_BG_CYAN\t\"\\033[46m\"\n #define GIT_COLOR_FAINT\t\t\"\\033[2m\"\n #define GIT_COLOR_FAINT_ITALIC\t\"\\033[2;3m\"\n+#define GIT_COLOR_REVERSE\t\"\\033[7m\"\n \n /* A special value meaning \"no color selected\" */\n #define GIT_COLOR_NIL \"NIL\"\n-- \ngitgitgadget\n\n"},{"id":"353274","messageId":"fb83ce71af8aa6266c082dad0604e5cb2b9f61d8.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 12/21] range-diff: use color for the commit pairs","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:05:01Z","receivedAt":"2018-07-21T22:05:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nArguably the most important part of `git range-diff`'s output is the\nlist of commits in the two branches, together with their relationships.\n\nFor that reason, tbdiff introduced color-coding that is pretty\nintuitive, especially for unchanged patches (all dim yellow, like the\nfirst line in `git show`'s output) vs modified patches (old commit is\nred, new commit is green). Let's imitate that color scheme.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 51 ++++++++++++++++++++++++++++++++++++++-------------\n 1 file changed, 38 insertions(+), 13 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 3fc3a4018..ab1e71e10 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -258,34 +258,53 @@ static void get_correspondences(struct string_list *a, struct string_list *b,\n \tfree(b2a);\n }\n \n-static void output_pair_header(struct strbuf *buf,\n+static void output_pair_header(struct diff_options *diffopt,\n+\t\t\t       struct strbuf *buf,\n \t\t\t       struct strbuf *dashes,\n \t\t\t       struct patch_util *a_util,\n \t\t\t       struct patch_util *b_util)\n {\n \tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n \tstruct commit *commit;\n+\tchar status;\n+\tconst char *color_reset = diff_get_color_opt(diffopt, DIFF_RESET);\n+\tconst char *color_old = diff_get_color_opt(diffopt, DIFF_FILE_OLD);\n+\tconst char *color_new = diff_get_color_opt(diffopt, DIFF_FILE_NEW);\n+\tconst char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT);\n+\tconst char *color;\n \n \tif (!dashes->len)\n \t\tstrbuf_addchars(dashes, '-',\n \t\t\t\tstrlen(find_unique_abbrev(oid,\n \t\t\t\t\t\t\t  DEFAULT_ABBREV)));\n \n+\tif (!b_util) {\n+\t\tcolor = color_old;\n+\t\tstatus = '<';\n+\t} else if (!a_util) {\n+\t\tcolor = color_new;\n+\t\tstatus = '>';\n+\t} else if (strcmp(a_util->patch, b_util->patch)) {\n+\t\tcolor = color_commit;\n+\t\tstatus = '!';\n+\t} else {\n+\t\tcolor = color_commit;\n+\t\tstatus = '=';\n+\t}\n+\n \tstrbuf_reset(buf);\n+\tstrbuf_addstr(buf, status == '!' ? color_old : color);\n \tif (!a_util)\n \t\tstrbuf_addf(buf, \"-:  %s \", dashes->buf);\n \telse\n \t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n \t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n \n-\tif (!a_util)\n-\t\tstrbuf_addch(buf, '>');\n-\telse if (!b_util)\n-\t\tstrbuf_addch(buf, '<');\n-\telse if (strcmp(a_util->patch, b_util->patch))\n-\t\tstrbuf_addch(buf, '!');\n-\telse\n-\t\tstrbuf_addch(buf, '=');\n+\tif (status == '!')\n+\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color);\n+\tstrbuf_addch(buf, status);\n+\tif (status == '!')\n+\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color_new);\n \n \tif (!b_util)\n \t\tstrbuf_addf(buf, \" -:  %s\", dashes->buf);\n@@ -298,12 +317,15 @@ static void output_pair_header(struct strbuf *buf,\n \t\tconst char *commit_buffer = get_commit_buffer(commit, NULL);\n \t\tconst char *subject;\n \n+\t\tif (status == '!')\n+\t\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color);\n+\n \t\tfind_commit_subject(commit_buffer, &subject);\n \t\tstrbuf_addch(buf, ' ');\n \t\tformat_subject(buf, subject, \" \");\n \t\tunuse_commit_buffer(commit, commit_buffer);\n \t}\n-\tstrbuf_addch(buf, '\\n');\n+\tstrbuf_addf(buf, \"%s\\n\", color_reset);\n \n \tfwrite(buf->buf, buf->len, 1, stdout);\n }\n@@ -361,21 +383,24 @@ static void output(struct string_list *a, struct string_list *b,\n \n \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n \t\tif (i < a->nr && a_util->matching < 0) {\n-\t\t\toutput_pair_header(&buf, &dashes, a_util, NULL);\n+\t\t\toutput_pair_header(diffopt,\n+\t\t\t\t\t   &buf, &dashes, a_util, NULL);\n \t\t\ti++;\n \t\t\tcontinue;\n \t\t}\n \n \t\t/* Show unmatched RHS commits. */\n \t\twhile (j < b->nr && b_util->matching < 0) {\n-\t\t\toutput_pair_header(&buf, &dashes, NULL, b_util);\n+\t\t\toutput_pair_header(diffopt,\n+\t\t\t\t\t   &buf, &dashes, NULL, b_util);\n \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n \n \t\t/* Show matching LHS/RHS pair. */\n \t\tif (j < b->nr) {\n \t\t\ta_util = a->items[b_util->matching].util;\n-\t\t\toutput_pair_header(&buf, &dashes, a_util, b_util);\n+\t\t\toutput_pair_header(diffopt,\n+\t\t\t\t\t   &buf, &dashes, a_util, b_util);\n \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n \t\t\t\t\t   b->items[j].string, diffopt);\n-- \ngitgitgadget\n\n"},{"id":"353273","messageId":"9de5bd2299eedbc78494cadc9dd8bda59430b2df.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 14/21] diff: add an internal option to dual-color diffs of diffs","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:05:04Z","receivedAt":"2018-07-21T22:05:07Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen diffing diffs, it can be quite daunting to figure out what the heck\nis going on, as there are nested +/- signs.\n\nLet's make this easier by adding a flag in diff_options that allows\ncolor-coding the outer diff sign with inverted colors, so that the\npreimage and postimage is colored like the diff it is.\n\nOf course, this really only makes sense when the preimage and postimage\n*are* diffs. So let's not expose this flag via a command-line option for\nnow.\n\nThis is a feature that was invented by git-tbdiff, and it will be used\nby `git range-diff` in the next commit, by offering it via a new option:\n`--dual-color`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n diff.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++-----------\n diff.h |  1 +\n 2 files changed, 69 insertions(+), 15 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex a94a8214f..e163bc8a3 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -563,14 +563,18 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,\n \tecbdata->blank_at_eof_in_postimage = (at - l2) + 1;\n }\n \n-static void emit_line_0(struct diff_options *o, const char *set, const char *reset,\n+static void emit_line_0(struct diff_options *o,\n+\t\t\tconst char *set, unsigned reverse, const char *reset,\n \t\t\tint first, const char *line, int len)\n {\n \tint has_trailing_newline, has_trailing_carriage_return;\n \tint nofirst;\n \tFILE *file = o->file;\n \n-\tfputs(diff_line_prefix(o), file);\n+\tif (first)\n+\t\tfputs(diff_line_prefix(o), file);\n+\telse if (!len)\n+\t\treturn;\n \n \tif (len == 0) {\n \t\thas_trailing_newline = (first == '\\n');\n@@ -588,8 +592,10 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res\n \t}\n \n \tif (len || !nofirst) {\n+\t\tif (reverse && want_color(o->use_color))\n+\t\t\tfputs(GIT_COLOR_REVERSE, file);\n \t\tfputs(set, file);\n-\t\tif (!nofirst)\n+\t\tif (first && !nofirst)\n \t\t\tfputc(first, file);\n \t\tfwrite(line, len, 1, file);\n \t\tfputs(reset, file);\n@@ -603,7 +609,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res\n static void emit_line(struct diff_options *o, const char *set, const char *reset,\n \t\t      const char *line, int len)\n {\n-\temit_line_0(o, set, reset, line[0], line+1, len-1);\n+\temit_line_0(o, set, 0, reset, line[0], line+1, len-1);\n }\n \n enum diff_symbol {\n@@ -963,7 +969,8 @@ static void dim_moved_lines(struct diff_options *o)\n \n static void emit_line_ws_markup(struct diff_options *o,\n \t\t\t\tconst char *set, const char *reset,\n-\t\t\t\tconst char *line, int len, char sign,\n+\t\t\t\tconst char *line, int len,\n+\t\t\t\tconst char *set_sign, char sign,\n \t\t\t\tunsigned ws_rule, int blank_at_eof)\n {\n \tconst char *ws = NULL;\n@@ -974,14 +981,20 @@ static void emit_line_ws_markup(struct diff_options *o,\n \t\t\tws = NULL;\n \t}\n \n-\tif (!ws)\n-\t\temit_line_0(o, set, reset, sign, line, len);\n-\telse if (blank_at_eof)\n+\tif (!ws && !set_sign)\n+\t\temit_line_0(o, set, 0, reset, sign, line, len);\n+\telse if (!ws) {\n+\t\t/* Emit just the prefix, then the rest. */\n+\t\temit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n+\t\t\t    sign, \"\", 0);\n+\t\temit_line_0(o, set, 0, reset, 0, line, len);\n+\t} else if (blank_at_eof)\n \t\t/* Blank line at EOF - paint '+' as well */\n-\t\temit_line_0(o, ws, reset, sign, line, len);\n+\t\temit_line_0(o, ws, 0, reset, sign, line, len);\n \telse {\n \t\t/* Emit just the prefix, then the rest. */\n-\t\temit_line_0(o, set, reset, sign, \"\", 0);\n+\t\temit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n+\t\t\t    sign, \"\", 0);\n \t\tws_check_emit(line, len, ws_rule,\n \t\t\t      o->file, set, reset, ws);\n \t}\n@@ -991,7 +1004,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\t\t\t struct emitted_diff_symbol *eds)\n {\n \tstatic const char *nneof = \" No newline at end of file\\n\";\n-\tconst char *context, *reset, *set, *meta, *fraginfo;\n+\tconst char *context, *reset, *set, *set_sign, *meta, *fraginfo;\n \tstruct strbuf sb = STRBUF_INIT;\n \n \tenum diff_symbol s = eds->s;\n@@ -1004,7 +1017,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\tcontext = diff_get_color_opt(o, DIFF_CONTEXT);\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n \t\tputc('\\n', o->file);\n-\t\temit_line_0(o, context, reset, '\\\\',\n+\t\temit_line_0(o, context, 0, reset, '\\\\',\n \t\t\t    nneof, strlen(nneof));\n \t\tbreak;\n \tcase DIFF_SYMBOL_SUBMODULE_HEADER:\n@@ -1031,7 +1044,18 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \tcase DIFF_SYMBOL_CONTEXT:\n \t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n-\t\temit_line_ws_markup(o, set, reset, line, len, ' ',\n+\t\tset_sign = NULL;\n+\t\tif (o->flags.dual_color_diffed_diffs) {\n+\t\t\tchar c = !len ? 0 : line[0];\n+\n+\t\t\tif (c == '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n+\t\t\telse if (c == '@')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n+\t\t\telse if (c == '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n+\t\t}\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',\n \t\t\t\t    flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);\n \t\tbreak;\n \tcase DIFF_SYMBOL_PLUS:\n@@ -1058,7 +1082,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n \t\t}\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n-\t\temit_line_ws_markup(o, set, reset, line, len, '+',\n+\t\tif (!o->flags.dual_color_diffed_diffs)\n+\t\t\tset_sign = NULL;\n+\t\telse {\n+\t\t\tchar c = !len ? 0 : line[0];\n+\n+\t\t\tset_sign = set;\n+\t\t\tif (c == '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n+\t\t\telse if (c == '@')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n+\t\t\telse if (c != '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t}\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK,\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);\n \t\tbreak;\n@@ -1086,7 +1123,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n \t\t}\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n-\t\temit_line_ws_markup(o, set, reset, line, len, '-',\n+\t\tif (!o->flags.dual_color_diffed_diffs)\n+\t\t\tset_sign = NULL;\n+\t\telse {\n+\t\t\tchar c = !len ? 0 : line[0];\n+\n+\t\t\tset_sign = set;\n+\t\t\tif (c == '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n+\t\t\telse if (c == '@')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n+\t\t\telse if (c != '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t}\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '-',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);\n \t\tbreak;\n \tcase DIFF_SYMBOL_WORDS_PORCELAIN:\n@@ -1277,6 +1327,7 @@ static void emit_hunk_header(struct emit_callback *ecbdata,\n \tconst char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);\n \tconst char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);\n \tconst char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);\n+\tconst char *reverse = ecbdata->color_diff ? GIT_COLOR_REVERSE : \"\";\n \tstatic const char atat[2] = { '@', '@' };\n \tconst char *cp, *ep;\n \tstruct strbuf msgbuf = STRBUF_INIT;\n@@ -1297,6 +1348,8 @@ static void emit_hunk_header(struct emit_callback *ecbdata,\n \tep += 2; /* skip over @@ */\n \n \t/* The hunk header in fraginfo color */\n+\tif (ecbdata->opt->flags.dual_color_diffed_diffs)\n+\t\tstrbuf_addstr(&msgbuf, reverse);\n \tstrbuf_addstr(&msgbuf, frag);\n \tstrbuf_add(&msgbuf, line, ep - line);\n \tstrbuf_addstr(&msgbuf, reset);\ndiff --git a/diff.h b/diff.h\nindex 928f48995..79beb6eea 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -95,6 +95,7 @@ struct diff_flags {\n \tunsigned default_follow_renames:1;\n \tunsigned stat_with_summary:1;\n \tunsigned suppress_diff_headers:1;\n+\tunsigned dual_color_diffed_diffs:1;\n };\n \n static inline void diff_flags_or(struct diff_flags *a,\n-- \ngitgitgadget\n\n"},{"id":"353275","messageId":"21b2f9e4b90a7e25ced8ce561a22aae1a419401a.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 15/21] range-diff: offer to dual-color the diffs","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:05:05Z","receivedAt":"2018-07-21T22:05:08Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen showing what changed between old and new commits, we show a diff of\nthe patches. This diff is a diff between diffs, therefore there are\nnested +/- signs, and it can be relatively hard to understand what is\ngoing on.\n\nWith the --dual-color option, the preimage and the postimage are colored\nlike the diffs they are, and the *outer* +/- sign is inverted for\nclarity.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/range-diff.c | 8 ++++++++\n 1 file changed, 8 insertions(+)\n\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 10065315d..c25d88317 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -20,9 +20,12 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n {\n \tint creation_factor = 60;\n \tstruct diff_options diffopt = { NULL };\n+\tint dual_color = 0;\n \tstruct option options[] = {\n \t\tOPT_INTEGER(0, \"creation-factor\", &creation_factor,\n \t\t\t    N_(\"Percentage by which creation is weighted\")),\n+\t\tOPT_BOOL(0, \"dual-color\", &dual_color,\n+\t\t\t    N_(\"color both diff and diff-between-diffs\")),\n \t\tOPT_END()\n \t};\n \tint i, j, res = 0;\n@@ -52,6 +55,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \targc = j;\n \tdiff_setup_done(&diffopt);\n \n+\tif (dual_color) {\n+\t\tdiffopt.use_color = 1;\n+\t\tdiffopt.flags.dual_color_diffed_diffs = 1;\n+\t}\n+\n \tif (argc == 2) {\n \t\tif (!strstr(argv[0], \"..\"))\n \t\t\tdie(_(\"no .. in range: '%s'\"), argv[0]);\n-- \ngitgitgadget\n\n"},{"id":"353276","messageId":"f4252f2b2198cf13d5b0a21c54098e2a1d8158dd.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 16/21] range-diff --dual-color: fix bogus white-space warning","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:05:07Z","receivedAt":"2018-07-21T22:05:10Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen displaying a diff of diffs, it is possible that there is an outer\n`+` before a context line. That happens when the context changed between\nold and new commit. When that context line starts with a tab (after the\nspace that marks it as context line), our diff machinery spits out a\nwhite-space error (space before tab), but in this case, that is\nincorrect.\n\nFix this by adding a specific whitespace flag that simply ignores the\nfirst space in the output.\n\nOf course, this flag is *really* specific to the \"diff of diffs\" use\ncase. The original idea was to simply skip the space from the output,\nbut that workaround was rejected by the Git maintainer as causing\nheadaches.\n\nNote: as the original code did not leave any space in the bit mask\nbefore the WSEH_* bits, the diff of this commit looks unnecessarily\ninvolved: the diff is dominated by making room for one more bit to be\nused by the whitespace rules.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n cache.h |  3 ++-\n diff.c  | 15 ++++++++-------\n diff.h  |  6 +++---\n ws.c    | 11 ++++++++++-\n 4 files changed, 23 insertions(+), 12 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 8b447652a..8abfbeb73 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1681,11 +1681,12 @@ void shift_tree_by(const struct object_id *, const struct object_id *, struct ob\n #define WS_CR_AT_EOL           01000\n #define WS_BLANK_AT_EOF        02000\n #define WS_TAB_IN_INDENT       04000\n+#define WS_IGNORE_FIRST_SPACE 010000\n #define WS_TRAILING_SPACE      (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF)\n #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|8)\n #define WS_TAB_WIDTH_MASK        077\n /* All WS_* -- when extended, adapt diff.c emit_symbol */\n-#define WS_RULE_MASK           07777\n+#define WS_RULE_MASK           017777\n extern unsigned whitespace_rule_cfg;\n extern unsigned whitespace_rule(const char *);\n extern unsigned parse_whitespace_rule(const char *);\ndiff --git a/diff.c b/diff.c\nindex e163bc8a3..03ed235c7 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -650,14 +650,14 @@ enum diff_symbol {\n };\n /*\n  * Flags for content lines:\n- * 0..12 are whitespace rules\n- * 13-15 are WSEH_NEW | WSEH_OLD | WSEH_CONTEXT\n- * 16 is marking if the line is blank at EOF\n+ * 0..14 are whitespace rules\n+ * 14-16 are WSEH_NEW | WSEH_OLD | WSEH_CONTEXT\n+ * 17 is marking if the line is blank at EOF\n  */\n-#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF\t(1<<16)\n-#define DIFF_SYMBOL_MOVED_LINE\t\t\t(1<<17)\n-#define DIFF_SYMBOL_MOVED_LINE_ALT\t\t(1<<18)\n-#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING\t(1<<19)\n+#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF\t(1<<17)\n+#define DIFF_SYMBOL_MOVED_LINE\t\t\t(1<<18)\n+#define DIFF_SYMBOL_MOVED_LINE_ALT\t\t(1<<19)\n+#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING\t(1<<20)\n #define DIFF_SYMBOL_CONTENT_WS_MASK (WSEH_NEW | WSEH_OLD | WSEH_CONTEXT | WS_RULE_MASK)\n \n /*\n@@ -1094,6 +1094,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n \t\t\telse if (c != '+')\n \t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t\tflags |= WS_IGNORE_FIRST_SPACE;\n \t\t}\n \t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK,\ndiff --git a/diff.h b/diff.h\nindex 79beb6eea..892416a14 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -160,9 +160,9 @@ struct diff_options {\n \tint abbrev;\n \tint ita_invisible_in_index;\n /* white-space error highlighting */\n-#define WSEH_NEW (1<<12)\n-#define WSEH_CONTEXT (1<<13)\n-#define WSEH_OLD (1<<14)\n+#define WSEH_NEW (1<<13)\n+#define WSEH_CONTEXT (1<<14)\n+#define WSEH_OLD (1<<15)\n \tunsigned ws_error_highlight;\n \tconst char *prefix;\n \tint prefix_length;\ndiff --git a/ws.c b/ws.c\nindex a07caedd5..e02365a6a 100644\n--- a/ws.c\n+++ b/ws.c\n@@ -20,6 +20,7 @@ static struct whitespace_rule {\n \t{ \"blank-at-eol\", WS_BLANK_AT_EOL, 0 },\n \t{ \"blank-at-eof\", WS_BLANK_AT_EOF, 0 },\n \t{ \"tab-in-indent\", WS_TAB_IN_INDENT, 0, 1 },\n+\t{ \"ignore-first-space\", WS_IGNORE_FIRST_SPACE, 0, 1 },\n };\n \n unsigned parse_whitespace_rule(const char *string)\n@@ -177,8 +178,16 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,\n \tif (trailing_whitespace == -1)\n \t\ttrailing_whitespace = len;\n \n+\tif ((ws_rule & WS_IGNORE_FIRST_SPACE) && len && line[0] == ' ') {\n+\t\tif (stream)\n+\t\t\tfwrite(line, 1, 1, stream);\n+\t\twritten++;\n+\t\tif (!trailing_whitespace)\n+\t\t\ttrailing_whitespace++;\n+\t}\n+\n \t/* Check indentation */\n-\tfor (i = 0; i < trailing_whitespace; i++) {\n+\tfor (i = written; i < trailing_whitespace; i++) {\n \t\tif (line[i] == ' ')\n \t\t\tcontinue;\n \t\tif (line[i] != '\\t')\n-- \ngitgitgadget\n\n"},{"id":"353277","messageId":"9b3632324f93afdab7273df7a7dc119e14a261a1.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 18/21] completion: support `git range-diff`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:05:09Z","receivedAt":"2018-07-21T22:05:13Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nTab completion of `git range-diff` is very convenient, especially\ngiven that the revision arguments to specify the commit ranges to\ncompare are typically more complex than, say, what is normally passed\nto `git log`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n contrib/completion/git-completion.bash | 14 ++++++++++++++\n 1 file changed, 14 insertions(+)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 94c95516e..402490673 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1976,6 +1976,20 @@ _git_push ()\n \t__git_complete_remote_or_refspec\n }\n \n+_git_range_diff ()\n+{\n+  case \"$cur\" in\n+  --*)\n+          __gitcomp \"\n+\t  \t--creation-factor= --dual-color\n+                  $__git_diff_common_options\n+                  \"\n+          return\n+          ;;\n+  esac\n+  __git_complete_revlist\n+}\n+\n _git_rebase ()\n {\n \t__git_find_repo_path\n-- \ngitgitgadget\n\n"},{"id":"353278","messageId":"9e09c6be66e960db496b1c9a30eb5040242ab764.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 17/21] range-diff: populate the man page","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:05:08Z","receivedAt":"2018-07-21T22:05:14Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe bulk of this patch consists of a heavily butchered version of\ntbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from\nhttps://github.com/trast/tbdiff.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-range-diff.txt | 229 +++++++++++++++++++++++++++++++\n 1 file changed, 229 insertions(+)\n\ndiff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\nindex de0ca5df4..f1a6737f8 100644\n--- a/Documentation/git-range-diff.txt\n+++ b/Documentation/git-range-diff.txt\n@@ -5,6 +5,235 @@ NAME\n ----\n git-range-diff - Compare two commit ranges (e.g. two versions of a branch)\n \n+SYNOPSIS\n+--------\n+[verse]\n+'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]\n+\t[--dual-color] [--creation-factor=<factor>]\n+\t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n+\n+DESCRIPTION\n+-----------\n+\n+This command shows the differences between two versions of a patch\n+series, or more generally, two commit ranges (ignoring merge commits).\n+\n+To that end, it first finds pairs of commits from both commit ranges\n+that correspond with each other. Two commits are said to correspond when\n+the diff between their patches (i.e. the author information, the commit\n+message and the commit diff) is reasonably small compared to the\n+patches' size. See ``Algorithm`` below for details.\n+\n+Finally, the list of matching commits is shown in the order of the\n+second commit range, with unmatched commits being inserted just after\n+all of their ancestors have been shown.\n+\n+\n+OPTIONS\n+-------\n+--dual-color::\n+\tWhen the commit diffs differ, recreate the original diffs'\n+\tcoloring, and add outer -/+ diff markers with the *background*\n+\tbeing red/green to make it easier to see e.g. when there was a\n+\tchange in what exact lines were added.\n+\n+--creation-factor=<percent>::\n+\tSet the creation/deletion cost fudge factor to `<percent>`.\n+\tDefaults to 60. Try a larger value if `git range-diff` erroneously\n+\tconsiders a large change a total rewrite (deletion of one commit\n+\tand addition of another), and a smaller one in the reverse case.\n+\tSee the ``Algorithm`` section below for an explanation why this is\n+\tneeded.\n+\n+<range1> <range2>::\n+\tCompare the commits specified by the two ranges, where\n+\t`<range1>` is considered an older version of `<range2>`.\n+\n+<rev1>...<rev2>::\n+\tEquivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.\n+\n+<base> <rev1> <rev2>::\n+\tEquivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.\n+\tNote that `<base>` does not need to be the exact branch point\n+\tof the branches. Example: after rebasing a branch `my-topic`,\n+\t`git range-diff my-topic@{u} my-topic@{1} my-topic` would\n+\tshow the differences introduced by the rebase.\n+\n+`git range-diff` also accepts the regular diff options (see\n+linkgit:git-diff[1]), most notably the `--color=[<when>]` and\n+`--no-color` options. These options are used when generating the \"diff\n+between patches\", i.e. to compare the author, commit message and diff of\n+corresponding old/new commits. There is currently no means to tweak the\n+diff options passed to `git log` when generating those patches.\n+\n+\n+CONFIGURATION\n+-------------\n+This command uses the `diff.color.*` and `pager.range-diff` settings\n+(the latter is on by default).\n+See linkgit:git-config[1].\n+\n+\n+EXAMPLES\n+--------\n+\n+When a rebase required merge conflicts to be resolved, compare the changes\n+introduced by the rebase directly afterwards using:\n+\n+------------\n+$ git range-diff @{u} @{1} @\n+------------\n+\n+\n+A typical output of `git range-diff` would look like this:\n+\n+------------\n+-:  ------- > 1:  0ddba11 Prepare for the inevitable!\n+1:  c0debee = 2:  cab005e Add a helpful message at the start\n+2:  f00dbal ! 3:  decafe1 Describe a bug\n+    @@ -1,3 +1,3 @@\n+     Author: A U Thor <author@example.com>\n+\n+    -TODO: Describe a bug\n+    +Describe a bug\n+    @@ -324,5 +324,6\n+      This is expected.\n+\n+    -+What is unexpected is that it will also crash.\n+    ++Unexpectedly, it also crashes. This is a bug, and the jury is\n+    ++still out there how to fix it best. See ticket #314 for details.\n+\n+      Contact\n+3:  bedead < -:  ------- TO-UNDO\n+------------\n+\n+In this example, there are 3 old and 3 new commits, where the developer\n+removed the 3rd, added a new one before the first two, and modified the\n+commit message of the 2nd commit as well its diff.\n+\n+When the output goes to a terminal, it is color-coded by default, just\n+like regular `git diff`'s output. In addition, the first line (adding a\n+commit) is green, the last line (deleting a commit) is red, the second\n+line (with a perfect match) is yellow like the commit header of `git\n+show`'s output, and the third line colors the old commit red, the new\n+one green and the rest like `git show`'s commit header.\n+\n+The color-coded diff is actually a bit hard to read, though, as it\n+colors the entire lines red or green. The line that added \"What is\n+unexpected\" in the old commit, for example, is completely red, even if\n+the intent of the old commit was to add something.\n+\n+To help with that, use the `--dual-color` mode. In this mode, the diff\n+of diffs will retain the original diff colors, and prefix the lines with\n+-/+ markers that have their *background* red or green, to make it more\n+obvious that they describe how the diff itself changed.\n+\n+\n+Algorithm\n+---------\n+\n+The general idea is this: we generate a cost matrix between the commits\n+in both commit ranges, then solve the least-cost assignment.\n+\n+The cost matrix is populated thusly: for each pair of commits, both\n+diffs are generated and the \"diff of diffs\" is generated, with 3 context\n+lines, then the number of lines in that diff is used as cost.\n+\n+To avoid false positives (e.g. when a patch has been removed, and an\n+unrelated patch has been added between two iterations of the same patch\n+series), the cost matrix is extended to allow for that, by adding\n+fixed-cost entries for wholesale deletes/adds.\n+\n+Example: Let commits `1--2` be the first iteration of a patch series and\n+`A--C` the second iteration. Let's assume that `A` is a cherry-pick of\n+`2,` and `C` is a cherry-pick of `1` but with a small modification (say,\n+a fixed typo). Visualize the commits as a bipartite graph:\n+\n+------------\n+    1            A\n+\n+    2            B\n+\n+\t\t C\n+------------\n+\n+We are looking for a \"best\" explanation of the new series in terms of\n+the old one. We can represent an \"explanation\" as an edge in the graph:\n+\n+\n+------------\n+    1            A\n+\t       /\n+    2 --------'  B\n+\n+\t\t C\n+------------\n+\n+This explanation comes for \"free\" because there was no change. Similarly\n+`C` could be explained using `1`, but that comes at some cost c>0\n+because of the modification:\n+\n+------------\n+    1 ----.      A\n+\t  |    /\n+    2 ----+---'  B\n+\t  |\n+\t  `----- C\n+\t  c>0\n+------------\n+\n+In mathematical terms, what we are looking for is some sort of a minimum\n+cost bipartite matching; `1` is matched to `C` at some cost, etc. The\n+underlying graph is in fact a complete bipartite graph; the cost we\n+associate with every edge is the size of the diff between the two\n+commits' patches. To explain also new commits, we introduce dummy nodes\n+on both sides:\n+\n+------------\n+    1 ----.      A\n+\t  |    /\n+    2 ----+---'  B\n+\t  |\n+    o     `----- C\n+\t  c>0\n+    o            o\n+\n+    o            o\n+------------\n+\n+The cost of an edge `o--C` is the size of `C`'s diff, modified by a\n+fudge factor that should be smaller than 100%. The cost of an edge\n+`o--o` is free. The fudge factor is necessary because even if `1` and\n+`C` have nothing in common, they may still share a few empty lines and\n+such, possibly making the assignment `1--C`, `o--o` slightly cheaper\n+than `1--o`, `o--C` even if `1` and `C` have nothing in common. With the\n+fudge factor we require a much larger common part to consider patches as\n+corresponding.\n+\n+The overall time needed to compute this algorithm is the time needed to\n+compute n+m commit diffs and then n*m diffs of patches, plus the time\n+needed to compute the least-cost assigment between n and m diffs. Git\n+uses an implementation of the Jonker-Volgenant algorithm to solve the\n+assignment problem, which has cubic runtime complexity. The matching\n+found in this case will look like this:\n+\n+------------\n+    1 ----.      A\n+\t  |    /\n+    2 ----+---'  B\n+       .--+-----'\n+    o -'  `----- C\n+\t  c>0\n+    o ---------- o\n+\n+    o ---------- o\n+------------\n+\n+\n+SEE ALSO\n+--------\n+linkgit:git-log[1]\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\n-- \ngitgitgadget\n\n"},{"id":"353279","messageId":"07ec215e83d73ab5d0cb6221c243b24d58694b6d.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 19/21] range-diff: left-pad patch numbers","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:05:11Z","receivedAt":"2018-07-21T22:05:15Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nAs pointed out by Elijah Newren, tbdiff has this neat little alignment\ntrick where it outputs the commit pairs with patch numbers that are\npadded to the maximal patch number's width:\n\n\t  1: cafedead =   1: acefade first patch\n\t[...]\n\t314: beefeada < 314: facecab up to PI!\n\nLet's do the same in range-diff, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 16 +++++++++-------\n 1 file changed, 9 insertions(+), 7 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex ab1e71e10..347b4a79f 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -259,6 +259,7 @@ static void get_correspondences(struct string_list *a, struct string_list *b,\n }\n \n static void output_pair_header(struct diff_options *diffopt,\n+\t\t\t       int patch_no_width,\n \t\t\t       struct strbuf *buf,\n \t\t\t       struct strbuf *dashes,\n \t\t\t       struct patch_util *a_util,\n@@ -295,9 +296,9 @@ static void output_pair_header(struct diff_options *diffopt,\n \tstrbuf_reset(buf);\n \tstrbuf_addstr(buf, status == '!' ? color_old : color);\n \tif (!a_util)\n-\t\tstrbuf_addf(buf, \"-:  %s \", dashes->buf);\n+\t\tstrbuf_addf(buf, \"%*s:  %s \", patch_no_width, \"-\", dashes->buf);\n \telse\n-\t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n+\t\tstrbuf_addf(buf, \"%*d:  %s \", patch_no_width, a_util->i + 1,\n \t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n \n \tif (status == '!')\n@@ -307,9 +308,9 @@ static void output_pair_header(struct diff_options *diffopt,\n \t\tstrbuf_addf(buf, \"%s%s\", color_reset, color_new);\n \n \tif (!b_util)\n-\t\tstrbuf_addf(buf, \" -:  %s\", dashes->buf);\n+\t\tstrbuf_addf(buf, \" %*s:  %s\", patch_no_width, \"-\", dashes->buf);\n \telse\n-\t\tstrbuf_addf(buf, \" %d:  %s\", b_util->i + 1,\n+\t\tstrbuf_addf(buf, \" %*d:  %s\", patch_no_width, b_util->i + 1,\n \t\t\t    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));\n \n \tcommit = lookup_commit_reference(oid);\n@@ -362,6 +363,7 @@ static void output(struct string_list *a, struct string_list *b,\n \t\t   struct diff_options *diffopt)\n {\n \tstruct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;\n+\tint patch_no_width = decimal_width(1 + (a->nr > b->nr ? a->nr : b->nr));\n \tint i = 0, j = 0;\n \n \t/*\n@@ -383,7 +385,7 @@ static void output(struct string_list *a, struct string_list *b,\n \n \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n \t\tif (i < a->nr && a_util->matching < 0) {\n-\t\t\toutput_pair_header(diffopt,\n+\t\t\toutput_pair_header(diffopt, patch_no_width,\n \t\t\t\t\t   &buf, &dashes, a_util, NULL);\n \t\t\ti++;\n \t\t\tcontinue;\n@@ -391,7 +393,7 @@ static void output(struct string_list *a, struct string_list *b,\n \n \t\t/* Show unmatched RHS commits. */\n \t\twhile (j < b->nr && b_util->matching < 0) {\n-\t\t\toutput_pair_header(diffopt,\n+\t\t\toutput_pair_header(diffopt, patch_no_width,\n \t\t\t\t\t   &buf, &dashes, NULL, b_util);\n \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n@@ -399,7 +401,7 @@ static void output(struct string_list *a, struct string_list *b,\n \t\t/* Show matching LHS/RHS pair. */\n \t\tif (j < b->nr) {\n \t\t\ta_util = a->items[b_util->matching].util;\n-\t\t\toutput_pair_header(diffopt,\n+\t\t\toutput_pair_header(diffopt, patch_no_width,\n \t\t\t\t\t   &buf, &dashes, a_util, b_util);\n \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n-- \ngitgitgadget\n\n"},{"id":"353281","messageId":"b370468e71af2b8c7ffa0e31f3a3910d15897ab4.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 20/21] range-diff: make --dual-color the default mode","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:05:12Z","receivedAt":"2018-07-21T22:05:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nAfter using this command extensively for the last two months, this\ndeveloper came to the conclusion that even if the dual color mode still\nleaves a lot of room for confusion about what was actually changed, the\nnon-dual color mode is substantially worse in that regard.\n\nTherefore, we really want to make the dual color mode the default.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-range-diff.txt       | 32 +++++++++++++++-----------\n builtin/range-diff.c                   | 10 ++++----\n contrib/completion/git-completion.bash |  2 +-\n 3 files changed, 25 insertions(+), 19 deletions(-)\n\ndiff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\nindex f1a6737f8..e3c0be559 100644\n--- a/Documentation/git-range-diff.txt\n+++ b/Documentation/git-range-diff.txt\n@@ -9,7 +9,7 @@ SYNOPSIS\n --------\n [verse]\n 'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]\n-\t[--dual-color] [--creation-factor=<factor>]\n+\t[--no-dual-color] [--creation-factor=<factor>]\n \t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n \n DESCRIPTION\n@@ -31,11 +31,14 @@ all of their ancestors have been shown.\n \n OPTIONS\n -------\n---dual-color::\n-\tWhen the commit diffs differ, recreate the original diffs'\n-\tcoloring, and add outer -/+ diff markers with the *background*\n-\tbeing red/green to make it easier to see e.g. when there was a\n-\tchange in what exact lines were added.\n+--no-dual-color::\n+\tWhen the commit diffs differ, `git range-diff` recreates the\n+\toriginal diffs' coloring, and adds outer -/+ diff markers with\n+\tthe *background* being red/green to make it easier to see e.g.\n+\twhen there was a change in what exact lines were added. This is\n+\tknown to `range-diff` as \"dual coloring\". Use `--no-dual-color`\n+\tto revert to color all lines according to the outer diff markers\n+\t(and completely ignore the inner diff when it comes to color).\n \n --creation-factor=<percent>::\n \tSet the creation/deletion cost fudge factor to `<percent>`.\n@@ -118,15 +121,16 @@ line (with a perfect match) is yellow like the commit header of `git\n show`'s output, and the third line colors the old commit red, the new\n one green and the rest like `git show`'s commit header.\n \n-The color-coded diff is actually a bit hard to read, though, as it\n-colors the entire lines red or green. The line that added \"What is\n-unexpected\" in the old commit, for example, is completely red, even if\n-the intent of the old commit was to add something.\n+A naive color-coded diff of diffs is actually a bit hard to read,\n+though, as it colors the entire lines red or green. The line that added\n+\"What is unexpected\" in the old commit, for example, is completely red,\n+even if the intent of the old commit was to add something.\n \n-To help with that, use the `--dual-color` mode. In this mode, the diff\n-of diffs will retain the original diff colors, and prefix the lines with\n--/+ markers that have their *background* red or green, to make it more\n-obvious that they describe how the diff itself changed.\n+To help with that, `range` uses the `--dual-color` mode by default. In\n+this mode, the diff of diffs will retain the original diff colors, and\n+prefix the lines with -/+ markers that have their *background* red or\n+green, to make it more obvious that they describe how the diff itself\n+changed.\n \n \n Algorithm\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex c25d88317..77ac3bff7 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -20,11 +20,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n {\n \tint creation_factor = 60;\n \tstruct diff_options diffopt = { NULL };\n-\tint dual_color = 0;\n+\tint simple_color = -1;\n \tstruct option options[] = {\n \t\tOPT_INTEGER(0, \"creation-factor\", &creation_factor,\n \t\t\t    N_(\"Percentage by which creation is weighted\")),\n-\t\tOPT_BOOL(0, \"dual-color\", &dual_color,\n+\t\tOPT_BOOL(0, \"no-dual-color\", &simple_color,\n \t\t\t    N_(\"color both diff and diff-between-diffs\")),\n \t\tOPT_END()\n \t};\n@@ -55,8 +55,10 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \targc = j;\n \tdiff_setup_done(&diffopt);\n \n-\tif (dual_color) {\n-\t\tdiffopt.use_color = 1;\n+\tif (simple_color < 1) {\n+\t\tif (!simple_color)\n+\t\t\t/* force color when --dual-color was used */\n+\t\t\tdiffopt.use_color = 1;\n \t\tdiffopt.flags.dual_color_diffed_diffs = 1;\n \t}\n \ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 402490673..e35fc28fc 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1981,7 +1981,7 @@ _git_range_diff ()\n   case \"$cur\" in\n   --*)\n           __gitcomp \"\n-\t  \t--creation-factor= --dual-color\n+\t  \t--creation-factor= --no-dual-color\n                   $__git_diff_common_options\n                   \"\n           return\n-- \ngitgitgadget\n\n"},{"id":"353280","messageId":"d8498fb32614842ce52b07b49076b59840b8a657.1532210683.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 21/21] range-diff: use dim/bold cues to improve dual color mode","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-07-21T22:05:14Z","receivedAt":"2018-07-21T22:05:18Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nIt *is* a confusing thing to look at a diff of diffs. All too easy is it\nto mix up whether the -/+ markers refer to the \"inner\" or the \"outer\"\ndiff, i.e. whether a `+` indicates that a line was added by either the\nold or the new diff (or both), or whether the new diff does something\ndifferent than the old diff.\n\nTo make things easier to process for normal developers, we introduced\nthe dual color mode which colors the lines according to the commit diff,\ni.e. lines that are added by a commit (whether old, new, or both) are\ncolored in green. In non-dual color mode, the lines would be colored\naccording to the outer diff: if the old commit added a line, it would be\ncolored red (because that line addition is only present in the first\ncommit range that was specified on the command-line, i.e. the \"old\"\ncommit, but not in the second commit range, i.e. the \"new\" commit).\n\nHowever, this dual color mode is still not making things clear enough,\nas we are looking at two levels of diffs, and we still only pick a color\naccording to *one* of them (the outer diff marker is colored\ndifferently, of course, but in particular with deep indentation, it is\neasy to lose track of that outer diff marker's background color).\n\nTherefore, let's add another dimension to the mix. Still use\ngreen/red/normal according to the commit diffs, but now also dim the\nlines that were only in the old commit, and use bold face for the lines\nthat are only in the new commit.\n\nThat way, it is much easier not to lose track of, say, when we are\nlooking at a line that was added in the previous iteration of a patch\nseries but the new iteration adds a slightly different version: the\nobsolete change will be dimmed, the current version of the patch will be\nbold.\n\nAt least this developer has a much easier time reading the range-diffs\nthat way.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/config.txt         |  6 ++++--\n Documentation/git-range-diff.txt | 17 +++++++++++++----\n color.h                          |  6 ++++++\n diff.c                           | 28 ++++++++++++++++++++++------\n diff.h                           |  8 +++++++-\n 5 files changed, 52 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex a32172a43..6dbfc9a09 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1159,8 +1159,10 @@ color.diff.<slot>::\n \t(highlighting whitespace errors), `oldMoved` (deleted lines),\n \t`newMoved` (added lines), `oldMovedDimmed`, `oldMovedAlternative`,\n \t`oldMovedAlternativeDimmed`, `newMovedDimmed`, `newMovedAlternative`\n-\tand `newMovedAlternativeDimmed` (See the '<mode>'\n-\tsetting of '--color-moved' in linkgit:git-diff[1] for details).\n+\t`newMovedAlternativeDimmed` (See the '<mode>'\n+\tsetting of '--color-moved' in linkgit:git-diff[1] for details),\n+\t`contextDimmed`, `oldDimmed`, `newDimmed`, `contextBold`,\n+\t`oldBold`, and `newBold` (see linkgit:git-range-diff[1] for details).\n \n color.decorate.<slot>::\n \tUse customized color for 'git log --decorate' output.  `<slot>` is one\ndiff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\nindex e3c0be559..0027f35a2 100644\n--- a/Documentation/git-range-diff.txt\n+++ b/Documentation/git-range-diff.txt\n@@ -35,10 +35,19 @@ OPTIONS\n \tWhen the commit diffs differ, `git range-diff` recreates the\n \toriginal diffs' coloring, and adds outer -/+ diff markers with\n \tthe *background* being red/green to make it easier to see e.g.\n-\twhen there was a change in what exact lines were added. This is\n-\tknown to `range-diff` as \"dual coloring\". Use `--no-dual-color`\n-\tto revert to color all lines according to the outer diff markers\n-\t(and completely ignore the inner diff when it comes to color).\n+\twhen there was a change in what exact lines were added.\n++\n+Additionally, the commit diff lines that are only present in the first commit\n+range are shown \"dimmed\" (this can be overridden using the `color.diff.<slot>`\n+config setting where `<slot>` is one of `contextDimmed`, `oldDimmed` and\n+`newDimmed`), and the commit diff lines that are only present in the second\n+commit range are shown in bold (which can be overridden using the config\n+settings `color.diff.<slot>` with `<slot>` being one of `contextBold`,\n+`oldBold` or `newBold`).\n++\n+This is known to `range-diff` as \"dual coloring\". Use `--no-dual-color`\n+to revert to color all lines according to the outer diff markers\n+(and completely ignore the inner diff when it comes to color).\n \n --creation-factor=<percent>::\n \tSet the creation/deletion cost fudge factor to `<percent>`.\ndiff --git a/color.h b/color.h\nindex 33e786342..98894d6a1 100644\n--- a/color.h\n+++ b/color.h\n@@ -36,6 +36,12 @@ struct strbuf;\n #define GIT_COLOR_BOLD_BLUE\t\"\\033[1;34m\"\n #define GIT_COLOR_BOLD_MAGENTA\t\"\\033[1;35m\"\n #define GIT_COLOR_BOLD_CYAN\t\"\\033[1;36m\"\n+#define GIT_COLOR_FAINT_RED\t\"\\033[2;31m\"\n+#define GIT_COLOR_FAINT_GREEN\t\"\\033[2;32m\"\n+#define GIT_COLOR_FAINT_YELLOW\t\"\\033[2;33m\"\n+#define GIT_COLOR_FAINT_BLUE\t\"\\033[2;34m\"\n+#define GIT_COLOR_FAINT_MAGENTA\t\"\\033[2;35m\"\n+#define GIT_COLOR_FAINT_CYAN\t\"\\033[2;36m\"\n #define GIT_COLOR_BG_RED\t\"\\033[41m\"\n #define GIT_COLOR_BG_GREEN\t\"\\033[42m\"\n #define GIT_COLOR_BG_YELLOW\t\"\\033[43m\"\ndiff --git a/diff.c b/diff.c\nindex 03ed235c7..272b0b938 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -69,6 +69,12 @@ static char diff_colors[][COLOR_MAXLEN] = {\n \tGIT_COLOR_BOLD_YELLOW,\t/* NEW_MOVED ALTERNATIVE */\n \tGIT_COLOR_FAINT,\t/* NEW_MOVED_DIM */\n \tGIT_COLOR_FAINT_ITALIC,\t/* NEW_MOVED_ALTERNATIVE_DIM */\n+\tGIT_COLOR_FAINT,\t/* CONTEXT_DIM */\n+\tGIT_COLOR_FAINT_RED,\t/* OLD_DIM */\n+\tGIT_COLOR_FAINT_GREEN,\t/* NEW_DIM */\n+\tGIT_COLOR_BOLD,\t\t/* CONTEXT_BOLD */\n+\tGIT_COLOR_BOLD_RED,\t/* OLD_BOLD */\n+\tGIT_COLOR_BOLD_GREEN,\t/* NEW_BOLD */\n };\n \n static const char *color_diff_slots[] = {\n@@ -88,6 +94,12 @@ static const char *color_diff_slots[] = {\n \t[DIFF_FILE_NEW_MOVED_ALT]     = \"newMovedAlternative\",\n \t[DIFF_FILE_NEW_MOVED_DIM]     = \"newMovedDimmed\",\n \t[DIFF_FILE_NEW_MOVED_ALT_DIM] = \"newMovedAlternativeDimmed\",\n+\t[DIFF_CONTEXT_DIM]\t      = \"contextDimmed\",\n+\t[DIFF_FILE_OLD_DIM]\t      = \"oldDimmed\",\n+\t[DIFF_FILE_NEW_DIM]\t      = \"newDimmed\",\n+\t[DIFF_CONTEXT_BOLD]\t      = \"contextBold\",\n+\t[DIFF_FILE_OLD_BOLD]\t      = \"oldBold\",\n+\t[DIFF_FILE_NEW_BOLD]\t      = \"newBold\",\n };\n \n static NORETURN void die_want_option(const char *option_name)\n@@ -1089,11 +1101,13 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \n \t\t\tset_sign = set;\n \t\t\tif (c == '-')\n-\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD_BOLD);\n \t\t\telse if (c == '@')\n \t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n-\t\t\telse if (c != '+')\n-\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t\telse if (c == '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW_BOLD);\n+\t\t\telse\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT_BOLD);\n \t\t\tflags |= WS_IGNORE_FIRST_SPACE;\n \t\t}\n \t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n@@ -1131,11 +1145,13 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \n \t\t\tset_sign = set;\n \t\t\tif (c == '+')\n-\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW_DIM);\n \t\t\telse if (c == '@')\n \t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n-\t\t\telse if (c != '-')\n-\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t\telse if (c == '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD_DIM);\n+\t\t\telse\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT_DIM);\n \t\t}\n \t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '-',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);\ndiff --git a/diff.h b/diff.h\nindex 892416a14..a08a3b2a2 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -243,7 +243,13 @@ enum color_diff {\n \tDIFF_FILE_NEW_MOVED = 13,\n \tDIFF_FILE_NEW_MOVED_ALT = 14,\n \tDIFF_FILE_NEW_MOVED_DIM = 15,\n-\tDIFF_FILE_NEW_MOVED_ALT_DIM = 16\n+\tDIFF_FILE_NEW_MOVED_ALT_DIM = 16,\n+\tDIFF_CONTEXT_DIM = 17,\n+\tDIFF_FILE_OLD_DIM = 18,\n+\tDIFF_FILE_NEW_DIM = 19,\n+\tDIFF_CONTEXT_BOLD = 20,\n+\tDIFF_FILE_OLD_BOLD = 21,\n+\tDIFF_FILE_NEW_BOLD = 22,\n };\n const char *diff_get_color(int diff_use_color, enum color_diff ix);\n #define diff_get_color_opt(o, ix) \\\n-- \ngitgitgadget\n"},{"id":"353282","messageId":"nycvar.QRO.7.76.6.1807220003070.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAGZ79kZzHN+HKYeezyeNwfe2+dTGHnOzs3okJTVrfm=AFwPbnQ@mail.gmail.com","subject":"Re: [PATCH v3 09/20] range-diff: adjust the output of the commit pairs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-21T22:07:23Z","receivedAt":"2018-07-21T22:07:48Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Fri, 20 Jul 2018, Stefan Beller wrote:\n\n> >     1. To roll again.\n> >\n> >         A player who rolls two sixes can reroll the dice for an additional\n> >         turn.\n> \n> This is where I had my AHA moment!\n> (Consider my software development process as chaotic as a dice roll\n> So rerolling is really just rolling the dice again to \"get my patch\n> accepted\" ;-)\n\nWouldn't that be nice? But you only get to reroll if you had two sixes.\nTough luck for you, Stefan.\n\n> >     2. (programming) To convert (an unrolled instruction sequence) back into\n> >        a loop. quotations ▼\n> \n> We do not have unrolled loops?\n\nWhen resending patch series? *rolls eyes*\n\n> This was good back in the day where the cost of each instruction weighted\n> heavy on the CPU, such that the JMPs that are needed (and the loop\n> variable check that might have had a bad branch prediction) for the loop were\n> slowing down the execution.\n> \n> Nowadays (when I was studying 5 years ago) the branch prediction and\n> individual instruction execution are really good, but the bottleneck\n> that I measured (when I had a lot of time at my disposal and attending a\n> class/project on micro architectures), was the CPU instruction cache\n> size, i.e. loop unrolling made the code *slower* than keeping tight\n> loops loaded in memory.\n> https://stackoverflow.com/questions/24196076/is-gcc-loop-unrolling-flag-really-effective\n> \n> > Noun\n> >\n> > reroll (plural rerolls)\n> >\n> >     (dice games) A situation in the rules of certain dice games where a\n> >     player is given the option to reroll an undesirable roll of the dice.\n> >\n> >\n> > You will notice how this does not list *any* hint at referring to\n> > something that Junio calls \"reroll\".\n> \n> We have undesirable patches that were 'rolled' onto the mailing list,\n> so they have to be rerolled?\n> \n> > Footnote *1*: https://en.wiktionary.org/wiki/commit#Noun does not even\n> > bother to acknowledge our use of referring to a snapshot of a source code\n> > base as a \"commit\".\n> \n> When Git was a content addressable file system, a commit was precisely\n> \"a database transaction, [...] making it a permanent change.\"\n> \n> Side note:\n> I was just giving a talk to my colleagues about diff aglorithms\n> (and eventually describing a bug in the histogram diff algorithm)\n> and we got really riled up with \"Longest Common Subsequence\",\n> as the mathematical definition is different than what the code\n> or I (after studying the code) had in mind.\n> \n> Naming things is hard, and sometimes the collective wisdom got\n> it wrong, but changing it would be very costly in the short/medium\n> term.\n\nMy point is not that naming is hard. But picking names that are\n*different* from what is established nomenclature is... unwise.\n\nIn this case, it makes an already unnecessarily awkward code contribution\nprocess even more unnecessarily uninviting.\n\n> Another note about \"rolling things\": At $DAYJOB I review changes\n> that are committed to the another revision control system w.r.t. its\n> compliance of open source licenses (hence I am exposed to a lot\n> of different projects), and some of those changes are titled\n> \"Roll up to version $X\" which I found strange, but knew\n> what was meant.\n\nTo \"roll up\" is, as far as this non-native speaker can tell, an\nestablished way to express this action.\n\nIn short: nothing you wrote can adequately defend why the Git project\nchooses to confuse new contributors seemingly on purpose.\n\nCiao,\nDscho"},{"id":"353287","messageId":"CAPig+cRd2V_hN0BVCcevXhu1v_QpL76mhqTGQmWPLK7sAD4Ytw@mail.gmail.com","threadId":"48405","inReplyTo":"2b8d09020fff0ac220c1878c65b47290c5245cb9.1532210683.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 11/21] range-diff: add tests","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-22T05:04:20Z","receivedAt":"2018-07-22T05:04:34Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, Jul 21, 2018 at 6:05 PM Thomas Rast via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> These are essentially lifted from https://github.com/trast/tbdiff, with\n> light touch-ups to account for the command now being names `git\n\ns/names/named/\n\n> range-diff`.\n>\n> Apart from renaming `tbdiff` to `range-diff`, only one test case needed\n> to be adjusted: 11 - 'changed message'.\n>\n> The underlying reason it had to be adjusted is that diff generation is\n> sometimes ambiguous. In this case, a comment line and an empty line are\n> added, but it is ambiguous whether they were added after the existing\n> empty line, or whether an empty line and the comment line are added\n> *before* the existing empty line. And apparently xdiff picks a different\n> option here than Python's difflib.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n"},{"id":"353294","messageId":"CAPig+cQ6H1ys6MrZ3qV_93WCQxz0mxnQJMm23XRTrDh1WHHnGg@mail.gmail.com","threadId":"48405","inReplyTo":"9b3632324f93afdab7273df7a7dc119e14a261a1.1532210683.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 18/21] completion: support `git range-diff`","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-22T05:49:11Z","receivedAt":"2018-07-22T05:49:25Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, Jul 21, 2018 at 6:05 PM Johannes Schindelin via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> Tab completion of `git range-diff` is very convenient, especially\n> given that the revision arguments to specify the commit ranges to\n> compare are typically more complex than, say, what is normally passed\n> to `git log`.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n> @@ -1976,6 +1976,20 @@ _git_push ()\n> +_git_range_diff ()\n> +{\n> +  case \"$cur\" in\n> +  --*)\n> +          __gitcomp \"\n> +               --creation-factor= --dual-color\n> +                  $__git_diff_common_options\n> +                  \"\n\nThis is indented with a mix of spaces and tabs.\n\n    Applying: completion: support `git range-diff`\n    .git/rebase-apply/patch:18: space before tab in indent.\n                --creation-factor= --dual-color\n    warning: 1 line adds whitespace errors.\n    Applying: range-diff: make --dual-color the default mode\n    .git/rebase-apply/patch:105: space before tab in indent.\n                --creation-factor= --no-dual-color\n    warning: 1 line adds whitespace errors.\n\nOther parts of this script seem to use tabs for indentation.\n"},{"id":"353329","messageId":"20180723012552.GA26886@sigill.intra.peff.net","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1807212347330.71@tvgsbejvaqbjf.bet","subject":"Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-07-23T01:25:53Z","receivedAt":"2018-07-23T01:25:57Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Jul 21, 2018 at 11:56:06PM +0200, Johannes Schindelin wrote:\n\n> > The script that I feed a message from gmane or public-inbox when I need\n> > to learn the set of commits that resulted from the message instead uses\n> > \"git grep $message-id notes/amlog\".  And that is fast enough for my\n> > purpose.\n> \n> Awesome. You might want to make sure that Peff stops advertising the amlog\n> notes, then, though.\n\nWoah, what did I do now?\n\n> > There is no good reason to abuse the notes mechanism to map a random\n> > object-name looking string (i.e. hash result of message id), other\n> > than the ease of \"quick access\" when somebody is making a lot of\n> > inquiry, but that \"database\" does not have to be stored in notes.\n> \n> Right. And it does not have to be stored anywhere, because nobody used it\n> anyway, right?\n\nIf I understand the situation correctly, Junio is saying that he will\ncontinue to produce the amlog mapping, and that it contains sufficient\ninformation to produce the reverse mapping (which, as an aside, I did\nnot even know existed -- I mostly want to go the other way, from digging\nin history to a mailing list conversation).\n\nE.g., the script below builds and queries an incremental reverse\nmapping.\n\n-- >8 --\n#!/usr/bin/perl\n\nmy $REF = 'refs/notes/amlog';\nmy $DBFILE = '.git/amlog.rev';\n\nuse DB_File;\nmy %h;\nmy $db = tie %h, 'DB_File', $DBFILE, O_CREAT|O_RDWR, 0644\n  or die \"unable to open/create $DBFILE: $!\";\n\nmy $db_tip = $h{TIP};\nchomp(my $rev_tip = `git rev-parse $REF`);\nif (!defined $db_tip || $db_tip ne $rev_tip) {\n  print STDERR \"Updating reverse mapping...\\n\";\n  # using -p here is quick and easy, since we know the\n  # shape of the data. Using --raw and cat-file might be less\n  # hacky, though.\n  my @cmd = (qw(git log --format= --reverse -p), $rev_tip);\n  push @cmd, \"^$db_tip\" if defined $db_tip;\n  open(my $fh, \"-|\", @cmd);\n\n  my $commit;\n  while (<$fh>) {\n    if (m{^\\+\\+\\+ b/([0-9a-f/]+)}) {\n      $commit = $1;\n      $commit =~ s/[^0-9a-f]//g;\n    } elsif (/^\\+Message-Id: <(.*)>/i) {\n      print STDERR \"Imported $commit => $1\\n\";\n      $h{$1} = $commit;\n    }\n  }\n  $h{TIP} = $rev_tip;\n}\n\nprint \"$h{$_} $_\\n\" for @ARGV;\n-- >8 --\n\nThat stores it in a local dbm. But it could also build a git-notes tree\nif you really want that.\n\nAnd if I understand what is being said here:\n\n> > It certainly does not belong to cycles worth spending by me *while*\n> > I work during the say with various history reshaping tools to record\n> > and/or update the reverse mapping and that is why my post-applypatch\n> > hook no longer has the \"reverse map\" hack.\n> > \n> > It is not like anybody (including me) needs realtime up-to-date\n> > reverse mapping from amlog while I run my \"commit --amend\", \"rebase\n> > -i\", etc. and the reverse map is constructable by reversing the\n> > forward map, obviously, with a postprocessing.  And I think that is\n> > a reasonably way forward if anybody wants to have a reverse mapping.\n> > The postprocessing can be done either by me before pushing out the\n> > amlog ref, or done by any consumer after fetching the amlog ref from\n> > me.  If I did the postprocessing and refuse to use rewrite hook you\n> > wouldn't even know ;-)\n\nIt is not \"I refuse to push out a reverse mapping\". It is \"I could make\nthe reverse mapping before push-out, and you would not need to know or\ncare if I did it all at once, or using a rewrite hook\".\n\nThough personally, I do not know if there is much point in pushing it\nout, given that receivers can reverse the mapping themselves.\n\nOr is there some argument that there is information in the reverse map\nthat _cannot_ be generated from the forward map?\n\n-Peff\n"},{"id":"353417","messageId":"CAGZ79kb4ki0cXLnJHeqzRvWaGWki1_epWOdCy49s_v9cy_tJ2A@mail.gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-23T21:03:38Z","receivedAt":"2018-07-23T21:03:52Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Sat, Jul 21, 2018 at 3:04 PM Johannes Schindelin via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n\n> Side note: I work on implementing range-diff not only to make life easier for reviewers who have to suffer through v2, v3, ... of my patch series, but also to verify my changes before submitting a new iteration. And also, maybe even more importantly, I plan to use it to verify my merging-rebases of Git for\n> Windows (for which I previously used to redirect the pre-rebase/post-rebase diffs vs upstream and then compare them using `git diff --no-index`). And of course any interested person can see what changes were necessary e.g. in the merging-rebase of Git for Windows onto v2.17.0 by running a command like:\n\nThanks for making tools that makes the life of a Git developer easier!\n(Just filed https://github.com/gitgitgadget/gitgitgadget/issues/26\nwhich asks to break lines for this cover letter)\n\n> base-commit: b7bd9486b055c3f967a870311e704e3bb0654e4f\n> Published-As: https://github.com/gitgitgadget/git/releases/tags/pr-1%2Fdscho%2Fbranch-diff-v4\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1/dscho/branch-diff-v4\n> Pull-Request: https://github.com/gitgitgadget/git/pull/1\n\nI just realized that if I had ideal memory of the previous review,\nI could contain my whole review answer to this email only.\n\n>\n> Range-diff vs v3:\n>\n>   1:  39272eefc !  1:  f7e70689e linear-assignment: a function to solve least-cost assignment problems\n>      @@ -223,9 +223,7 @@\n>       +                         BUG(\"negative j: %d\", j);\n>       +                 i = pred[j];\n>       +                 column2row[j] = i;\n>      -+                 k = j;\n>      -+                 j = row2column[i];\n>      -+                 row2column[i] = k;\n>      ++                 SWAP(j, row2column[i]);\n\nThe dual color option (as a default) really helps here. Thanks for that!\nDoes it have to be renamed though? (It's more than two colors; originally\nit was inverting the beginning signs)\n\nMaybe --color=emphasize-later\nassuming there will be other modes for coloring, such as \"diff-only\",\nwhich would correspond with --no-dual-color, or \"none\" that will correspond\nwould be --no-color. I imagine there could be more fancy things, hence I would\npropose a mode rather than a switch.\n(Feel free to push back if discussing naming here feels like bike shedding)\n\n\n2:  7f15b26d4ea !  82:  88134121d2a Introduce `range-diff` to compare\niterations of a topic branch\n[...]\n>       diff --git a/Makefile b/Makefile\n>       --- a/Makefile\n>       +++ b/Makefile\n\nThe line starting with --- is red (default removed color) and the line\nwith +++ is green (default add color).\n\nIdeally these two lines and the third line above starting with \"diff --git\"\nwould render in GIT_COLOR_BOLD (\"METAINFO\").\n\n>   3:  076e1192d !  3:  4e3fb47a1 range-diff: first rudimentary implementation\n>      @@ -4,7 +4,7 @@\n>\n>           At this stage, `git range-diff` can determine corresponding commits\n>           of two related commit ranges. This makes use of the recently introduced\n>      -    implementation of the Hungarian algorithm.\n>      +    implementation of the linear assignment algorithm.\n>\n>           The core of this patch is a straight port of the ideas of tbdiff, the\n>           apparently dormant project at https://github.com/trast/tbdiff.\n>      @@ -51,19 +51,17 @@\n>       + int res = 0;\n>       + struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n>\n>      -- argc = parse_options(argc, argv, NULL, options,\n>      --                      builtin_range_diff_usage, 0);\n>      -+ argc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,\n>      -+                      0);\n>      +  argc = parse_options(argc, argv, NULL, options,\n>      +                       builtin_range_diff_usage, 0);\n\nThis is really nice in colors when viewed locally.\n\n>  16:  dfa7b1e71 <  -:  --------- range-diff --dual-color: work around bogus white-space warning\n>   -:  --------- > 16:  f4252f2b2 range-diff --dual-color: fix bogus white-space warning\n\nAh; here my initial assumption of only reviewing the range-diff breaks down now.\nI'll dig into patch 16 separately.\n\nMaybe it is worth having an option to expand all \"new\" patches.\n(Given that the range-diff pr-1/dscho/branch-diff-v3...pr-1/dscho/branch-diff-v4\ntold me you used a different base, this is a hard problem, as I certainly\nwould want to skip over all new base commits, but this one is interesting\nto look at. An easy way out: Maybe an option to expand any new\ncommits/patches after the first expansion? Asking for opinions rather\nthan implementing it)\n\n>  19:  144363006 <  -:  --------- range-diff: left-pad patch numbers\n>   -:  --------- > 19:  07ec215e8 range-diff: left-pad patch numbers\n\n>   -:  --------- > 21:  d8498fb32 range-diff: use dim/bold cues to improve dual color mode\n\nThose are interesting, I'll look at them separately, too.\n\nThanks,\nStefan\n"},{"id":"353418","messageId":"CAGZ79kbebFenJYTvReZV+TuehUfTWDYx0gr+ZnTdw00BBUKm-Q@mail.gmail.com","threadId":"48405","inReplyTo":"2b8d09020fff0ac220c1878c65b47290c5245cb9.1532210683.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 11/21] range-diff: add tests","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-23T21:25:14Z","receivedAt":"2018-07-23T21:25:28Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"> +test_expect_success 'simple B...C (unmodified)' '\n> +       git range-diff --no-color\n\nI wonder if we want to have tests for colors, too, eventually.\n(Feel free to push back on it or put it into the left over hashtag.\nBut given how much time we (or I) spent discussing colors,\nthis would be a welcome extension for the tests)\n\nStefan\n"},{"id":"353426","messageId":"xmqq36w94o87.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"CAGZ79kb4ki0cXLnJHeqzRvWaGWki1_epWOdCy49s_v9cy_tJ2A@mail.gmail.com","subject":"Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-23T21:49:12Z","receivedAt":"2018-07-23T21:49:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> On Sat, Jul 21, 2018 at 3:04 PM Johannes Schindelin via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>\n>> Side note: I work on implementing range-diff not only to make life easier for reviewers who have to suffer through v2, v3, ... of my patch series, but also to verify my changes before submitting a new iteration. And also, maybe even more importantly, I plan to use it to verify my merging-rebases of Git for\n>> Windows (for which I previously used to redirect the pre-rebase/post-rebase diffs vs upstream and then compare them using `git diff --no-index`). And of course any interested person can see what changes were necessary e.g. in the merging-rebase of Git for Windows onto v2.17.0 by running a command like:\n>\n> Thanks for making tools that makes the life of a Git developer easier!\n> (Just filed https://github.com/gitgitgadget/gitgitgadget/issues/26\n> which asks to break lines for this cover letter)\n\nThanks.  These cover letters are unreadable without W Q\n(gnus-article-fill-long-lines)\n"},{"id":"353427","messageId":"CAGZ79kZRoN6DmKYPyvQ33yXqxz8ukfuXVROw9pzZBvob-vjHAQ@mail.gmail.com","threadId":"48405","inReplyTo":"f4252f2b2198cf13d5b0a21c54098e2a1d8158dd.1532210683.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 16/21] range-diff --dual-color: fix bogus white-space warning","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-23T22:20:39Z","receivedAt":"2018-07-23T22:20:53Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Sat, Jul 21, 2018 at 3:05 PM Johannes Schindelin via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n>\n> When displaying a diff of diffs, it is possible that there is an outer\n> `+` before a context line. That happens when the context changed between\n> old and new commit. When that context line starts with a tab (after the\n> space that marks it as context line), our diff machinery spits out a\n> white-space error (space before tab), but in this case, that is\n> incorrect.\n>\n> Fix this by adding a specific whitespace flag that simply ignores the\n> first space in the output.\n\nThat sounds like a simple (not easy) solution, which sounds acceptable\nto me here.\n\nI guess you dropped all ideas that I originally proposed for the cleanup\nregarding ws. that is fine, I can roll the cleanup on top of your patches\nhere.\n\n> Of course, this flag is *really* specific to the \"diff of diffs\" use\n> case. The original idea was to simply skip the space from the output,\n> but that workaround was rejected by the Git maintainer as causing\n> headaches.\n\nBy that you mean\nhttps://public-inbox.org/git/xmqqr2kb9jk2.fsf@gitster-ct.c.googlers.com/\n?\n\n> Note: as the original code did not leave any space in the bit mask\n> before the WSEH_* bits, the diff of this commit looks unnecessarily\n> involved: the diff is dominated by making room for one more bit to be\n> used by the whitespace rules.\n\nIt took me some minutes, but I am reasonably convinced this patch\nis correct (and doesn't collide with other series in flight, sb/diff-color-more\nadds another flag to move detection in another bit field at (1<<23))\n\nThanks for writing this patch instead of the other, though I'll leave\nit to Junio to weigh in if this approach is the best design.\n\nStefan\n\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  cache.h |  3 ++-\n>  diff.c  | 15 ++++++++-------\n>  diff.h  |  6 +++---\n>  ws.c    | 11 ++++++++++-\n>  4 files changed, 23 insertions(+), 12 deletions(-)\n>\n> diff --git a/cache.h b/cache.h\n> index 8b447652a..8abfbeb73 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -1681,11 +1681,12 @@ void shift_tree_by(const struct object_id *, const struct object_id *, struct ob\n>  #define WS_CR_AT_EOL           01000\n>  #define WS_BLANK_AT_EOF        02000\n>  #define WS_TAB_IN_INDENT       04000\n> +#define WS_IGNORE_FIRST_SPACE 010000\n>  #define WS_TRAILING_SPACE      (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF)\n>  #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|8)\n>  #define WS_TAB_WIDTH_MASK        077\n>  /* All WS_* -- when extended, adapt diff.c emit_symbol */\n> -#define WS_RULE_MASK           07777\n> +#define WS_RULE_MASK           017777\n>  extern unsigned whitespace_rule_cfg;\n>  extern unsigned whitespace_rule(const char *);\n>  extern unsigned parse_whitespace_rule(const char *);\n> diff --git a/diff.c b/diff.c\n> index e163bc8a3..03ed235c7 100644\n> --- a/diff.c\n> +++ b/diff.c\n> @@ -650,14 +650,14 @@ enum diff_symbol {\n>  };\n>  /*\n>   * Flags for content lines:\n> - * 0..12 are whitespace rules\n> - * 13-15 are WSEH_NEW | WSEH_OLD | WSEH_CONTEXT\n> - * 16 is marking if the line is blank at EOF\n> + * 0..14 are whitespace rules\n> + * 14-16 are WSEH_NEW | WSEH_OLD | WSEH_CONTEXT\n> + * 17 is marking if the line is blank at EOF\n>   */\n> -#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF     (1<<16)\n> -#define DIFF_SYMBOL_MOVED_LINE                 (1<<17)\n> -#define DIFF_SYMBOL_MOVED_LINE_ALT             (1<<18)\n> -#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING   (1<<19)\n> +#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF     (1<<17)\n> +#define DIFF_SYMBOL_MOVED_LINE                 (1<<18)\n> +#define DIFF_SYMBOL_MOVED_LINE_ALT             (1<<19)\n> +#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING   (1<<20)\n>  #define DIFF_SYMBOL_CONTENT_WS_MASK (WSEH_NEW | WSEH_OLD | WSEH_CONTEXT | WS_RULE_MASK)\n>\n>  /*\n> @@ -1094,6 +1094,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n>                                 set = diff_get_color_opt(o, DIFF_FRAGINFO);\n>                         else if (c != '+')\n>                                 set = diff_get_color_opt(o, DIFF_CONTEXT);\n> +                       flags |= WS_IGNORE_FIRST_SPACE;\n>                 }\n>                 emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n>                                     flags & DIFF_SYMBOL_CONTENT_WS_MASK,\n> diff --git a/diff.h b/diff.h\n> index 79beb6eea..892416a14 100644\n> --- a/diff.h\n> +++ b/diff.h\n> @@ -160,9 +160,9 @@ struct diff_options {\n>         int abbrev;\n>         int ita_invisible_in_index;\n>  /* white-space error highlighting */\n> -#define WSEH_NEW (1<<12)\n> -#define WSEH_CONTEXT (1<<13)\n> -#define WSEH_OLD (1<<14)\n> +#define WSEH_NEW (1<<13)\n> +#define WSEH_CONTEXT (1<<14)\n> +#define WSEH_OLD (1<<15)\n>         unsigned ws_error_highlight;\n>         const char *prefix;\n>         int prefix_length;\n> diff --git a/ws.c b/ws.c\n> index a07caedd5..e02365a6a 100644\n> --- a/ws.c\n> +++ b/ws.c\n> @@ -20,6 +20,7 @@ static struct whitespace_rule {\n>         { \"blank-at-eol\", WS_BLANK_AT_EOL, 0 },\n>         { \"blank-at-eof\", WS_BLANK_AT_EOF, 0 },\n>         { \"tab-in-indent\", WS_TAB_IN_INDENT, 0, 1 },\n> +       { \"ignore-first-space\", WS_IGNORE_FIRST_SPACE, 0, 1 },\n>  };\n>\n>  unsigned parse_whitespace_rule(const char *string)\n> @@ -177,8 +178,16 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,\n>         if (trailing_whitespace == -1)\n>                 trailing_whitespace = len;\n>\n> +       if ((ws_rule & WS_IGNORE_FIRST_SPACE) && len && line[0] == ' ') {\n> +               if (stream)\n> +                       fwrite(line, 1, 1, stream);\n> +               written++;\n> +               if (!trailing_whitespace)\n> +                       trailing_whitespace++;\n> +       }\n> +\n>         /* Check indentation */\n> -       for (i = 0; i < trailing_whitespace; i++) {\n> +       for (i = written; i < trailing_whitespace; i++) {\n>                 if (line[i] == ' ')\n>                         continue;\n>                 if (line[i] != '\\t')\n> --\n> gitgitgadget\n>\n"},{"id":"353428","messageId":"xmqqy3e137wd.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"9de5bd2299eedbc78494cadc9dd8bda59430b2df.1532210683.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 14/21] diff: add an internal option to dual-color diffs of diffs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-23T22:27:14Z","receivedAt":"2018-07-23T22:27:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n>\n> When diffing diffs, it can be quite daunting to figure out what the heck\n> is going on, as there are nested +/- signs.\n>\n> Let's make this easier by adding a flag in diff_options that allows\n> color-coding the outer diff sign with inverted colors, so that the\n> preimage and postimage is colored like the diff it is.\n>\n> Of course, this really only makes sense when the preimage and postimage\n> *are* diffs. So let's not expose this flag via a command-line option for\n> now.\n>\n> This is a feature that was invented by git-tbdiff, and it will be used\n> by `git range-diff` in the next commit, by offering it via a new option:\n> `--dual-color`.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  diff.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++-----------\n>  diff.h |  1 +\n>  2 files changed, 69 insertions(+), 15 deletions(-)\n>\n> diff --git a/diff.c b/diff.c\n> index a94a8214f..e163bc8a3 100644\n> --- a/diff.c\n> +++ b/diff.c\n> @@ -563,14 +563,18 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,\n>  \tecbdata->blank_at_eof_in_postimage = (at - l2) + 1;\n>  }\n>  \n> -static void emit_line_0(struct diff_options *o, const char *set, const char *reset,\n> +static void emit_line_0(struct diff_options *o,\n> +\t\t\tconst char *set, unsigned reverse, const char *reset,\n>  \t\t\tint first, const char *line, int len)\n>  {\n>  \tint has_trailing_newline, has_trailing_carriage_return;\n>  \tint nofirst;\n>  \tFILE *file = o->file;\n>  \n> -\tfputs(diff_line_prefix(o), file);\n> +\tif (first)\n> +\t\tfputs(diff_line_prefix(o), file);\n> +\telse if (!len)\n> +\t\treturn;\n\nCan you explain this hunk in the log message?  I am not sure how the\ndescription in the log message relates to this change.  Is the idea\nof this change essentially \"all the existing callers that aren't\ndoing the diff-of-diffs send a non-NUL first character, and for them\nthis change is a no-op.  New callers share most of the remainder of\nemit_line_0() logic but do not want to show the prefix, so the\nsupport for it is piggy-backing by a special case where first could\nbe NUL\"?\n"},{"id":"353429","messageId":"xmqqtvop37c1.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"f4252f2b2198cf13d5b0a21c54098e2a1d8158dd.1532210683.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 16/21] range-diff --dual-color: fix bogus white-space warning","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-23T22:39:26Z","receivedAt":"2018-07-23T22:39:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> @@ -177,8 +178,16 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,\n>  \tif (trailing_whitespace == -1)\n>  \t\ttrailing_whitespace = len;\n>  \n> +\tif ((ws_rule & WS_IGNORE_FIRST_SPACE) && len && line[0] == ' ') {\n> +\t\tif (stream)\n> +\t\t\tfwrite(line, 1, 1, stream);\n> +\t\twritten++;\n> +\t\tif (!trailing_whitespace)\n> +\t\t\ttrailing_whitespace++;\n> +\t}\n> +\n>  \t/* Check indentation */\n> -\tfor (i = 0; i < trailing_whitespace; i++) {\n> +\tfor (i = written; i < trailing_whitespace; i++) {\n\nIt is pleasing to see that with a surprisingly clean and small\nchange like this we can exempt the initial space byte from\nSP-before-HT check and from Indent-with-non-tab at the same time.\n\nVery nice.\n\nOne reason why a surprisingly small special case is required is\nperhaps because we are blessed with the original code being clean\n[*1*], and the fact that a line[0] that is not ' ' will not trigger\nany indentation related whitespace errors without this special case,\nI guess.\n\n>  \t\tif (line[i] == ' ')\n>  \t\t\tcontinue;\n>  \t\tif (line[i] != '\\t')\n\n\n[Footnote]\n\n*1* ws.c used to be almost all my code long time ago, but most of\n    the shape of the current whitespace_error checking code comes from\n    c1795bb08aa which is not mine, and I can say good things about it\n    without feeling embarrassingly boasty ;-)\n"},{"id":"353430","messageId":"CAGZ79kZrJRQWPzojjhQJGRZWSNvLnZ0C++9XggMVp2wuFNnnLQ@mail.gmail.com","threadId":"48405","inReplyTo":"xmqqy3e137wd.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v4 14/21] diff: add an internal option to dual-color diffs of diffs","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-23T22:48:24Z","receivedAt":"2018-07-23T22:48:38Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"> > -     fputs(diff_line_prefix(o), file);\n> > +     if (first)\n> > +             fputs(diff_line_prefix(o), file);\n> > +     else if (!len)\n> > +             return;\n>\n> Can you explain this hunk in the log message?  I am not sure how the\n> description in the log message relates to this change.  Is the idea\n> of this change essentially \"all the existing callers that aren't\n> doing the diff-of-diffs send a non-NUL first character, and for them\n> this change is a no-op.  New callers share most of the remainder of\n> emit_line_0() logic but do not want to show the prefix, so the\n> support for it is piggy-backing by a special case where first could\n> be NUL\"?\n\nAll but two caller have 'reverse' set to 0, using the arguments as before.\n\nThe other two callers are using the function twice to get the prefix\nand set sign going, and then the second call to get the rest of the\nline going (which needs to omit the prefix as that was done in the\nfirst call) :\n\n+               /* Emit just the prefix, then the rest. */\n+               emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n+                           sign, \"\", 0);\n+               emit_line_0(o, set, 0, reset, 0, line, len);\n\nI attempted to clean it up on top, but likely got it wrong as we have\nno tests for colored range diffs, yet.\nhttps://public-inbox.org/git/20180710174552.30123-3-sbeller@google.com/\nMy suggestion would be to first clarify emit_line_0 and have its arguments\nand its execution map better to each other, (and as a result only needing to\nhave one call of emit_line_0 instead of two)\n\nThat is my understanding of the situation.\n\nThanks,\nStefan\n"},{"id":"353437","messageId":"xmqq7ell2zkh.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"xmqqtvop37c1.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v4 16/21] range-diff --dual-color: fix bogus white-space warning","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-24T01:27:10Z","receivedAt":"2018-07-24T01:27:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> It is pleasing to see that with a surprisingly clean and small\n> change like this we can exempt the initial space byte from\n> SP-before-HT check and from Indent-with-non-tab at the same time.\n>\n> Very nice.\n>\n> One reason why a surprisingly small special case is required is\n> perhaps because we are blessed with the original code being clean\n> [*1*], and the fact that a line[0] that is not ' ' will not trigger\n> any indentation related whitespace errors without this special case,\n> I guess.\n\nHaving said good things about the patch, I unfortunately realized\nthat we weren't that lucky.  As we do want to see whitespace errors\non lines with line[0] != ' ' to be flagged.  So \"... will not\ntrigger\" in the above is not a blessing, but something that further\nneeds to be fixed.  The special case should also be made for line[0]\nthat is '+' and possibly '-' (and I also suspect that the changes in\nthis patch may mostly be reusable with little tweaks if any).\n\nImagine we start from this commit that \"git show\" shows us like so:\n\n\t int main(int ac, char **av)\n\t {\n\t ________  printf(\"Hello\");\n\t+________  putchar(',');\n\t+          putchar(' ');\n\t           printf(\"World\\n\");\n\t           return 0;\n\t }\n\nI've drawn a horizontal-tab as long underscore to clarify in the\nabove picture.  If you have \"core.whitespace=indent-with-non-tab\"\n\"git show\" would paint the line that adds \" \" as violating (as it\ntypes 10 SPs, when it could have been a tab and 2 SPs), but it does\nnot highlight the line with \"World\" or \"return\", which is sensible\nas they are pre-existing violations.\n\nThen imagine we did \"git commit --amend\" and \"git show\" would give\nthis instead:\n\n\t int main(int ac, char **av)\n\t {\n\t ________  printf(\"Hello\");\n\t+          putchar(',');\n\t+________  putchar(' ');\n\t           printf(\"World\\n\");\n\t           return 0;\n\t }\n\nThat is, relative to the previous attempt, we stopped introducing\nnew indent-with-non-tab violation to the line that adds \" \", but\nadded a new violation to the line that adds \",\".\n\nAfter such \"git commit --amend\", what do we want to see in the\noutput from \"git range-diff @{1}...\"?\n\nMy quick test of the current code does not show any whitespace\nbreakage for either versions.  I *think* what we want to actually\nsee is\n\n - just like WS_IGNORE_FIRST_SPACE logic shifted the column by\n   incrementing written (and final condition) for the loop in your\n   patch for line[0]==' ', detect the overlong run of SP correctly\n   by ignoring the first column that is '+', and complaining that\n   the new commit is typing 10 SPs before putchar(',').\n\n - Earlier I thought that lines with '-' in the outer diff should\n   become exempt from whitespace-error highlighting, but I think\n   that is a mistake.  If a line in diff-of-diff that begins with\n   \"-+\" has whitespace violation (e.g \"-+\" followed by 10 SPs), that\n   is \"the old version of the patch used to introduce whitespace\n   violation\", which is a useful piece of information when we look\n   at the corresponding line in the same diff-of-diff that begins\n   with \"++\".  We could either say \"and you cleaned that mistake up\n   in your newer patch\", or \"and you still have that mistake in your\n   newer patch\" (another possibility is there is no whitespace error\n   on a \"-+\" line, and the corresponding \"++\" line has one---\"you\n   got it right in the previous round, but you somehow made it\n   worse\").\n\nHere is the reproduction of the sample data I based on the above\nthought experiment on.\n\ndiff --git a/script.sh b/script.sh\nnew file mode 100755\nindex 0000000..0090661\n--- /dev/null\n+++ b/script.sh\n@@ -0,0 +1,48 @@\n+#!/bin/sh\n+\n+git init\n+git config core.whitespace indent-with-non-tab\n+\n+tr _ '\\011' <<\\EOF >hello.c\n+int main(int ac, char **av)\n+{\n+_  printf(\"Hello\");\n+          printf(\"World\\n\");\n+          return 0;\n+}\n+EOF\n+\n+git add hello.c && git commit -m initial\n+\n+tr _ '\\011' <<\\EOF >hello.c\n+int main(int ac, char **av)\n+{\n+_  printf(\"Hello\");\n+          putchar(',');\n+_  putchar(' ');\n+          printf(\"World\\n\");\n+          return 0;\n+}\n+EOF\n+\n+git commit -a -m second\n+\n+tr _ '\\011' <<\\EOF >hello.c\n+int main(int ac, char **av)\n+{\n+_  printf(\"Hello\");\n+_  putchar(',');\n+          putchar(' ');\n+          printf(\"World\\n\");\n+          return 0;\n+}\n+EOF\n+\n+git commit -a --amend -m third\n+\n+\n+git show @{1}\n+git show HEAD\n+\n+git range-diff ..@{1} @{1}..\n+\n-- \n2.18.0-232-gb7bd9486b0\n\n\n\n"},{"id":"353438","messageId":"xmqqlga11jwp.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"20180723012552.GA26886@sigill.intra.peff.net","subject":"Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-24T01:50:46Z","receivedAt":"2018-07-24T01:50:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> If I understand the situation correctly, Junio is saying that he will\n> continue to produce the amlog mapping, and that it contains sufficient\n> information to produce the reverse mapping (which, as an aside, I did\n> not even know existed -- I mostly want to go the other way, from digging\n> in history to a mailing list conversation).\n\nYes, the reverse mapping in amlog was an experiment that did not\nwork well in the end.\n\nWhen I use \"git am\" to make a commit out of a message, a\npost-applypatch hook picks up the \"Message-Id:\" from the original\nmessage and adds a git note to the resulting commit.  This is in\nline with how the notes are meant to be used.  We have a commit\nobject, and a piece of information that we want to associate with\nthe commit object, which is not recorded as a part of the commit\nobject.  So we say \"git notes add -m 'that piece of info' $commit\"\n(the message-id happens to be that piece of info in this example).\n\nAnd with notes.rewriteRef, \"git commit --amend\" etc. would copy the\npiece of info about the original commit to the rewritten commit.\n\n\tSide Note: there are a few workflow elements I do want to\n\tkeep using but they currently *lose* the mapping info.  An\n\tobvious one is\n\n\t  $ git checkout -b to/pic master &&\n\t  ... review in MUA and then ...\n\t  $ git am -s mbox &&\n\t  ... review in tree, attempt to build, tweak, etc.\n          $ git format-patch --stdout master..to/pic >P &&\n          $ edit P &&\n          $ git reset --hard master &&\n          $ git am P\n\n\twhich is far more versatile and efficient when doing certain\n\ttransformations on the series than running \"rebase -i\" and\n\treopening and editing the target files of the patches one by\n\tone in each step.  But because format-patch does not\n\tgenerate Message-Id header of the original one out of the\n\tcommit, the post-applypatch hook run by \"am\" at the end of\n\tthe steps would not have a chance to record that for the\n\tnewly created commit.\n\n\tFor this one, I think I can use \"format-patch --notes=amlog\"\n\tto produce the patch file and then teach post-applypatch\n\tscript to pay attention to the Notes annotation without\n\tchanging anything else to record the message id of the\n\toriginal.  Other workflow elements that lose the notes need\n\tto be identified and either a fix implemented or a\n\tworkaround found for each of them.  For example, I suspect\n\tthere is no workaround for \"cherry-pick\" and it would take a\n\treal fix.\n\nA reverse mapping entry used to get created by post-applypatch to\nmap the blob that represents the notes text added to the $commit to\nanother text blob that contains the 40-hex of the commit object.\nThis is the experiment that did not work well.  As none of the later\nintegrator's work e.g. \"commit --amend\", \"rebase\", \"cherry-pick\",\netc. is about rewriting that blob, notes.rewriteRef mechanism would\nnot kick in, and that is understandasble.\n\nAnd these (incomplete) reverse mapping entries get in the way to\nmaintain and correct the forward mapping.  When a commit that got\nunreachable gets expired, I want \"git notes prune\" to remove notes\non them, and I do not want to even think about what should happen to\nthe entries in the notes tree that abuse the mechanism to map blobs\nthat are otherwise *not* even reachable from the main history.\n\nA much more important task is to make sure that the forward mapping\nthat annotates invidual commits reachable from 'pu' and/or 'master' \nis maintained correctly by various tools.  From a correctly maintained\nforward mapping, it should be straight forward to get a reverse mapping\nif needed.\n\n> Though personally, I do not know if there is much point in pushing it\n> out, given that receivers can reverse the mapping themselves.\n\nBefore this thread, I was planning to construct and publish the\nreverse mapping at the end of the day, but do so on a separate notes\nref (see above---the hacky abuse gets in the way of maintaining and\ndebugging the forward mapping, but a separate notes-ref that only\ncontains hacks is less worrysome).  But I have changed my mind and\ndecided not to generate or publish one.  It is sort of similar to\nthe way the pack .idx is constructed only by the receiver [*1*].\n\n> Or is there some argument that there is information in the reverse map\n> that _cannot_ be generated from the forward map?\n\nI know there is no information loss (after all I was the only one\nwho ran that experimental hack), but there is one objection that is\nstill possible, even though I admit that is a weak argument.\n\nIf a plumbing \"diff-{files,tree,index}\" family had a sibling\n\"diff-notes\" to compare two notes-shaped trees while pretending that\nthe object-name fan-out did not exist (i.e. instead, the trees being\ncompared is without a subtree and full of 40-hex filenames), then it\nwould be less cumbersome to incrementally update the reverse mapping\nby reading forward mapping with something like:\n\n\tgit diff-notes --raw amlog@{1} amlog\n\nto learn the commits whose notes have changed.  But without such a\nplumbing, it is cumbersome to do so correctly.  \"git diff-tree -r\"\ncould serve as a rough substitute, until the note tree grows and get\nrebalanced by reorganizing the fan-out, and on the day it happens\nthe reverse mapper needs to read and discard ghost changes that are\nonly due to tree reorganizing [*2*].\n\n\n[Footnotes]\n\n*1* Even if the sender could give one when it creates a .pack, the\n    receiver would not trust that it is matches the corresponding\n    .pack before using it, and the cost to validate is similar to\n    the cost to generate.\n\n*2* That makes it less efficient on that day (which hopefully would\n    happen once in a blue moon) but would not affect correctness.\n"},{"id":"353452","messageId":"20180724094501.GA3578@sigill.intra.peff.net","threadId":"48405","inReplyTo":"xmqqlga11jwp.fsf@gitster-ct.c.googlers.com","subject":"Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-07-24T09:45:01Z","receivedAt":"2018-07-24T09:45:04Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jul 23, 2018 at 06:50:46PM -0700, Junio C Hamano wrote:\n\n> \tSide Note: there are a few workflow elements I do want to\n> \tkeep using but they currently *lose* the mapping info.  An\n> \tobvious one is\n> \n> \t  $ git checkout -b to/pic master &&\n> \t  ... review in MUA and then ...\n> \t  $ git am -s mbox &&\n> \t  ... review in tree, attempt to build, tweak, etc.\n>           $ git format-patch --stdout master..to/pic >P &&\n>           $ edit P &&\n>           $ git reset --hard master &&\n>           $ git am P\n> \n> \twhich is far more versatile and efficient when doing certain\n> \ttransformations on the series than running \"rebase -i\" and\n> \treopening and editing the target files of the patches one by\n> \tone in each step.  But because format-patch does not\n> \tgenerate Message-Id header of the original one out of the\n> \tcommit, the post-applypatch hook run by \"am\" at the end of\n> \tthe steps would not have a chance to record that for the\n> \tnewly created commit.\n> \n> \tFor this one, I think I can use \"format-patch --notes=amlog\"\n> \tto produce the patch file and then teach post-applypatch\n> \tscript to pay attention to the Notes annotation without\n> \tchanging anything else to record the message id of the\n> \toriginal.\n\nYes. I wonder if it would make sense to teach format-patch/am a\nmicro-format to automatically handle this case. I.e., some\nmachine-readable way of passing the notes in the email message.\nOf course it's easy to design a format that covers the relatively\nrestricted form of these amlog notes, and much harder to cover the\ngeneral case.\n\n>       Other workflow elements that lose the notes need\n> \tto be identified and either a fix implemented or a\n> \tworkaround found for each of them.  For example, I suspect\n> \tthere is no workaround for \"cherry-pick\" and it would take a\n> \treal fix.\n\nI think the existing notes.rewriteRef is probably a good match here. I\ncan definitely think of notes you wouldn't want to cherry-pick, but I'm\nhaving trouble coming up with an example that should survive a rebase\nbut not a cherry-pick.\n\n> And these (incomplete) reverse mapping entries get in the way to\n> maintain and correct the forward mapping.  When a commit that got\n> unreachable gets expired, I want \"git notes prune\" to remove notes\n> on them, and I do not want to even think about what should happen to\n> the entries in the notes tree that abuse the mechanism to map blobs\n> that are otherwise *not* even reachable from the main history.\n\nRight, I think the notes tree is a poor distribution method for that\nreason.\n\n> > Though personally, I do not know if there is much point in pushing it\n> > out, given that receivers can reverse the mapping themselves.\n> \n> Before this thread, I was planning to construct and publish the\n> reverse mapping at the end of the day, but do so on a separate notes\n> ref (see above---the hacky abuse gets in the way of maintaining and\n> debugging the forward mapping, but a separate notes-ref that only\n> contains hacks is less worrysome).  But I have changed my mind and\n> decided not to generate or publish one.  It is sort of similar to\n> the way the pack .idx is constructed only by the receiver [*1*].\n\nYes, the pack .idx was the same mental model I had when writing my\nearlier message.\n\n> > Or is there some argument that there is information in the reverse map\n> > that _cannot_ be generated from the forward map?\n> \n> I know there is no information loss (after all I was the only one\n> who ran that experimental hack), but there is one objection that is\n> still possible, even though I admit that is a weak argument.\n\nI wondered if you might have a case like this (building as we go):\n\n - message-id M becomes commit X\n   - we write the forward map X->M\n   - we write the reverse map M->X\n - during a rewrite (e.g., --amend), commit X becomes commit Y\n   - we write the forward map Y->M\n   - we write the reverse map M->Y\n\nThe difference between that result and an inverted map created at the\nend is that we know that M->Y is the final result. Whereas by looking at\nthe inverted map, we do not know which of M->X and M->Y is correct. In\nfact they are _both_ correct. But only one of X and Y would eventually\nget merged (both may make it into the repo's of people fetching from you\nif we imagine that X is on \"pu\" and you push between the two steps).\n\nSo I think the inverted mapping is not actually one-to-one, and in\neither case you'd want to retain all possible matches (pruning only when\na commit is eventually dropped from the forward mapping, which rewritten\nthings from \"pu\" would eventually do). And in that case it does not\nmatter if you generate it incrementally or all at once.\n\n> If a plumbing \"diff-{files,tree,index}\" family had a sibling\n> \"diff-notes\" to compare two notes-shaped trees while pretending that\n> the object-name fan-out did not exist (i.e. instead, the trees being\n> compared is without a subtree and full of 40-hex filenames), then it\n> would be less cumbersome to incrementally update the reverse mapping\n> by reading forward mapping with something like:\n> \n> \tgit diff-notes --raw amlog@{1} amlog\n> \n> to learn the commits whose notes have changed.  But without such a\n> plumbing, it is cumbersome to do so correctly.  \"git diff-tree -r\"\n> could serve as a rough substitute, until the note tree grows and get\n> rebalanced by reorganizing the fan-out, and on the day it happens\n> the reverse mapper needs to read and discard ghost changes that are\n> only due to tree reorganizing [*2*].\n\nYeah. My \"log\" hackery was trying to do that incremental comparison, but\nit did not handle the multiple-commit case (nor did it handle\ndeletions). I agree an end-point diff is sufficient (and more\nefficient).\n\n-Peff\n"},{"id":"353577","messageId":"CAGZ79kZVQZkriu8d9WwYjm01qwb89ntf2dpK_jxRqDHgL1Eq6Q@mail.gmail.com","threadId":"48405","inReplyTo":"xmqq36w94o87.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-25T17:44:35Z","receivedAt":"2018-07-25T17:44:49Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Jul 23, 2018 at 2:49 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Stefan Beller <sbeller@google.com> writes:\n>\n> > On Sat, Jul 21, 2018 at 3:04 PM Johannes Schindelin via GitGitGadget\n> > <gitgitgadget@gmail.com> wrote:\n> >\n> >> Side note: I work on implementing range-diff not only to make life easier for reviewers who have to suffer through v2, v3, ... of my patch series, but also to verify my changes before submitting a new iteration. And also, maybe even more importantly, I plan to use it to verify my merging-rebases of Git for\n> >> Windows (for which I previously used to redirect the pre-rebase/post-rebase diffs vs upstream and then compare them using `git diff --no-index`). And of course any interested person can see what changes were necessary e.g. in the merging-rebase of Git for Windows onto v2.17.0 by running a command like:\n> >\n> > Thanks for making tools that makes the life of a Git developer easier!\n> > (Just filed https://github.com/gitgitgadget/gitgitgadget/issues/26\n> > which asks to break lines for this cover letter)\n>\n> Thanks.  These cover letters are unreadable without W Q\n> (gnus-article-fill-long-lines)\n\nWhile I had some comments on how I dislike some aspects of the\nimplementation, I think it proves its usefulness by my usage, so I\nwould suggest to merge it down to next as-is (and as soon as possible);\ndeferring the issues in the implementation to later.\n\nI found running the range-diff on origin/pu to be a pleasant\nexperience, although that still highlights the issues of\nin-exact coloring (the colors are chosen by the first two\ncharacters of the diff, which leads to mis-coloring of\ndiff headers of the inner diff in the outer diff.\n\nBut despite the imperfection, I strongly urge to consider\nthe series as-is as good enough for inclusion.\n\nThanks,\nStefan\n\nThanks,\nStefan\n"},{"id":"353624","messageId":"nycvar.QRO.7.76.6.1807261121570.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAGZ79kb4ki0cXLnJHeqzRvWaGWki1_epWOdCy49s_v9cy_tJ2A@mail.gmail.com","subject":"Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-26T09:47:57Z","receivedAt":"2018-07-26T09:48:10Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Mon, 23 Jul 2018, Stefan Beller wrote:\n\n> On Sat, Jul 21, 2018 at 3:04 PM Johannes Schindelin via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> \n> > Range-diff vs v3:\n> >\n> >   1:  39272eefc !  1:  f7e70689e linear-assignment: a function to solve least-cost assignment problems\n> >      @@ -223,9 +223,7 @@\n> >       +                         BUG(\"negative j: %d\", j);\n> >       +                 i = pred[j];\n> >       +                 column2row[j] = i;\n> >      -+                 k = j;\n> >      -+                 j = row2column[i];\n> >      -+                 row2column[i] = k;\n> >      ++                 SWAP(j, row2column[i]);\n> \n> The dual color option (as a default) really helps here. Thanks for that!\n> Does it have to be renamed though? (It's more than two colors; originally\n> it was inverting the beginning signs)\n\nI understand (and understood) the \"dual\" to mean that there are two\nseparate coloring methods, the coloring of the inner, and the coloring of\nthe outer diff. (And in my mind, the dimming is not so much an \"inner\"\ndiff things as an \"outer\" diff thing.)\n\n> Maybe --color=emphasize-later assuming there will be other modes for\n> coloring, such as \"diff-only\", which would correspond with\n> --no-dual-color, or \"none\" that will correspond would be --no-color. I\n> imagine there could be more fancy things, hence I would propose a mode\n> rather than a switch.  (Feel free to push back if discussing naming here\n> feels like bike shedding)\n\nYour suggestion does not really feel like bike-shedding to me, here, I can\nsee the merit of it.\n\nIt's just that 1) overloading `--color` here would be cumbersome, as\n`--color` is *already* a diff option that we actually use, and 2) I am not\nall that certain that new fancy things will crop up anytime soon. It was\nhard enough to think of the dimming feature, and then implementing it.\n\nSo while I think your idea has merit, I still think that we can do that\nlater. The --no-dual-color option can easily be deprecated in favor of,\nsay, --color-mode=<mode>, when (and if) new modes crop up.\n\n> 2:  7f15b26d4ea !  82:  88134121d2a Introduce `range-diff` to compare\n> iterations of a topic branch\n> [...]\n> >       diff --git a/Makefile b/Makefile\n> >       --- a/Makefile\n> >       +++ b/Makefile\n> \n> The line starting with --- is red (default removed color) and the line\n> with +++ is green (default add color).\n> \n> Ideally these two lines and the third line above starting with \"diff --git\"\n> would render in GIT_COLOR_BOLD (\"METAINFO\").\n\nI see where you are coming from, but given how complicated it seems to me\nto implement this (dual color mode gets away with working line-based for\nthe moment, and what you ask for would require state, and would not even\nbe fool-proof, as the `diff --git` line might not even be part of the\ncontext.\n\nSeeing how long this patch series has already simmered, I would want to\ninvoke that old adage \"the perfect is the enemy of the good\", and rather\nsee a version of range-diff enter Git's source code, if need be marked as\n\"EXPERIMENTAL\" so that the maintainer can claim that it is okay to be\nbuggy, and then invite contributions from other sides than from me.\n\n> >  16:  dfa7b1e71 <  -:  --------- range-diff --dual-color: work around bogus white-space warning\n> >   -:  --------- > 16:  f4252f2b2 range-diff --dual-color: fix bogus white-space warning\n> \n> Ah; here my initial assumption of only reviewing the range-diff breaks down now.\n> I'll dig into patch 16 separately.\n\nRight. In this case, it is a total rewrite, anyway (and I'll have to ask\nyou to overlook my frustration with how complex and hard it is to work on\nws.c without breaking anything). For the sake of review, you should ignore\nthe old patch. Unless you find that the new version is so complex and\nprone to introduce bugs (with which I would agree) that we should go back\nto the original workaround, which is so easy to understand that there are\nno obvious bugs lurking inside it.\n\n> Maybe it is worth having an option to expand all \"new\" patches.\n\nSure. And I would love to have this in a separate patch series, as it is\nwell beyond the original purpose of this patch series to simply make\ntbdiff a builtin.\n\nI should have known better, of course, but I was really not keen on\nimproving range-diff *before* it enters the code base, to the point of\nintroducing new features that might very well introduce new regressions in\nunrelated commands, too.\n\nEssentially, I am declaring a feature-freeze on this patch series until it\nenters `next`.\n\n> (Given that the range-diff\n> pr-1/dscho/branch-diff-v3...pr-1/dscho/branch-diff-v4 told me you used a\n> different base, this is a hard problem, as I certainly would want to\n> skip over all new base commits, but this one is interesting to look at.\n> An easy way out: Maybe an option to expand any new commits/patches after\n> the first expansion? Asking for opinions rather than implementing it)\n\nAny fulfilled wish is immediately welcomed with offspring, it seems.\n\nAgain, this is a very nice feature, I think, and even nicer: it can be\nimplemented by somebody else than me, on top of my patch series, after it\nstabilized and entered `next`.\n\n> >  19:  144363006 <  -:  --------- range-diff: left-pad patch numbers\n> >   -:  --------- > 19:  07ec215e8 range-diff: left-pad patch numbers\n\nYes, this is something where I would have used a different\n`--creation-factor` locally, but I did not want to hack up GitGitGadget\n*just* for this patch series.\n\n> >   -:  --------- > 21:  d8498fb32 range-diff: use dim/bold cues to improve dual color mode\n> \n> Those are interesting, I'll look at them separately, too.\n\nThat last one is indeed very interesting, from a feature point of view,\nand a little intimidating from a review point of view: it entered the\npatch series only in v4. Combined, this is a clear sign that I should not\nhave included that feature in this here patch series. But I did fall prey\nto \"featuritis\". I'll try to be better about this. The most important\nthing now is to stabilize the `range-diff` command and to get it included.\nIt already simmers for way too long.\n\nCiao,\nDscho\n"},{"id":"353790","messageId":"20180728084652.GB2734@hank.intra.tgummerer.com","threadId":"48405","inReplyTo":"f7e70689efcbeb8341c19fa3940c818142a2cddf.1532210683.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 01/21] linear-assignment: a function to solve least-cost assignment problems","fromName":"Thomas Gummerer","fromEmail":"t.gummerer@gmail.com","sentAt":"2018-07-28T08:46:52Z","receivedAt":"2018-07-28T08:46:57Z","isPatch":true,"sender":{"key":"t.gummerer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/191004?v=4"},"body":"On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> \n> The problem solved by the code introduced in this commit goes like this:\n> given two sets of items, and a cost matrix which says how much it\n> \"costs\" to assign any given item of the first set to any given item of\n> the second, assign all items (except when the sets have different size)\n> in the cheapest way.\n> \n> We use the Jonker-Volgenant algorithm to solve the assignment problem to\n> answer questions such as: given two different versions of a topic branch\n> (or iterations of a patch series), what is the best pairing of\n> commits/patches between the different versions?\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  Makefile            |   1 +\n>  linear-assignment.c | 201 ++++++++++++++++++++++++++++++++++++++++++++\n>  linear-assignment.h |  22 +++++\n>  3 files changed, 224 insertions(+)\n>  create mode 100644 linear-assignment.c\n>  create mode 100644 linear-assignment.h\n>\n> [...]\n> \n> diff --git a/linear-assignment.h b/linear-assignment.h\n> new file mode 100644\n> index 000000000..fc4c502c8\n> --- /dev/null\n> +++ b/linear-assignment.h\n> @@ -0,0 +1,22 @@\n> +#ifndef HUNGARIAN_H\n> +#define HUNGARIAN_H\n\nnit: maybe s/HUNGARIAN/LINEAR_ASSIGNMENT/ in the two lines above.\n\n> +\n> +/*\n> + * Compute an assignment of columns -> rows (and vice versa) such that every\n> + * column is assigned to at most one row (and vice versa) minimizing the\n> + * overall cost.\n> + *\n> + * The parameter `cost` is the cost matrix: the cost to assign column j to row\n> + * i is `cost[j + column_count * i].\n> + *\n> + * The arrays column2row and row2column will be populated with the respective\n> + * assignments (-1 for unassigned, which can happen only if column_count !=\n> + * row_count).\n> + */\n> +void compute_assignment(int column_count, int row_count, int *cost,\n> +\t\t\tint *column2row, int *row2column);\n> +\n> +/* The maximal cost in the cost matrix (to prevent integer overflows). */\n> +#define COST_MAX (1<<16)\n> +\n> +#endif\n> -- \n> gitgitgadget\n> \n"},{"id":"353829","messageId":"20180729183629.GC2734@hank.intra.tgummerer.com","threadId":"48405","inReplyTo":"4e3fb47a1dcef96780bd536032b81dd99387f2db.1532210683.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 03/21] range-diff: first rudimentary implementation","fromName":"Thomas Gummerer","fromEmail":"t.gummerer@gmail.com","sentAt":"2018-07-29T18:36:29Z","receivedAt":"2018-07-29T18:36:36Z","isPatch":true,"sender":{"key":"t.gummerer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/191004?v=4"},"body":"On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> \n> At this stage, `git range-diff` can determine corresponding commits\n> of two related commit ranges. This makes use of the recently introduced\n> implementation of the linear assignment algorithm.\n> \n> The core of this patch is a straight port of the ideas of tbdiff, the\n> apparently dormant project at https://github.com/trast/tbdiff.\n> \n> The output does not at all match `tbdiff`'s output yet, as this patch\n> really concentrates on getting the patch matching part right.\n> \n> Note: due to differences in the diff algorithm (`tbdiff` uses the Python\n> module `difflib`, Git uses its xdiff fork), the cost matrix calculated\n> by `range-diff` is different (but very similar) to the one calculated\n> by `tbdiff`. Therefore, it is possible that they find different matching\n> commits in corner cases (e.g. when a patch was split into two patches of\n> roughly equal length).\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  Makefile             |   1 +\n>  builtin/range-diff.c |  43 +++++-\n>  range-diff.c         | 311 +++++++++++++++++++++++++++++++++++++++++++\n>  range-diff.h         |   7 +\n>  4 files changed, 361 insertions(+), 1 deletion(-)\n>  create mode 100644 range-diff.c\n>  create mode 100644 range-diff.h\n>\n> [...]\n> \n> diff --git a/range-diff.c b/range-diff.c\n> new file mode 100644\n> index 000000000..15d418afa\n> --- /dev/null\n> +++ b/range-diff.c\n> @@ -0,0 +1,311 @@\n> +#include \"cache.h\"\n> +#include \"range-diff.h\"\n> +#include \"string-list.h\"\n> +#include \"run-command.h\"\n> +#include \"argv-array.h\"\n> +#include \"hashmap.h\"\n> +#include \"xdiff-interface.h\"\n> +#include \"linear-assignment.h\"\n> +\n> +struct patch_util {\n> +\t/* For the search for an exact match */\n> +\tstruct hashmap_entry e;\n> +\tconst char *diff, *patch;\n> +\n> +\tint i;\n> +\tint diffsize;\n> +\tsize_t diff_offset;\n> +\t/* the index of the matching item in the other branch, or -1 */\n> +\tint matching;\n> +\tstruct object_id oid;\n> +};\n> +\n> +/*\n> + * Reads the patches into a string list, with the `util` field being populated\n> + * as struct object_id (will need to be free()d).\n> + */\n> +static int read_patches(const char *range, struct string_list *list)\n> +{\n> +\tstruct child_process cp = CHILD_PROCESS_INIT;\n> +\tFILE *in;\n> +\tstruct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;\n> +\tstruct patch_util *util = NULL;\n> +\tint in_header = 1;\n> +\n> +\targv_array_pushl(&cp.args, \"log\", \"--no-color\", \"-p\", \"--no-merges\",\n> +\t\t\t\"--reverse\", \"--date-order\", \"--decorate=no\",\n> +\t\t\t\"--no-abbrev-commit\", range,\n> +\t\t\tNULL);\n\nCompared to tbdiff, add \"--decorate=no\", and \"--no-abbrev-commit\".  I\nsee we're abbreviating the commit hashes later.  We don't want ref\nnames here, so \"--decorate=no\" makes sense as well.\n\n> +\tcp.out = -1;\n> +\tcp.no_stdin = 1;\n> +\tcp.git_cmd = 1;\n> +\n> +\tif (start_command(&cp))\n> +\t\treturn error_errno(_(\"could not start `log`\"));\n> +\tin = fdopen(cp.out, \"r\");\n> +\tif (!in) {\n> +\t\terror_errno(_(\"could not read `log` output\"));\n> +\t\tfinish_command(&cp);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\twhile (strbuf_getline(&line, in) != EOF) {\n> +\t\tconst char *p;\n> +\n> +\t\tif (skip_prefix(line.buf, \"commit \", &p)) {\n> +\t\t\tif (util) {\n> +\t\t\t\tstring_list_append(list, buf.buf)->util = util;\n> +\t\t\t\tstrbuf_reset(&buf);\n> +\t\t\t}\n> +\t\t\tutil = xcalloc(sizeof(*util), 1);\n> +\t\t\tif (get_oid(p, &util->oid)) {\n> +\t\t\t\terror(_(\"could not parse commit '%s'\"), p);\n> +\t\t\t\tfree(util);\n> +\t\t\t\tstring_list_clear(list, 1);\n> +\t\t\t\tstrbuf_release(&buf);\n> +\t\t\t\tstrbuf_release(&line);\n> +\t\t\t\tfclose(in);\n> +\t\t\t\tfinish_command(&cp);\n> +\t\t\t\treturn -1;\n> +\t\t\t}\n> +\t\t\tutil->matching = -1;\n> +\t\t\tin_header = 1;\n> +\t\t\tcontinue;\n> +\t\t}\n> +\n> +\t\tif (starts_with(line.buf, \"diff --git\")) {\n> +\t\t\tin_header = 0;\n> +\t\t\tstrbuf_addch(&buf, '\\n');\n> +\t\t\tif (!util->diff_offset)\n> +\t\t\t\tutil->diff_offset = buf.len;\n> +\t\t\tstrbuf_addbuf(&buf, &line);\n> +\t\t} else if (in_header) {\n> +\t\t\tif (starts_with(line.buf, \"Author: \")) {\n> +\t\t\t\tstrbuf_addbuf(&buf, &line);\n> +\t\t\t\tstrbuf_addstr(&buf, \"\\n\\n\");\n> +\t\t\t} else if (starts_with(line.buf, \"    \")) {\n> +\t\t\t\tstrbuf_addbuf(&buf, &line);\n> +\t\t\t\tstrbuf_addch(&buf, '\\n');\n> +\t\t\t}\n> +\t\t\tcontinue;\n> +\t\t} else if (starts_with(line.buf, \"@@ \"))\n> +\t\t\tstrbuf_addstr(&buf, \"@@\");\n> +\t\telse if (!line.buf[0] || starts_with(line.buf, \"index \"))\n> +\t\t\t/*\n> +\t\t\t * A completely blank (not ' \\n', which is context)\n> +\t\t\t * line is not valid in a diff.  We skip it\n> +\t\t\t * silently, because this neatly handles the blank\n> +\t\t\t * separator line between commits in git-log\n> +\t\t\t * output.\n> +\t\t\t *\n> +\t\t\t * We also want to ignore the diff's `index` lines\n> +\t\t\t * because they contain exact blob hashes in which\n> +\t\t\t * we are not interested.\n> +\t\t\t */\n> +\t\t\tcontinue;\n> +\t\telse\n> +\t\t\tstrbuf_addbuf(&buf, &line);\n> +\n> +\t\tstrbuf_addch(&buf, '\\n');\n> +\t\tutil->diffsize++;\n> +\t}\n\nThis seems to differ from tbdiff in the number of newlines we're\nadding in various places, however I think as long as it's consistent\nin itself, and with the way we're printing the output the differences\nshouldn't matter.\n\n> +\tfclose(in);\n> +\tstrbuf_release(&line);\n> +\n> +\tif (util)\n> +\t\tstring_list_append(list, buf.buf)->util = util;\n> +\tstrbuf_release(&buf);\n> +\n> +\tif (finish_command(&cp))\n> +\t\treturn -1;\n> +\n> +\treturn 0;\n> +}\n> +\n> +static int patch_util_cmp(const void *dummy, const struct patch_util *a,\n> +\t\t     const struct patch_util *b, const char *keydata)\n> +{\n> +\treturn strcmp(a->diff, keydata ? keydata : b->diff);\n> +}\n> +\n> +static void find_exact_matches(struct string_list *a, struct string_list *b)\n> +{\n> +\tstruct hashmap map;\n> +\tint i;\n> +\n> +\thashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);\n> +\n> +\t/* First, add the patches of a to a hash map */\n> +\tfor (i = 0; i < a->nr; i++) {\n> +\t\tstruct patch_util *util = a->items[i].util;\n> +\n> +\t\tutil->i = i;\n> +\t\tutil->patch = a->items[i].string;\n> +\t\tutil->diff = util->patch + util->diff_offset;\n> +\t\thashmap_entry_init(util, strhash(util->diff));\n> +\t\thashmap_add(&map, util);\n> +\t}\n> +\n> +\t/* Now try to find exact matches in b */\n> +\tfor (i = 0; i < b->nr; i++) {\n> +\t\tstruct patch_util *util = b->items[i].util, *other;\n> +\n> +\t\tutil->i = i;\n> +\t\tutil->patch = b->items[i].string;\n> +\t\tutil->diff = util->patch + util->diff_offset;\n> +\t\thashmap_entry_init(util, strhash(util->diff));\n> +\t\tother = hashmap_remove(&map, util, NULL);\n> +\t\tif (other) {\n> +\t\t\tif (other->matching >= 0)\n> +\t\t\t\tBUG(\"already assigned!\");\n> +\n> +\t\t\tother->matching = i;\n> +\t\t\tutil->matching = other->i;\n> +\t\t}\n> +\t}\n\nOne possibly interesting corner case here is what happens when there\nare two patches that have the exact same diff, for example in the\npathological case of commit A doing something, commit B reverting\ncommit A, and then commit C reverting commit B, so it ends up with the\nsame diff.\n\nHaving those same commits unchanged in both ranges (e.g. if a commit\nearlier in the range has been changed, and range B has been rebased on\ntop of that), we'd get the following mapping from range A to range B\nfor the commits in question:\n\nA -> C\nB -> B\nC -> A\n\nWhich is not quite what I would expect as the user (even though it is\na valid mapping, and it probably doesn't matter too much for the end\nresult of the range diff, as nothing has changed between the commits\nanyway).  So I'm not sure it's worth fixing this, as it is a\npathological case, and nothing really breaks.\n\n> +\n> +\thashmap_free(&map, 0);\n> +}\n> +\n> +static void diffsize_consume(void *data, char *line, unsigned long len)\n> +{\n> +\t(*(int *)data)++;\n> +}\n> +\n> +static int diffsize(const char *a, const char *b)\n> +{\n> +\txpparam_t pp = { 0 };\n> +\txdemitconf_t cfg = { 0 };\n> +\tmmfile_t mf1, mf2;\n> +\tint count = 0;\n> +\n> +\tmf1.ptr = (char *)a;\n> +\tmf1.size = strlen(a);\n> +\tmf2.ptr = (char *)b;\n> +\tmf2.size = strlen(b);\n> +\n> +\tcfg.ctxlen = 3;\n> +\tif (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))\n> +\t\treturn count;\n> +\n> +\terror(_(\"failed to generate diff\"));\n> +\treturn COST_MAX;\n> +}\n> +\n> +static void get_correspondences(struct string_list *a, struct string_list *b,\n> +\t\t\t\tint creation_factor)\n> +{\n> +\tint n = a->nr + b->nr;\n> +\tint *cost, c, *a2b, *b2a;\n> +\tint i, j;\n> +\n> +\tALLOC_ARRAY(cost, st_mult(n, n));\n> +\tALLOC_ARRAY(a2b, n);\n> +\tALLOC_ARRAY(b2a, n);\n> +\n> +\tfor (i = 0; i < a->nr; i++) {\n> +\t\tstruct patch_util *a_util = a->items[i].util;\n> +\n> +\t\tfor (j = 0; j < b->nr; j++) {\n> +\t\t\tstruct patch_util *b_util = b->items[j].util;\n> +\n> +\t\t\tif (a_util->matching == j)\n> +\t\t\t\tc = 0;\n> +\t\t\telse if (a_util->matching < 0 && b_util->matching < 0)\n> +\t\t\t\tc = diffsize(a_util->diff, b_util->diff);\n> +\t\t\telse\n> +\t\t\t\tc = COST_MAX;\n> +\t\t\tcost[i + n * j] = c;\n> +\t\t}\n> +\n> +\t\tc = a_util->matching < 0 ?\n> +\t\t\ta_util->diffsize * creation_factor / 100 : COST_MAX;\n> +\t\tfor (j = b->nr; j < n; j++)\n> +\t\t\tcost[i + n * j] = c;\n> +\t}\n> +\n> +\tfor (j = 0; j < b->nr; j++) {\n> +\t\tstruct patch_util *util = b->items[j].util;\n> +\n> +\t\tc = util->matching < 0 ?\n> +\t\t\tutil->diffsize * creation_factor / 100 : COST_MAX;\n> +\t\tfor (i = a->nr; i < n; i++)\n> +\t\t\tcost[i + n * j] = c;\n> +\t}\n> +\n> +\tfor (i = a->nr; i < n; i++)\n> +\t\tfor (j = b->nr; j < n; j++)\n> +\t\t\tcost[i + n * j] = 0;\n> +\n> +\tcompute_assignment(n, n, cost, a2b, b2a);\n> +\n> +\tfor (i = 0; i < a->nr; i++)\n> +\t\tif (a2b[i] >= 0 && a2b[i] < b->nr) {\n> +\t\t\tstruct patch_util *a_util = a->items[i].util;\n> +\t\t\tstruct patch_util *b_util = b->items[a2b[i]].util;\n> +\n> +\t\t\ta_util->matching = a2b[i];\n> +\t\t\tb_util->matching = i;\n\nSo here we re-assign 'matching' in the struct regardless of whether it\nwas assigned before while searching for exact matches or not.\n\nShouldn't diffsize for matching patches also be 0?  So are we doing\nthe 'find_exact_matches()' bit only as an optimization, or am I\nmissing some other reason why that is beneficial?\n\n> +\t\t}\n> +\n> +\tfree(cost);\n> +\tfree(a2b);\n> +\tfree(b2a);\n> +}\n> +\n> +static const char *short_oid(struct patch_util *util)\n> +{\n> +\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n> +}\n> +\n> +static void output(struct string_list *a, struct string_list *b)\n> +{\n> +\tint i;\n> +\n> +\tfor (i = 0; i < b->nr; i++) {\n> +\t\tstruct patch_util *util = b->items[i].util, *prev;\n> +\n> +\t\tif (util->matching < 0)\n> +\t\t\tprintf(\"-: -------- > %d: %s\\n\",\n> +\t\t\t\t\ti + 1, short_oid(util));\n> +\t\telse {\n> +\t\t\tprev = a->items[util->matching].util;\n> +\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n> +\t\t\t       util->matching + 1, short_oid(prev),\n> +\t\t\t       i + 1, short_oid(util));\n> +\t\t}\n> +\t}\n> +\n> +\tfor (i = 0; i < a->nr; i++) {\n> +\t\tstruct patch_util *util = a->items[i].util;\n> +\n> +\t\tif (util->matching < 0)\n> +\t\t\tprintf(\"%d: %s < -: --------\\n\",\n> +\t\t\t       i + 1, short_oid(util));\n> +\t}\n> +}\n> +\n> +int show_range_diff(const char *range1, const char *range2,\n> +\t\t    int creation_factor)\n> +{\n> +\tint res = 0;\n> +\n> +\tstruct string_list branch1 = STRING_LIST_INIT_DUP;\n> +\tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n> +\n> +\tif (read_patches(range1, &branch1))\n> +\t\tres = error(_(\"could not parse log for '%s'\"), range1);\n> +\tif (!res && read_patches(range2, &branch2))\n> +\t\tres = error(_(\"could not parse log for '%s'\"), range2);\n> +\n> +\tif (!res) {\n> +\t\tfind_exact_matches(&branch1, &branch2);\n\nNote to self: here we assign the matching member of struct patch_util\nfor each patch in both ranges to a patch number in the other range if\nit is an exact match.\n\nWe also assign the patch and diff members, and number the patches\nusing the 'i' member of struct patch_util.  Let's see if that\nnumbering is still useful later.\n\n> +\t\tget_correspondences(&branch1, &branch2, creation_factor);\n\nAnd here we use the linear assignment algorithm to match the rest of\nthe commits.\n\n> +\t\toutput(&branch1, &branch2);\n\nAnd finally we print the output.  We don't seem to use the util->i\nthat's assigned for range b (or range 2) anywhere at the moment, which\nI was wondering about earlier, so I assume it's there mainly for\nsymmetry, but it doesn't really hurt other than me wondering what it\nwas for.\n\n> +\t}\n> +\n> +\tstring_list_clear(&branch1, 1);\n> +\tstring_list_clear(&branch2, 1);\n> +\n> +\treturn res;\n> +}\n> diff --git a/range-diff.h b/range-diff.h\n> new file mode 100644\n> index 000000000..dd30449c4\n> --- /dev/null\n> +++ b/range-diff.h\n> @@ -0,0 +1,7 @@\n> +#ifndef BRANCH_DIFF_H\n> +#define BRANCH_DIFF_H\n\ns/BRANCH/RANGE/ above?\n\n> +int show_range_diff(const char *range1, const char *range2,\n> +\t\t    int creation_factor);\n> +\n> +#endif\n> -- \n> gitgitgadget\n> \n"},{"id":"353830","messageId":"20180729190359.GD2734@hank.intra.tgummerer.com","threadId":"48405","inReplyTo":"94afaeaf224563effda7b3c0b8939567302d2ba1.1532210683.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 05/21] range-diff: also show the diff between patches","fromName":"Thomas Gummerer","fromEmail":"t.gummerer@gmail.com","sentAt":"2018-07-29T19:03:59Z","receivedAt":"2018-07-29T19:04:07Z","isPatch":true,"sender":{"key":"t.gummerer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/191004?v=4"},"body":"On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> \n> Just like tbdiff, we now show the diff between matching patches. This is\n> a \"diff of two diffs\", so it can be a bit daunting to read for the\n> beginner.\n> \n> An alternative would be to display an interdiff, i.e. the hypothetical\n> diff which is the result of first reverting the old diff and then\n> applying the new diff.\n> \n> Especially when rebasing often, an interdiff is often not feasible,\n> though: if the old diff cannot be applied in reverse (due to a moving\n> upstream), an interdiff can simply not be inferred.\n> \n> This commit brings `range-diff` closer to feature parity with regard\n> to tbdiff.\n> \n> To make `git range-diff` respect e.g. color.diff.* settings, we have\n> to adjust git_branch_config() accordingly.\n> \n> Note: while we now parse diff options such as --color, the effect is not\n> yet the same as in tbdiff, where also the commit pairs would be colored.\n> This is left for a later commit.\n> \n> Note also: while tbdiff accepts the `--no-patches` option to suppress\n> these diffs between patches, we prefer the `-s` option that is\n> automatically supported via our use of diff_opt_parse().\n\nOne slightly unfortunate thing here is that we don't show these\noptions in 'git range-diff -h', which would be nice to have.  I don't\nknow if that's possible in git right now, if it's not easily possible,\nI definitely wouldn't want to delay this series for that, and we could\njust add it to the list of possible future enhancements that other\npeople mentioned.\n\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  builtin/range-diff.c | 25 ++++++++++++++++++++++---\n>  range-diff.c         | 34 +++++++++++++++++++++++++++++++---\n>  range-diff.h         |  4 +++-\n>  3 files changed, 56 insertions(+), 7 deletions(-)\n>\n> [...]\n> \n> diff --git a/range-diff.c b/range-diff.c\n> index 2d94200d3..71883a4b7 100644\n> --- a/range-diff.c\n> +++ b/range-diff.c\n> @@ -6,6 +6,7 @@\n>  #include \"hashmap.h\"\n>  #include \"xdiff-interface.h\"\n>  #include \"linear-assignment.h\"\n> +#include \"diffcore.h\"\n>  \n>  struct patch_util {\n>  \t/* For the search for an exact match */\n> @@ -258,7 +259,31 @@ static const char *short_oid(struct patch_util *util)\n>  \treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n>  }\n>  \n> -static void output(struct string_list *a, struct string_list *b)\n> +static struct diff_filespec *get_filespec(const char *name, const char *p)\n> +{\n> +\tstruct diff_filespec *spec = alloc_filespec(name);\n> +\n> +\tfill_filespec(spec, &null_oid, 0, 0644);\n> +\tspec->data = (char *)p;\n> +\tspec->size = strlen(p);\n> +\tspec->should_munmap = 0;\n> +\tspec->is_stdin = 1;\n> +\n> +\treturn spec;\n> +}\n> +\n> +static void patch_diff(const char *a, const char *b,\n> +\t\t\t      struct diff_options *diffopt)\n> +{\n> +\tdiff_queue(&diff_queued_diff,\n> +\t\t   get_filespec(\"a\", a), get_filespec(\"b\", b));\n> +\n> +\tdiffcore_std(diffopt);\n> +\tdiff_flush(diffopt);\n> +}\n> +\n> +static void output(struct string_list *a, struct string_list *b,\n> +\t\t   struct diff_options *diffopt)\n>  {\n>  \tint i = 0, j = 0;\n>  \n> @@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)\n>  \t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n>  \t\t\t       b_util->matching + 1, short_oid(a_util),\n>  \t\t\t       j + 1, short_oid(b_util));\n> +\t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n\nLooking at this line, it looks like it would be easy to support\n'--no-patches' as well, which may be slightly easier to understand that\n'-s' to someone new to the command.  But again that can be added later\nif someone actually cares about it.\n\n> +\t\t\t\tpatch_diff(a->items[b_util->matching].string,\n> +\t\t\t\t\t   b->items[j].string, diffopt);\n>  \t\t\ta_util->shown = 1;\n>  \t\t\tj++;\n>  \t\t}\n> @@ -307,7 +335,7 @@ static void output(struct string_list *a, struct string_list *b)\n>  }\n>  \n>  int show_range_diff(const char *range1, const char *range2,\n> -\t\t    int creation_factor)\n> +\t\t    int creation_factor, struct diff_options *diffopt)\n>  {\n>  \tint res = 0;\n>  \n> @@ -322,7 +350,7 @@ int show_range_diff(const char *range1, const char *range2,\n>  \tif (!res) {\n>  \t\tfind_exact_matches(&branch1, &branch2);\n>  \t\tget_correspondences(&branch1, &branch2, creation_factor);\n> -\t\toutput(&branch1, &branch2);\n> +\t\toutput(&branch1, &branch2, diffopt);\n>  \t}\n>  \n>  \tstring_list_clear(&branch1, 1);\n> diff --git a/range-diff.h b/range-diff.h\n> index dd30449c4..aea9d43f3 100644\n> --- a/range-diff.h\n> +++ b/range-diff.h\n> @@ -1,7 +1,9 @@\n>  #ifndef BRANCH_DIFF_H\n>  #define BRANCH_DIFF_H\n>  \n> +#include \"diff.h\"\n> +\n>  int show_range_diff(const char *range1, const char *range2,\n> -\t\t    int creation_factor);\n> +\t\t    int creation_factor, struct diff_options *diffopt);\n>  \n>  #endif\n> -- \n> gitgitgadget\n> \n"},{"id":"353831","messageId":"CAPig+cTuD0+8etdMLu8FkFVxnXUM218taxU9in-fe3QXhDj5WQ@mail.gmail.com","threadId":"48405","inReplyTo":"20180729190359.GD2734@hank.intra.tgummerer.com","subject":"Re: [PATCH v4 05/21] range-diff: also show the diff between patches","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-29T19:22:09Z","receivedAt":"2018-07-29T19:22:24Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, Jul 29, 2018 at 3:04 PM Thomas Gummerer <t.gummerer@gmail.com> wrote:\n> On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> > Just like tbdiff, we now show the diff between matching patches. This is\n> > a \"diff of two diffs\", so it can be a bit daunting to read for the\n> > beginner.\n> > [...]\n> > Note also: while tbdiff accepts the `--no-patches` option to suppress\n> > these diffs between patches, we prefer the `-s` option that is\n> > automatically supported via our use of diff_opt_parse().\n>\n> One slightly unfortunate thing here is that we don't show these\n> options in 'git range-diff -h', which would be nice to have.  I don't\n> know if that's possible in git right now, if it's not easily possible,\n> I definitely wouldn't want to delay this series for that, and we could\n> just add it to the list of possible future enhancements that other\n> people mentioned.\n\nThis issue is not specific to git-range-diff; it's shared by other\ncommands which inherit diff options via diff_opt_parse(). For\ninstance, \"git log -h\" doesn't show diff-related options either, yet\nit accepts them.\n\n> > diff --git a/range-diff.c b/range-diff.c\n> > @@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)\n> >                       printf(\"%d: %s ! %d: %s\\n\",\n> >                              b_util->matching + 1, short_oid(a_util),\n> >                              j + 1, short_oid(b_util));\n> > +                     if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n>\n> Looking at this line, it looks like it would be easy to support\n> '--no-patches' as well, which may be slightly easier to understand that\n> '-s' to someone new to the command.  But again that can be added later\n> if someone actually cares about it.\n\nWhat wasn't mentioned (but was implied) by the commit message is that\n\"-s\" is short for \"--no-patch\", which also comes for free via\ndiff_opt_parse(). True, \"--no-patch\" isn't spelled exactly the same as\n\"--no-patches\", but git-range-diff isn't exactly a perfect tbdiff\nclone, so hopefully not a git problem. Moreover, \"--no-patch\" is\ninternally consistent within the Git builtin commands.\n"},{"id":"353834","messageId":"20180729193818.GE2734@hank.intra.tgummerer.com","threadId":"48405","inReplyTo":"9641ab5c0df984f5e7ea9c49debffffe2a929095.1532210683.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 09/21] range-diff: adjust the output of the commit pairs","fromName":"Thomas Gummerer","fromEmail":"t.gummerer@gmail.com","sentAt":"2018-07-29T19:38:18Z","receivedAt":"2018-07-29T19:38:24Z","isPatch":true,"sender":{"key":"t.gummerer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/191004?v=4"},"body":"On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> \n> This change brings `git range-diff` yet another step closer to\n> feature parity with tbdiff: it now shows the oneline, too, and indicates\n> with `=` when the commits have identical diffs.\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  range-diff.c | 64 ++++++++++++++++++++++++++++++++++++++++++++--------\n>  1 file changed, 55 insertions(+), 9 deletions(-)\n> \n> diff --git a/range-diff.c b/range-diff.c\n> index 1ecee2c09..8329f52e7 100644\n> --- a/range-diff.c\n> +++ b/range-diff.c\n> @@ -7,6 +7,8 @@\n>  #include \"xdiff-interface.h\"\n>  #include \"linear-assignment.h\"\n>  #include \"diffcore.h\"\n> +#include \"commit.h\"\n> +#include \"pretty.h\"\n>  \n>  struct patch_util {\n>  \t/* For the search for an exact match */\n> @@ -255,9 +257,54 @@ static void get_correspondences(struct string_list *a, struct string_list *b,\n>  \tfree(b2a);\n>  }\n>  \n> -static const char *short_oid(struct patch_util *util)\n> +static void output_pair_header(struct strbuf *buf,\n> +\t\t\t       struct strbuf *dashes,\n> +\t\t\t       struct patch_util *a_util,\n> +\t\t\t       struct patch_util *b_util)\n>  {\n> -\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n> +\tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n> +\tstruct commit *commit;\n> +\n> +\tif (!dashes->len)\n> +\t\tstrbuf_addchars(dashes, '-',\n> +\t\t\t\tstrlen(find_unique_abbrev(oid,\n> +\t\t\t\t\t\t\t  DEFAULT_ABBREV)));\n\nWe're doing this only once, which makes sense.  What's a bit\nunfortunate here I guess is that if the first commit we're dealing\nwith in the range-diff has a longer unique abbreviation, the dashes\nwill be longer for all commits, even if all the others have a shorter\nabbreviation.\n\nTbh I don't really know what the right thing to do here is, so this is\nprobably as good a heuristic as any.  It would probably be worse to\nhave different length dashes lines, than guessing based on the first\ncommit.\n\n> +\n> +\tstrbuf_reset(buf);\n> +\tif (!a_util)\n> +\t\tstrbuf_addf(buf, \"-:  %s \", dashes->buf);\n> +\telse\n> +\t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n> +\t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n> +\n> +\tif (!a_util)\n> +\t\tstrbuf_addch(buf, '>');\n> +\telse if (!b_util)\n> +\t\tstrbuf_addch(buf, '<');\n> +\telse if (strcmp(a_util->patch, b_util->patch))\n> +\t\tstrbuf_addch(buf, '!');\n> +\telse\n> +\t\tstrbuf_addch(buf, '=');\n> +\n> +\tif (!b_util)\n> +\t\tstrbuf_addf(buf, \" -:  %s\", dashes->buf);\n> +\telse\n> +\t\tstrbuf_addf(buf, \" %d:  %s\", b_util->i + 1,\n> +\t\t\t    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));\n> +\n> +\tcommit = lookup_commit_reference(oid);\n\nThis bit surprised me slightly.  May be worth mentioning that we now\nalso show the first line of the commit message here.\n\n> +\tif (commit) {\n> +\t\tconst char *commit_buffer = get_commit_buffer(commit, NULL);\n> +\t\tconst char *subject;\n> +\n> +\t\tfind_commit_subject(commit_buffer, &subject);\n> +\t\tstrbuf_addch(buf, ' ');\n> +\t\tformat_subject(buf, subject, \" \");\n> +\t\tunuse_commit_buffer(commit, commit_buffer);\n\nI think the above could be written slightly shorter as\n\n    strbuf_addch(buf, ' ');\n    pp_commit_easy(CMIT_FMT_ONELINE, commit, &buf);\n\nNot sure if it's worth changing this at this stage of the series\nthough, or if there is something in the above that I'm missing, that\nwould make the shorter version not workable.\n\n> +\t}\n> +\tstrbuf_addch(buf, '\\n');\n> +\n> +\tfwrite(buf->buf, buf->len, 1, stdout);\n>  }\n>  \n>  static struct diff_filespec *get_filespec(const char *name, const char *p)\n> @@ -286,6 +333,7 @@ static void patch_diff(const char *a, const char *b,\n>  static void output(struct string_list *a, struct string_list *b,\n>  \t\t   struct diff_options *diffopt)\n>  {\n> +\tstruct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;\n>  \tint i = 0, j = 0;\n>  \n>  \t/*\n> @@ -307,25 +355,21 @@ static void output(struct string_list *a, struct string_list *b,\n>  \n>  \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n>  \t\tif (i < a->nr && a_util->matching < 0) {\n> -\t\t\tprintf(\"%d: %s < -: --------\\n\",\n> -\t\t\t       i + 1, short_oid(a_util));\n> +\t\t\toutput_pair_header(&buf, &dashes, a_util, NULL);\n>  \t\t\ti++;\n>  \t\t\tcontinue;\n>  \t\t}\n>  \n>  \t\t/* Show unmatched RHS commits. */\n>  \t\twhile (j < b->nr && b_util->matching < 0) {\n> -\t\t\tprintf(\"-: -------- > %d: %s\\n\",\n> -\t\t\t       j + 1, short_oid(b_util));\n> +\t\t\toutput_pair_header(&buf, &dashes, NULL, b_util);\n>  \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n>  \t\t}\n>  \n>  \t\t/* Show matching LHS/RHS pair. */\n>  \t\tif (j < b->nr) {\n>  \t\t\ta_util = a->items[b_util->matching].util;\n> -\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n> -\t\t\t       b_util->matching + 1, short_oid(a_util),\n> -\t\t\t       j + 1, short_oid(b_util));\n> +\t\t\toutput_pair_header(&buf, &dashes, a_util, b_util);\n>  \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n>  \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n>  \t\t\t\t\t   b->items[j].string, diffopt);\n> @@ -333,6 +377,8 @@ static void output(struct string_list *a, struct string_list *b,\n>  \t\t\tj++;\n>  \t\t}\n>  \t}\n> +\tstrbuf_release(&buf);\n> +\tstrbuf_release(&dashes);\n>  }\n>  \n>  int show_range_diff(const char *range1, const char *range2,\n> -- \n> gitgitgadget\n> \n"},{"id":"353840","messageId":"20180729205202.GA9955@hank.intra.tgummerer.com","threadId":"48405","inReplyTo":"0a52f887887cae039ee84b90cc05a6396242a744.1532210683.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 10/21] range-diff: do not show \"function names\" in hunk headers","fromName":"Thomas Gummerer","fromEmail":"t.gummerer@gmail.com","sentAt":"2018-07-29T20:52:02Z","receivedAt":"2018-07-29T20:52:07Z","isPatch":true,"sender":{"key":"t.gummerer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/191004?v=4"},"body":"On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> \n> We are comparing complete, formatted commit messages with patches. There\n> are no function names here, so stop looking for them.\n\nWhile there are no function names here, trying out range-diff without\nthis patch applied, the headers were getting here do seem kind of\nuseful:\n\n    1: 92588fc6b6 ! 3: 43c9ef552c\n        @@ -8,8 +8,16 @@ diff --git a/read-cache.c b/read-cache.c\n    \t[...]\n\nThe filename can be quite useful in this output.  I guess this is a\nbit brittle though, so I'm also happy to defer changing this to show\nsomething useful to the list of possible future enhancements\n(obviously doesn't necessarily have to be implemented by you at that\npoint).\n\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  range-diff.c | 6 ++++++\n>  1 file changed, 6 insertions(+)\n> \n> diff --git a/range-diff.c b/range-diff.c\n> index 8329f52e7..3fc3a4018 100644\n> --- a/range-diff.c\n> +++ b/range-diff.c\n> @@ -9,6 +9,7 @@\n>  #include \"diffcore.h\"\n>  #include \"commit.h\"\n>  #include \"pretty.h\"\n> +#include \"userdiff.h\"\n>  \n>  struct patch_util {\n>  \t/* For the search for an exact match */\n> @@ -307,6 +308,10 @@ static void output_pair_header(struct strbuf *buf,\n>  \tfwrite(buf->buf, buf->len, 1, stdout);\n>  }\n>  \n> +static struct userdiff_driver no_func_name = {\n> +\t.funcname = { \"$^\", 0 }\n> +};\n> +\n>  static struct diff_filespec *get_filespec(const char *name, const char *p)\n>  {\n>  \tstruct diff_filespec *spec = alloc_filespec(name);\n> @@ -316,6 +321,7 @@ static struct diff_filespec *get_filespec(const char *name, const char *p)\n>  \tspec->size = strlen(p);\n>  \tspec->should_munmap = 0;\n>  \tspec->is_stdin = 1;\n> +\tspec->driver = &no_func_name;\n>  \n>  \treturn spec;\n>  }\n> -- \n> gitgitgadget\n> \n"},{"id":"353843","messageId":"20180729212354.GB9955@hank.intra.tgummerer.com","threadId":"48405","inReplyTo":"9e09c6be66e960db496b1c9a30eb5040242ab764.1532210683.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 17/21] range-diff: populate the man page","fromName":"Thomas Gummerer","fromEmail":"t.gummerer@gmail.com","sentAt":"2018-07-29T21:23:54Z","receivedAt":"2018-07-29T21:23:59Z","isPatch":true,"sender":{"key":"t.gummerer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/191004?v=4"},"body":"On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> \n> The bulk of this patch consists of a heavily butchered version of\n> tbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from\n\nThanks for the mention here, but this was really mostly Thomas Rast's\nwriting.  My contributions here are very minor compared to his :)\n\n> https://github.com/trast/tbdiff.\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  Documentation/git-range-diff.txt | 229 +++++++++++++++++++++++++++++++\n>  1 file changed, 229 insertions(+)\n> \n> [...]\n>\n> +CONFIGURATION\n> +-------------\n> +This command uses the `diff.color.*` and `pager.range-diff` settings\n> +(the latter is on by default).\n> +See linkgit:git-config[1].\n\nWould it be worth implementing a `rangeDiff.dualColor` configuration\nat some point?  Dual color mode seems like something I would like to\nhave on by default, even if we are not making it the default for the\ncommand itself.\n\n(Again this is something that can be a future enhancement).\n\n> +EXAMPLES\n> +--------\n> [...]\n> -- \n> gitgitgadget\n> \n"},{"id":"353844","messageId":"20180729212809.GA13316@hank.intra.tgummerer.com","threadId":"48405","inReplyTo":"9641ab5c0df984f5e7ea9c49debffffe2a929095.1532210683.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 09/21] range-diff: adjust the output of the commit pairs","fromName":"Thomas Gummerer","fromEmail":"t.gummerer@gmail.com","sentAt":"2018-07-29T21:28:09Z","receivedAt":"2018-07-29T21:28:14Z","isPatch":true,"sender":{"key":"t.gummerer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/191004?v=4"},"body":"On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> \n> This change brings `git range-diff` yet another step closer to\n> feature parity with tbdiff: it now shows the oneline, too, and indicates\n> with `=` when the commits have identical diffs.\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  range-diff.c | 64 ++++++++++++++++++++++++++++++++++++++++++++--------\n>  1 file changed, 55 insertions(+), 9 deletions(-)\n> \n> diff --git a/range-diff.c b/range-diff.c\n> index 1ecee2c09..8329f52e7 100644\n> --- a/range-diff.c\n> +++ b/range-diff.c\n> @@ -7,6 +7,8 @@\n>  #include \"xdiff-interface.h\"\n>  #include \"linear-assignment.h\"\n>  #include \"diffcore.h\"\n> +#include \"commit.h\"\n> +#include \"pretty.h\"\n>  \n>  struct patch_util {\n>  \t/* For the search for an exact match */\n> @@ -255,9 +257,54 @@ static void get_correspondences(struct string_list *a, struct string_list *b,\n>  \tfree(b2a);\n>  }\n>  \n> -static const char *short_oid(struct patch_util *util)\n> +static void output_pair_header(struct strbuf *buf,\n> +\t\t\t       struct strbuf *dashes,\n> +\t\t\t       struct patch_util *a_util,\n> +\t\t\t       struct patch_util *b_util)\n>  {\n> -\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n> +\tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n> +\tstruct commit *commit;\n> +\n> +\tif (!dashes->len)\n> +\t\tstrbuf_addchars(dashes, '-',\n> +\t\t\t\tstrlen(find_unique_abbrev(oid,\n> +\t\t\t\t\t\t\t  DEFAULT_ABBREV)));\n> +\n> +\tstrbuf_reset(buf);\n> +\tif (!a_util)\n> +\t\tstrbuf_addf(buf, \"-:  %s \", dashes->buf);\n> +\telse\n> +\t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n> +\t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n\nI failed to notice this earlier, but here we are starting to use\nutil->i, which I was wondering about earlier.  Good :)\n\n> +\tif (!a_util)\n> +\t\tstrbuf_addch(buf, '>');\n> +\telse if (!b_util)\n> +\t\tstrbuf_addch(buf, '<');\n> +\telse if (strcmp(a_util->patch, b_util->patch))\n> +\t\tstrbuf_addch(buf, '!');\n> +\telse\n> +\t\tstrbuf_addch(buf, '=');\n> +\n> +\tif (!b_util)\n> +\t\tstrbuf_addf(buf, \" -:  %s\", dashes->buf);\n> +\telse\n> +\t\tstrbuf_addf(buf, \" %d:  %s\", b_util->i + 1,\n> +\t\t\t    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));\n\nAnd here for range b.\n\n> +\n> [...]\n"},{"id":"353845","messageId":"20180729213325.GC9955@hank.intra.tgummerer.com","threadId":"48405","inReplyTo":"b370468e71af2b8c7ffa0e31f3a3910d15897ab4.1532210683.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 20/21] range-diff: make --dual-color the default mode","fromName":"Thomas Gummerer","fromEmail":"t.gummerer@gmail.com","sentAt":"2018-07-29T21:33:25Z","receivedAt":"2018-07-29T21:33:30Z","isPatch":true,"sender":{"key":"t.gummerer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/191004?v=4"},"body":"On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> \n> After using this command extensively for the last two months, this\n> developer came to the conclusion that even if the dual color mode still\n> leaves a lot of room for confusion about what was actually changed, the\n> non-dual color mode is substantially worse in that regard.\n> \n> Therefore, we really want to make the dual color mode the default.\n\nAh and here we're making it default, so I wouldn't need a\n`rangeDiff.dualColor` config variable anymore.  Even better!\n\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  Documentation/git-range-diff.txt       | 32 +++++++++++++++-----------\n>  builtin/range-diff.c                   | 10 ++++----\n>  contrib/completion/git-completion.bash |  2 +-\n>  3 files changed, 25 insertions(+), 19 deletions(-)\n> \n> [...]\n"},{"id":"353847","messageId":"20180729214543.GD9955@hank.intra.tgummerer.com","threadId":"48405","inReplyTo":"CAPig+cTuD0+8etdMLu8FkFVxnXUM218taxU9in-fe3QXhDj5WQ@mail.gmail.com","subject":"Re: [PATCH v4 05/21] range-diff: also show the diff between patches","fromName":"Thomas Gummerer","fromEmail":"t.gummerer@gmail.com","sentAt":"2018-07-29T21:45:43Z","receivedAt":"2018-07-29T21:45:48Z","isPatch":true,"sender":{"key":"t.gummerer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/191004?v=4"},"body":"On 07/29, Eric Sunshine wrote:\n> On Sun, Jul 29, 2018 at 3:04 PM Thomas Gummerer <t.gummerer@gmail.com> wrote:\n> > On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> > > Just like tbdiff, we now show the diff between matching patches. This is\n> > > a \"diff of two diffs\", so it can be a bit daunting to read for the\n> > > beginner.\n> > > [...]\n> > > Note also: while tbdiff accepts the `--no-patches` option to suppress\n> > > these diffs between patches, we prefer the `-s` option that is\n> > > automatically supported via our use of diff_opt_parse().\n> >\n> > One slightly unfortunate thing here is that we don't show these\n> > options in 'git range-diff -h', which would be nice to have.  I don't\n> > know if that's possible in git right now, if it's not easily possible,\n> > I definitely wouldn't want to delay this series for that, and we could\n> > just add it to the list of possible future enhancements that other\n> > people mentioned.\n> \n> This issue is not specific to git-range-diff; it's shared by other\n> commands which inherit diff options via diff_opt_parse(). For\n> instance, \"git log -h\" doesn't show diff-related options either, yet\n> it accepts them.\n\nFair enough, that makes sense.  Thanks for the pointer!\n\nThere's one more thing that I noticed here:\n\n    git range-diff --no-patches\n    fatal: single arg format requires a symmetric range\n\nWhich is a slightly confusing error message.  In contrast git log does\nthe following on an unrecognized argument:\n\n    git log --no-patches\n    fatal: unrecognized argument: --no-patches\n\nwhich is a little better I think.  I do however also thing the \"fatal:\nsingle arg format requires a symmetric range\" is useful when someone\ngenuinely tries to use the single argument version of the command.  So\nI don't know what a good solution for this would be.\n\n> > > diff --git a/range-diff.c b/range-diff.c\n> > > @@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)\n> > >                       printf(\"%d: %s ! %d: %s\\n\",\n> > >                              b_util->matching + 1, short_oid(a_util),\n> > >                              j + 1, short_oid(b_util));\n> > > +                     if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n> >\n> > Looking at this line, it looks like it would be easy to support\n> > '--no-patches' as well, which may be slightly easier to understand that\n> > '-s' to someone new to the command.  But again that can be added later\n> > if someone actually cares about it.\n> \n> What wasn't mentioned (but was implied) by the commit message is that\n> \"-s\" is short for \"--no-patch\", which also comes for free via\n> diff_opt_parse(). True, \"--no-patch\" isn't spelled exactly the same as\n> \"--no-patches\", but git-range-diff isn't exactly a perfect tbdiff\n> clone, so hopefully not a git problem. Moreover, \"--no-patch\" is\n> internally consistent within the Git builtin commands.\n\nMakes sense, thanks!  \"--no-patch\" does make sense to me.  There's\nstill a lot of command line flags in git to learn for me, even after\nall this time using it ;)  Might be nice to spell it out in the commit\nmessage for someone like me, especially as \"--no-patches\" is already\nmentioned.  Though I guess most regulars here would know about\n\"--no-patch\", so maybe it's not worth it.  Anyway that is definitely\nnot worth another round here.\n"},{"id":"353848","messageId":"20180729215053.GE9955@hank.intra.tgummerer.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike","fromName":"Thomas Gummerer","fromEmail":"t.gummerer@gmail.com","sentAt":"2018-07-29T21:50:53Z","receivedAt":"2018-07-29T21:51:00Z","isPatch":true,"sender":{"key":"t.gummerer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/191004?v=4"},"body":"On 07/21, Johannes Schindelin via GitGitGadget wrote:\n\n> The incredibly useful [`git-tbdiff`](https://github.com/trast/tbdiff) tool to compare patch series (say, to see what changed between two iterations sent to the Git mailing list) is slightly less useful for this developer due to the fact that it requires the `hungarian` and `numpy` Python packages which are for some reason really hard to build in MSYS2. So hard that I even had to give up, because it was simply easier to re-implement the whole shebang as a builtin command.\n\nThanks for your work making this a built-in!  I finally found some\ntime to go through the series, and overall it makes sense to me.  I\nleft a few comments here and there, and fwiw I didn't think there's\nanything major that needs to be addressed.\n\nHope it helps!\n\n> The project at https://github.com/trast/tbdiff seems to be dormant, anyway. Funny (and true) story: I looked at the open Pull Requests to see how active that project is, only to find to my surprise that I had submitted one in August 2015, and that it was still unanswered let alone merged.\n> \n> While at it, I forward-ported AEvar's patch to force `--decorate=no` because `git -p tbdiff` would fail otherwise.\n> \n> Side note: I work on implementing range-diff not only to make life easier for reviewers who have to suffer through v2, v3, ... of my patch series, but also to verify my changes before submitting a new iteration. And also, maybe even more importantly, I plan to use it to verify my merging-rebases of Git for\n> Windows (for which I previously used to redirect the pre-rebase/post-rebase diffs vs upstream and then compare them using `git diff --no-index`). And of course any interested person can see what changes were necessary e.g. in the merging-rebase of Git for Windows onto v2.17.0 by running a command like:\n> \n> ```sh\n>         base=^{/Start.the.merging-rebase}\n>         tag=v2.17.0.windows.1\n>         pre=$tag$base^2\n>         git range-diff $pre$base..$pre $tag$base..$tag\n> ```\n> \n> The command uses what it calls the \"dual color mode\" (can be disabled via `--no-dual-color`) which helps identifying what *actually* changed: it prefixes lines with a `-` (and red background) that correspond to the first commit range, and with a `+` (and green background) that correspond to the second range. The rest of the lines will be colored according to the original diffs.\n> \n> Changes since v3:\n> \n> - The cover letter was adjusted to reflect the new reality (the command is called `range-diff` now, not `branch-diff`, and `--dual-color` is the default).\n> - The documentation was adjusted a bit more in the patch that makes `--dual-color` the default.\n> - Clarified the calculation of the cost matrix, as per Stefan Beller's request.\n> - The man page now spells out that merge *commits* are ignored in the commit ranges (not merges per se).\n> - The code in `linear-assignment.c` was adjusted to use the `SWAP()` macro.\n> - The commit message of the patch introducing the first rudimentary implementation no longer talks about the \"Hungarian\" algorithm, but about the \"linear assignment algorithm\" instead.\n> - A bogus indentation change was backed out from the patch introducing the first rudimentary implementation.\n> - Instead of merely warning about missing `..` in the 2-parameter invocation, we now exit with the error message.\n> - The `diff_opt_parse()` function is allowed to return a value larger than 1, indicating that more than just one command-line parameter was parsed. We now advance by the indicated value instead of always advancing exactly 1 (which is still correct much of the time).\n> - A lengthy `if...else if...else if...else` was simplified (from a logical point of view) by reordering it.\n> - The unnecessarily `static` variable `dashes` was turned into a local variable of the caller.\n> - The commit message talking about the new man page still referred to `git branch --diff`, which has been fixed.\n> - A forgotten t7910 reference was changed to t3206.\n> - An unbalanced double-tick was fixed in the man page.\n> - Fixed grammar both of the commit message and the description of the `--no-dual-color` option.\n> - To fix the build, a blank man page is now introduced together with the new `range-diff` command, even if it is populated for real only at a later patch (i.e. at the same time as before).\n> - The headaches Junio fears would be incurred by that simple workaround to avoid bogus white-space error reporting are fended off: a more complex patch is now in place that adds (and uses) a new white-space flag. Sadly, as is all too common when Junio \"encourages\" me to replace a simple workaround by something \"proper\", it caused all kinds of headaches to get this right, so I am rather less certain that the \"proper\" fix will cause us less headaches than the simple workaround would have done. But whatever.\n> - The dual color mode now also dims the changes that are exclusively in the first specified commit range, and uses bold face on the changes exclusively in the second one. This matches the intuition when using `range-diff` to compare an older iteration of a patch series to a newer one: the changes from the previous iteration that were replaced by new ones \"fade\", while the changes that replace them are \"shiny new\".\n> \n> Changes since v2:\n> \n> - Right-aligned the patch numbers in the commit pairs.\n> - Used ALLOC_ARRAY() in hungarian.c instead of xmalloc(sizeof()*size).\n> - Changed compute_assignment()s return type from int to void, as it always succeeds.\n> - Changed the Hungarian Algorithm to use an integer cost matrix.\n> - Changed the --creation-weight <double> option to --creation-factor <percent> where <percent> is an integer.\n> - Retitled 1/19 and 2/19 to better conform with the current conventions, as pointed out (and suggested) by Junio.\n> - Shut up Coverity, and at the same time avoided passing the unnecessary `i` and `j` parameters to output_pair_header().\n> - Removed support for the `--no-patches` option: we inherit diff_options' support for `-s` already (and much more).\n> - Removed the ugly `_INV` enum values, and introduced a beautiful GIT_COLOR_REVERSE instead. This way, whatever the user configured as color.diff.new (or .old) will be used in reverse in the dual color mode.\n> - Instead of overriding the fragment header color, the dual color mode will now reverse the \"outer\" fragment headers, too.\n> - Turned the stand-alone branch-diff command into the `--diff` option of `git branch`. Adjusted pretty much *all* commit messages to account for this. This change should no longer be visible: see below.\n> - Pretty much re-wrote the completion, to support the new --diff mode of git-branch. See below: it was reverted for range-diff.\n> - Renamed t7910 to t3206, to be closer to the git-branch tests.\n> - Ensured that git_diff_ui_config() gets called, and therefore color.diff.* respected.\n> - Avoided leaking `four_spaces`.\n> - Fixed a declaration in a for (;;) statement (which Junio had as a fixup! that I almost missed).\n> - Renamed `branch --diff`, which had been renamed from `branch-diff` (which was picked to avoid re-using `tbdiff`) to `range-diff`.\n> - Renamed `hungarian.c` and its header to `linear-assignment.c`\n> - Made `--dual-color` the default, and changed it to still auto-detect whether color should be used rather than forcing it\n> \n> Johannes Schindelin (20):\n>   linear-assignment: a function to solve least-cost assignment problems\n>   Introduce `range-diff` to compare iterations of a topic branch\n>   range-diff: first rudimentary implementation\n>   range-diff: improve the order of the shown commits\n>   range-diff: also show the diff between patches\n>   range-diff: right-trim commit messages\n>   range-diff: indent the diffs just like tbdiff\n>   range-diff: suppress the diff headers\n>   range-diff: adjust the output of the commit pairs\n>   range-diff: do not show \"function names\" in hunk headers\n>   range-diff: use color for the commit pairs\n>   color: add the meta color GIT_COLOR_REVERSE\n>   diff: add an internal option to dual-color diffs of diffs\n>   range-diff: offer to dual-color the diffs\n>   range-diff --dual-color: fix bogus white-space warning\n>   range-diff: populate the man page\n>   completion: support `git range-diff`\n>   range-diff: left-pad patch numbers\n>   range-diff: make --dual-color the default mode\n>   range-diff: use dim/bold cues to improve dual color mode\n> \n> Thomas Rast (1):\n>   range-diff: add tests\n> \n>  .gitignore                             |   1 +\n>  Documentation/config.txt               |   6 +-\n>  Documentation/git-range-diff.txt       | 252 +++++++++++\n>  Makefile                               |   3 +\n>  builtin.h                              |   1 +\n>  builtin/range-diff.c                   | 106 +++++\n>  cache.h                                |   3 +-\n>  color.h                                |   7 +\n>  command-list.txt                       |   1 +\n>  contrib/completion/git-completion.bash |  14 +\n>  diff.c                                 | 119 ++++-\n>  diff.h                                 |  16 +-\n>  git.c                                  |   1 +\n>  linear-assignment.c                    | 201 ++++++++\n>  linear-assignment.h                    |  22 +\n>  range-diff.c                           | 440 ++++++++++++++++++\n>  range-diff.h                           |   9 +\n>  t/.gitattributes                       |   1 +\n>  t/t3206-range-diff.sh                  | 145 ++++++\n>  t/t3206/history.export                 | 604 +++++++++++++++++++++++++\n>  ws.c                                   |  11 +-\n>  21 files changed, 1932 insertions(+), 31 deletions(-)\n>  create mode 100644 Documentation/git-range-diff.txt\n>  create mode 100644 builtin/range-diff.c\n>  create mode 100644 linear-assignment.c\n>  create mode 100644 linear-assignment.h\n>  create mode 100644 range-diff.c\n>  create mode 100644 range-diff.h\n>  create mode 100755 t/t3206-range-diff.sh\n>  create mode 100644 t/t3206/history.export\n> \n> \n> base-commit: b7bd9486b055c3f967a870311e704e3bb0654e4f\n> Published-As: https://github.com/gitgitgadget/git/releases/tags/pr-1%2Fdscho%2Fbranch-diff-v4\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1/dscho/branch-diff-v4\n> Pull-Request: https://github.com/gitgitgadget/git/pull/1\n> \n> Range-diff vs v3:\n> \n>   1:  39272eefc !  1:  f7e70689e linear-assignment: a function to solve least-cost assignment problems\n>      @@ -223,9 +223,7 @@\n>       +\t\t\t\tBUG(\"negative j: %d\", j);\n>       +\t\t\ti = pred[j];\n>       +\t\t\tcolumn2row[j] = i;\n>      -+\t\t\tk = j;\n>      -+\t\t\tj = row2column[i];\n>      -+\t\t\trow2column[i] = k;\n>      ++\t\t\tSWAP(j, row2column[i]);\n>       +\t\t} while (i1 != i);\n>       +\t}\n>       +\n>   2:  7f15b26d4 !  2:  88134121d Introduce `range-diff` to compare iterations of a topic branch\n>      @@ -10,6 +10,10 @@\n>           At this point, we ignore tbdiff's color options, as they will all be\n>           implemented later using diff_options.\n>       \n>      +    Since f318d739159 (generate-cmds.sh: export all commands to\n>      +    command-list.h, 2018-05-10), every new command *requires* a man page to\n>      +    build right away, so let's also add a blank man page, too.\n>      +\n>           Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n>       \n>       diff --git a/.gitignore b/.gitignore\n>      @@ -24,6 +28,22 @@\n>        /git-rebase\n>        /git-rebase--am\n>       \n>      +diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\n>      +new file mode 100644\n>      +--- /dev/null\n>      ++++ b/Documentation/git-range-diff.txt\n>      +@@\n>      ++git-range-diff(1)\n>      ++==================\n>      ++\n>      ++NAME\n>      ++----\n>      ++git-range-diff - Compare two commit ranges (e.g. two versions of a branch)\n>      ++\n>      ++GIT\n>      ++---\n>      ++Part of the linkgit:git[1] suite\n>      +\n>       diff --git a/Makefile b/Makefile\n>       --- a/Makefile\n>       +++ b/Makefile\n>   3:  076e1192d !  3:  4e3fb47a1 range-diff: first rudimentary implementation\n>      @@ -4,7 +4,7 @@\n>       \n>           At this stage, `git range-diff` can determine corresponding commits\n>           of two related commit ranges. This makes use of the recently introduced\n>      -    implementation of the Hungarian algorithm.\n>      +    implementation of the linear assignment algorithm.\n>       \n>           The core of this patch is a straight port of the ideas of tbdiff, the\n>           apparently dormant project at https://github.com/trast/tbdiff.\n>      @@ -51,19 +51,17 @@\n>       +\tint res = 0;\n>       +\tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n>        \n>      --\targc = parse_options(argc, argv, NULL, options,\n>      --\t\t\t     builtin_range_diff_usage, 0);\n>      -+\targc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,\n>      -+\t\t\t     0);\n>      + \targc = parse_options(argc, argv, NULL, options,\n>      + \t\t\t     builtin_range_diff_usage, 0);\n>        \n>       -\treturn 0;\n>       +\tif (argc == 2) {\n>       +\t\tif (!strstr(argv[0], \"..\"))\n>      -+\t\t\twarning(_(\"no .. in range: '%s'\"), argv[0]);\n>      ++\t\t\tdie(_(\"no .. in range: '%s'\"), argv[0]);\n>       +\t\tstrbuf_addstr(&range1, argv[0]);\n>       +\n>       +\t\tif (!strstr(argv[1], \"..\"))\n>      -+\t\t\twarning(_(\"no .. in range: '%s'\"), argv[1]);\n>      ++\t\t\tdie(_(\"no .. in range: '%s'\"), argv[1]);\n>       +\t\tstrbuf_addstr(&range2, argv[1]);\n>       +\t} else if (argc == 3) {\n>       +\t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n>      @@ -195,17 +193,21 @@\n>       +\t\t\tcontinue;\n>       +\t\t} else if (starts_with(line.buf, \"@@ \"))\n>       +\t\t\tstrbuf_addstr(&buf, \"@@\");\n>      -+\t\telse if (line.buf[0] && !starts_with(line.buf, \"index \"))\n>      ++\t\telse if (!line.buf[0] || starts_with(line.buf, \"index \"))\n>       +\t\t\t/*\n>       +\t\t\t * A completely blank (not ' \\n', which is context)\n>       +\t\t\t * line is not valid in a diff.  We skip it\n>       +\t\t\t * silently, because this neatly handles the blank\n>       +\t\t\t * separator line between commits in git-log\n>       +\t\t\t * output.\n>      ++\t\t\t *\n>      ++\t\t\t * We also want to ignore the diff's `index` lines\n>      ++\t\t\t * because they contain exact blob hashes in which\n>      ++\t\t\t * we are not interested.\n>       +\t\t\t */\n>      -+\t\t\tstrbuf_addbuf(&buf, &line);\n>      -+\t\telse\n>       +\t\t\tcontinue;\n>      ++\t\telse\n>      ++\t\t\tstrbuf_addbuf(&buf, &line);\n>       +\n>       +\t\tstrbuf_addch(&buf, '\\n');\n>       +\t\tutil->diffsize++;\n>   4:  e98489c8c =  4:  47bee09b0 range-diff: improve the order of the shown commits\n>   5:  935cad180 !  5:  94afaeaf2 range-diff: also show the diff between patches\n>      @@ -55,21 +55,22 @@\n>       +\tint i, j, res = 0;\n>        \tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n>        \n>      --\targc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,\n>      --\t\t\t     0);\n>       +\tgit_config(git_diff_ui_config, NULL);\n>       +\n>       +\tdiff_setup(&diffopt);\n>       +\tdiffopt.output_format = DIFF_FORMAT_PATCH;\n>       +\n>      -+\targc = parse_options(argc, argv, NULL, options,\n>      -+\t\t\tbuiltin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n>      + \targc = parse_options(argc, argv, NULL, options,\n>      +-\t\t\t     builtin_range_diff_usage, 0);\n>      ++\t\t\t     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n>       +\n>      -+\tfor (i = j = 0; i < argc; i++) {\n>      ++\tfor (i = j = 0; i < argc; ) {\n>       +\t\tint c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);\n>       +\n>       +\t\tif (!c)\n>      -+\t\t\targv[j++] = argv[i];\n>      ++\t\t\targv[j++] = argv[i++];\n>      ++\t\telse\n>      ++\t\t\ti += c;\n>       +\t}\n>       +\targc = j;\n>       +\tdiff_setup_done(&diffopt);\n>   6:  93ac1931d =  6:  41ab875a3 range-diff: right-trim commit messages\n>   7:  ca5282815 !  7:  a3dd99509 range-diff: indent the diffs just like tbdiff\n>      @@ -39,7 +39,7 @@\n>       +\tdiffopt.output_prefix_data = &four_spaces;\n>        \n>        \targc = parse_options(argc, argv, NULL, options,\n>      - \t\t\tbuiltin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n>      + \t\t\t     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n>       @@\n>        \n>        \tstrbuf_release(&range1);\n>   8:  80622685f =  8:  61b2ff2f7 range-diff: suppress the diff headers\n>   9:  6b31cbf72 !  9:  9641ab5c0 range-diff: adjust the output of the commit pairs\n>      @@ -26,25 +26,22 @@\n>        \n>       -static const char *short_oid(struct patch_util *util)\n>       +static void output_pair_header(struct strbuf *buf,\n>      ++\t\t\t       struct strbuf *dashes,\n>       +\t\t\t       struct patch_util *a_util,\n>       +\t\t\t       struct patch_util *b_util)\n>        {\n>       -\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n>      -+\tstatic char *dashes;\n>       +\tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n>       +\tstruct commit *commit;\n>       +\n>      -+\tif (!dashes) {\n>      -+\t\tchar *p;\n>      -+\n>      -+\t\tdashes = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV));\n>      -+\t\tfor (p = dashes; *p; p++)\n>      -+\t\t\t*p = '-';\n>      -+\t}\n>      ++\tif (!dashes->len)\n>      ++\t\tstrbuf_addchars(dashes, '-',\n>      ++\t\t\t\tstrlen(find_unique_abbrev(oid,\n>      ++\t\t\t\t\t\t\t  DEFAULT_ABBREV)));\n>       +\n>       +\tstrbuf_reset(buf);\n>       +\tif (!a_util)\n>      -+\t\tstrbuf_addf(buf, \"-:  %s \", dashes);\n>      ++\t\tstrbuf_addf(buf, \"-:  %s \", dashes->buf);\n>       +\telse\n>       +\t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n>       +\t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n>      @@ -59,7 +56,7 @@\n>       +\t\tstrbuf_addch(buf, '=');\n>       +\n>       +\tif (!b_util)\n>      -+\t\tstrbuf_addf(buf, \" -:  %s\", dashes);\n>      ++\t\tstrbuf_addf(buf, \" -:  %s\", dashes->buf);\n>       +\telse\n>       +\t\tstrbuf_addf(buf, \" %d:  %s\", b_util->i + 1,\n>       +\t\t\t    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));\n>      @@ -84,7 +81,7 @@\n>        static void output(struct string_list *a, struct string_list *b,\n>        \t\t   struct diff_options *diffopt)\n>        {\n>      -+\tstruct strbuf buf = STRBUF_INIT;\n>      ++\tstruct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;\n>        \tint i = 0, j = 0;\n>        \n>        \t/*\n>      @@ -94,7 +91,7 @@\n>        \t\tif (i < a->nr && a_util->matching < 0) {\n>       -\t\t\tprintf(\"%d: %s < -: --------\\n\",\n>       -\t\t\t       i + 1, short_oid(a_util));\n>      -+\t\t\toutput_pair_header(&buf, a_util, NULL);\n>      ++\t\t\toutput_pair_header(&buf, &dashes, a_util, NULL);\n>        \t\t\ti++;\n>        \t\t\tcontinue;\n>        \t\t}\n>      @@ -103,7 +100,7 @@\n>        \t\twhile (j < b->nr && b_util->matching < 0) {\n>       -\t\t\tprintf(\"-: -------- > %d: %s\\n\",\n>       -\t\t\t       j + 1, short_oid(b_util));\n>      -+\t\t\toutput_pair_header(&buf, NULL, b_util);\n>      ++\t\t\toutput_pair_header(&buf, &dashes, NULL, b_util);\n>        \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n>        \t\t}\n>        \n>      @@ -113,7 +110,7 @@\n>       -\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n>       -\t\t\t       b_util->matching + 1, short_oid(a_util),\n>       -\t\t\t       j + 1, short_oid(b_util));\n>      -+\t\t\toutput_pair_header(&buf, a_util, b_util);\n>      ++\t\t\toutput_pair_header(&buf, &dashes, a_util, b_util);\n>        \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n>        \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n>        \t\t\t\t\t   b->items[j].string, diffopt);\n>      @@ -122,6 +119,7 @@\n>        \t\t}\n>        \t}\n>       +\tstrbuf_release(&buf);\n>      ++\tstrbuf_release(&dashes);\n>        }\n>        \n>        int show_range_diff(const char *range1, const char *range2,\n>  10:  ef997bb8b = 10:  0a52f8878 range-diff: do not show \"function names\" in hunk headers\n>  11:  3d9e5b0ba ! 11:  2b8d09020 range-diff: add tests\n>      @@ -3,8 +3,8 @@\n>           range-diff: add tests\n>       \n>           These are essentially lifted from https://github.com/trast/tbdiff, with\n>      -    light touch-ups to account for the command now being an option of `git\n>      -    branch`.\n>      +    light touch-ups to account for the command now being names `git\n>      +    range-diff`.\n>       \n>           Apart from renaming `tbdiff` to `range-diff`, only one test case needed\n>           to be adjusted: 11 - 'changed message'.\n>      @@ -22,12 +22,13 @@\n>       --- a/t/.gitattributes\n>       +++ b/t/.gitattributes\n>       @@\n>      - /t5515/* eol=lf\n>      - /t556x_common eol=lf\n>      - /t7500/* eol=lf\n>      -+/t7910/* eol=lf\n>      - /t8005/*.txt eol=lf\n>      - /t9*/*.dump eol=lf\n>      + t[0-9][0-9][0-9][0-9]/* -whitespace\n>      + /diff-lib/* eol=lf\n>      + /t0110/url-* binary\n>      ++/t3206/* eol=lf\n>      + /t3900/*.txt eol=lf\n>      + /t3901/*.txt eol=lf\n>      + /t4034/*/* eol=lf\n>       \n>       diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh\n>       new file mode 100755\n>  12:  7273cc647 ! 12:  fb83ce71a range-diff: use color for the commit pairs\n>      @@ -20,11 +20,12 @@\n>        }\n>        \n>       -static void output_pair_header(struct strbuf *buf,\n>      -+static void output_pair_header(struct diff_options *diffopt, struct strbuf *buf,\n>      ++static void output_pair_header(struct diff_options *diffopt,\n>      ++\t\t\t       struct strbuf *buf,\n>      + \t\t\t       struct strbuf *dashes,\n>        \t\t\t       struct patch_util *a_util,\n>        \t\t\t       struct patch_util *b_util)\n>        {\n>      - \tstatic char *dashes;\n>        \tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n>        \tstruct commit *commit;\n>       +\tchar status;\n>      @@ -34,11 +35,10 @@\n>       +\tconst char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT);\n>       +\tconst char *color;\n>        \n>      - \tif (!dashes) {\n>      - \t\tchar *p;\n>      -@@\n>      - \t\t\t*p = '-';\n>      - \t}\n>      + \tif (!dashes->len)\n>      + \t\tstrbuf_addchars(dashes, '-',\n>      + \t\t\t\tstrlen(find_unique_abbrev(oid,\n>      + \t\t\t\t\t\t\t  DEFAULT_ABBREV)));\n>        \n>       +\tif (!b_util) {\n>       +\t\tcolor = color_old;\n>      @@ -57,7 +57,7 @@\n>        \tstrbuf_reset(buf);\n>       +\tstrbuf_addstr(buf, status == '!' ? color_old : color);\n>        \tif (!a_util)\n>      - \t\tstrbuf_addf(buf, \"-:  %s \", dashes);\n>      + \t\tstrbuf_addf(buf, \"-:  %s \", dashes->buf);\n>        \telse\n>        \t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n>        \t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n>      @@ -77,7 +77,7 @@\n>       +\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color_new);\n>        \n>        \tif (!b_util)\n>      - \t\tstrbuf_addf(buf, \" -:  %s\", dashes);\n>      + \t\tstrbuf_addf(buf, \" -:  %s\", dashes->buf);\n>       @@\n>        \t\tconst char *commit_buffer = get_commit_buffer(commit, NULL);\n>        \t\tconst char *subject;\n>      @@ -99,24 +99,27 @@\n>        \n>        \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n>        \t\tif (i < a->nr && a_util->matching < 0) {\n>      --\t\t\toutput_pair_header(&buf, a_util, NULL);\n>      -+\t\t\toutput_pair_header(diffopt, &buf, a_util, NULL);\n>      +-\t\t\toutput_pair_header(&buf, &dashes, a_util, NULL);\n>      ++\t\t\toutput_pair_header(diffopt,\n>      ++\t\t\t\t\t   &buf, &dashes, a_util, NULL);\n>        \t\t\ti++;\n>        \t\t\tcontinue;\n>        \t\t}\n>        \n>        \t\t/* Show unmatched RHS commits. */\n>        \t\twhile (j < b->nr && b_util->matching < 0) {\n>      --\t\t\toutput_pair_header(&buf, NULL, b_util);\n>      -+\t\t\toutput_pair_header(diffopt, &buf, NULL, b_util);\n>      +-\t\t\toutput_pair_header(&buf, &dashes, NULL, b_util);\n>      ++\t\t\toutput_pair_header(diffopt,\n>      ++\t\t\t\t\t   &buf, &dashes, NULL, b_util);\n>        \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n>        \t\t}\n>        \n>        \t\t/* Show matching LHS/RHS pair. */\n>        \t\tif (j < b->nr) {\n>        \t\t\ta_util = a->items[b_util->matching].util;\n>      --\t\t\toutput_pair_header(&buf, a_util, b_util);\n>      -+\t\t\toutput_pair_header(diffopt, &buf, a_util, b_util);\n>      +-\t\t\toutput_pair_header(&buf, &dashes, a_util, b_util);\n>      ++\t\t\toutput_pair_header(diffopt,\n>      ++\t\t\t\t\t   &buf, &dashes, a_util, b_util);\n>        \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n>        \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n>        \t\t\t\t\t   b->items[j].string, diffopt);\n>  13:  96a3073fb = 13:  9ccb9516a color: add the meta color GIT_COLOR_REVERSE\n>  14:  6be4baf60 = 14:  9de5bd229 diff: add an internal option to dual-color diffs of diffs\n>  15:  02e13c0c6 ! 15:  21b2f9e4b range-diff: offer to dual-color the diffs\n>      @@ -40,4 +40,4 @@\n>       +\n>        \tif (argc == 2) {\n>        \t\tif (!strstr(argv[0], \"..\"))\n>      - \t\t\twarning(_(\"no .. in range: '%s'\"), argv[0]);\n>      + \t\t\tdie(_(\"no .. in range: '%s'\"), argv[0]);\n>  16:  dfa7b1e71 <  -:  --------- range-diff --dual-color: work around bogus white-space warning\n>   -:  --------- > 16:  f4252f2b2 range-diff --dual-color: fix bogus white-space warning\n>  17:  799da25ef ! 17:  9e09c6be6 range-diff: add a man page\n>      @@ -1,6 +1,6 @@\n>       Author: Johannes Schindelin <johannes.schindelin@gmx.de>\n>       \n>      -    range-diff: add a man page\n>      +    range-diff: populate the man page\n>       \n>           The bulk of this patch consists of a heavily butchered version of\n>           tbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from\n>      @@ -9,17 +9,12 @@\n>           Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n>       \n>       diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\n>      -new file mode 100644\n>      ---- /dev/null\n>      +--- a/Documentation/git-range-diff.txt\n>       +++ b/Documentation/git-range-diff.txt\n>       @@\n>      -+git-range-diff(1)\n>      -+==================\n>      -+\n>      -+NAME\n>      -+----\n>      -+git-range-diff - Compare two commit ranges (e.g. two versions of a branch)\n>      -+\n>      + ----\n>      + git-range-diff - Compare two commit ranges (e.g. two versions of a branch)\n>      + \n>       +SYNOPSIS\n>       +--------\n>       +[verse]\n>      @@ -31,13 +26,13 @@\n>       +-----------\n>       +\n>       +This command shows the differences between two versions of a patch\n>      -+series, or more generally, two commit ranges (ignoring merges).\n>      ++series, or more generally, two commit ranges (ignoring merge commits).\n>       +\n>       +To that end, it first finds pairs of commits from both commit ranges\n>       +that correspond with each other. Two commits are said to correspond when\n>       +the diff between their patches (i.e. the author information, the commit\n>       +message and the commit diff) is reasonably small compared to the\n>      -+patches' size. See ``Algorithm` below for details.\n>      ++patches' size. See ``Algorithm`` below for details.\n>       +\n>       +Finally, the list of matching commits is shown in the order of the\n>       +second commit range, with unmatched commits being inserted just after\n>      @@ -150,6 +145,10 @@\n>       +The general idea is this: we generate a cost matrix between the commits\n>       +in both commit ranges, then solve the least-cost assignment.\n>       +\n>      ++The cost matrix is populated thusly: for each pair of commits, both\n>      ++diffs are generated and the \"diff of diffs\" is generated, with 3 context\n>      ++lines, then the number of lines in that diff is used as cost.\n>      ++\n>       +To avoid false positives (e.g. when a patch has been removed, and an\n>       +unrelated patch has been added between two iterations of the same patch\n>       +series), the cost matrix is extended to allow for that, by adding\n>      @@ -245,6 +244,6 @@\n>       +--------\n>       +linkgit:git-log[1]\n>       +\n>      -+GIT\n>      -+---\n>      -+Part of the linkgit:git[1] suite\n>      + GIT\n>      + ---\n>      + Part of the linkgit:git[1] suite\n>  18:  d05b54c60 ! 18:  9b3632324 completion: support `git range-diff`\n>      @@ -4,17 +4,11 @@\n>       \n>           Tab completion of `git range-diff` is very convenient, especially\n>           given that the revision arguments to specify the commit ranges to\n>      -    compare are typically more complex than, say, your grandfather's `git\n>      -    log` arguments.\n>      +    compare are typically more complex than, say, what is normally passed\n>      +    to `git log`.\n>       \n>           Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n>       \n>      -    squash! WIP completion: support `git range-diff`\n>      -\n>      -    Revert \"WIP completion: support `git range-diff`\"\n>      -\n>      -    This reverts commit 2e7af652af9e53a19fd947f8ebe37a78043afa49.\n>      -\n>       diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n>       --- a/contrib/completion/git-completion.bash\n>       +++ b/contrib/completion/git-completion.bash\n>  19:  144363006 <  -:  --------- range-diff: left-pad patch numbers\n>   -:  --------- > 19:  07ec215e8 range-diff: left-pad patch numbers\n>  20:  4a68b95ce ! 20:  b370468e7 range-diff: make --dual-color the default mode\n>      @@ -4,7 +4,7 @@\n>       \n>           After using this command extensively for the last two months, this\n>           developer came to the conclusion that even if the dual color mode still\n>      -    leaves a lot of room for confusion what was actually changed, the\n>      +    leaves a lot of room for confusion about what was actually changed, the\n>           non-dual color mode is substantially worse in that regard.\n>       \n>           Therefore, we really want to make the dual color mode the default.\n>      @@ -14,6 +14,15 @@\n>       diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\n>       --- a/Documentation/git-range-diff.txt\n>       +++ b/Documentation/git-range-diff.txt\n>      +@@\n>      + --------\n>      + [verse]\n>      + 'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]\n>      +-\t[--dual-color] [--creation-factor=<factor>]\n>      ++\t[--no-dual-color] [--creation-factor=<factor>]\n>      + \t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n>      + \n>      + DESCRIPTION\n>       @@\n>        \n>        OPTIONS\n>      @@ -25,7 +34,7 @@\n>       -\tchange in what exact lines were added.\n>       +--no-dual-color::\n>       +\tWhen the commit diffs differ, `git range-diff` recreates the\n>      -+\toriginal diffs' coloring, and add outer -/+ diff markers with\n>      ++\toriginal diffs' coloring, and adds outer -/+ diff markers with\n>       +\tthe *background* being red/green to make it easier to see e.g.\n>       +\twhen there was a change in what exact lines were added. This is\n>       +\tknown to `range-diff` as \"dual coloring\". Use `--no-dual-color`\n>      @@ -34,6 +43,31 @@\n>        \n>        --creation-factor=<percent>::\n>        \tSet the creation/deletion cost fudge factor to `<percent>`.\n>      +@@\n>      + show`'s output, and the third line colors the old commit red, the new\n>      + one green and the rest like `git show`'s commit header.\n>      + \n>      +-The color-coded diff is actually a bit hard to read, though, as it\n>      +-colors the entire lines red or green. The line that added \"What is\n>      +-unexpected\" in the old commit, for example, is completely red, even if\n>      +-the intent of the old commit was to add something.\n>      ++A naive color-coded diff of diffs is actually a bit hard to read,\n>      ++though, as it colors the entire lines red or green. The line that added\n>      ++\"What is unexpected\" in the old commit, for example, is completely red,\n>      ++even if the intent of the old commit was to add something.\n>      + \n>      +-To help with that, use the `--dual-color` mode. In this mode, the diff\n>      +-of diffs will retain the original diff colors, and prefix the lines with\n>      +--/+ markers that have their *background* red or green, to make it more\n>      +-obvious that they describe how the diff itself changed.\n>      ++To help with that, `range` uses the `--dual-color` mode by default. In\n>      ++this mode, the diff of diffs will retain the original diff colors, and\n>      ++prefix the lines with -/+ markers that have their *background* red or\n>      ++green, to make it more obvious that they describe how the diff itself\n>      ++changed.\n>      + \n>      + \n>      + Algorithm\n>       \n>       diff --git a/builtin/range-diff.c b/builtin/range-diff.c\n>       --- a/builtin/range-diff.c\n>   -:  --------- > 21:  d8498fb32 range-diff: use dim/bold cues to improve dual color mode\n> \n> -- \n> gitgitgadget\n"},{"id":"353904","messageId":"nycvar.QRO.7.76.6.1807301756170.10478@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180728084652.GB2734@hank.intra.tgummerer.com","subject":"Re: [PATCH v4 01/21] linear-assignment: a function to solve least-cost assignment problems","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-30T15:59:27Z","receivedAt":"2018-07-30T15:59:36Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Thomas,\n\nOn Sat, 28 Jul 2018, Thomas Gummerer wrote:\n\n> On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> > From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > \n> > The problem solved by the code introduced in this commit goes like this:\n> > given two sets of items, and a cost matrix which says how much it\n> > \"costs\" to assign any given item of the first set to any given item of\n> > the second, assign all items (except when the sets have different size)\n> > in the cheapest way.\n> > \n> > We use the Jonker-Volgenant algorithm to solve the assignment problem to\n> > answer questions such as: given two different versions of a topic branch\n> > (or iterations of a patch series), what is the best pairing of\n> > commits/patches between the different versions?\n> > \n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> >  Makefile            |   1 +\n> >  linear-assignment.c | 201 ++++++++++++++++++++++++++++++++++++++++++++\n> >  linear-assignment.h |  22 +++++\n> >  3 files changed, 224 insertions(+)\n> >  create mode 100644 linear-assignment.c\n> >  create mode 100644 linear-assignment.h\n> >\n> > [...]\n> > \n> > diff --git a/linear-assignment.h b/linear-assignment.h\n> > new file mode 100644\n> > index 000000000..fc4c502c8\n> > --- /dev/null\n> > +++ b/linear-assignment.h\n> > @@ -0,0 +1,22 @@\n> > +#ifndef HUNGARIAN_H\n> > +#define HUNGARIAN_H\n> \n> nit: maybe s/HUNGARIAN/LINEAR_ASSIGNMENT/ in the two lines above.\n\nMakes sense.\n\nCiao,\nDscho\n\n> \n> > +\n> > +/*\n> > + * Compute an assignment of columns -> rows (and vice versa) such that every\n> > + * column is assigned to at most one row (and vice versa) minimizing the\n> > + * overall cost.\n> > + *\n> > + * The parameter `cost` is the cost matrix: the cost to assign column j to row\n> > + * i is `cost[j + column_count * i].\n> > + *\n> > + * The arrays column2row and row2column will be populated with the respective\n> > + * assignments (-1 for unassigned, which can happen only if column_count !=\n> > + * row_count).\n> > + */\n> > +void compute_assignment(int column_count, int row_count, int *cost,\n> > +\t\t\tint *column2row, int *row2column);\n> > +\n> > +/* The maximal cost in the cost matrix (to prevent integer overflows). */\n> > +#define COST_MAX (1<<16)\n> > +\n> > +#endif\n> > -- \n> > gitgitgadget\n> > \n> \n"},{"id":"353906","messageId":"nycvar.QRO.7.76.6.1807301759340.10478@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180729183629.GC2734@hank.intra.tgummerer.com","subject":"Re: [PATCH v4 03/21] range-diff: first rudimentary implementation","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-30T16:21:29Z","receivedAt":"2018-07-30T16:21:50Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Thomas,\n\nOn Sun, 29 Jul 2018, Thomas Gummerer wrote:\n\n> On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> > From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > \n> > At this stage, `git range-diff` can determine corresponding commits\n> > of two related commit ranges. This makes use of the recently introduced\n> > implementation of the linear assignment algorithm.\n> > \n> > The core of this patch is a straight port of the ideas of tbdiff, the\n> > apparently dormant project at https://github.com/trast/tbdiff.\n> > \n> > The output does not at all match `tbdiff`'s output yet, as this patch\n> > really concentrates on getting the patch matching part right.\n> > \n> > Note: due to differences in the diff algorithm (`tbdiff` uses the Python\n> > module `difflib`, Git uses its xdiff fork), the cost matrix calculated\n> > by `range-diff` is different (but very similar) to the one calculated\n> > by `tbdiff`. Therefore, it is possible that they find different matching\n> > commits in corner cases (e.g. when a patch was split into two patches of\n> > roughly equal length).\n> > \n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> >  Makefile             |   1 +\n> >  builtin/range-diff.c |  43 +++++-\n> >  range-diff.c         | 311 +++++++++++++++++++++++++++++++++++++++++++\n> >  range-diff.h         |   7 +\n> >  4 files changed, 361 insertions(+), 1 deletion(-)\n> >  create mode 100644 range-diff.c\n> >  create mode 100644 range-diff.h\n> >\n> > [...]\n> > \n> > diff --git a/range-diff.c b/range-diff.c\n> > new file mode 100644\n> > index 000000000..15d418afa\n> > --- /dev/null\n> > +++ b/range-diff.c\n> > @@ -0,0 +1,311 @@\n> > +#include \"cache.h\"\n> > +#include \"range-diff.h\"\n> > +#include \"string-list.h\"\n> > +#include \"run-command.h\"\n> > +#include \"argv-array.h\"\n> > +#include \"hashmap.h\"\n> > +#include \"xdiff-interface.h\"\n> > +#include \"linear-assignment.h\"\n> > +\n> > +struct patch_util {\n> > +\t/* For the search for an exact match */\n> > +\tstruct hashmap_entry e;\n> > +\tconst char *diff, *patch;\n> > +\n> > +\tint i;\n> > +\tint diffsize;\n> > +\tsize_t diff_offset;\n> > +\t/* the index of the matching item in the other branch, or -1 */\n> > +\tint matching;\n> > +\tstruct object_id oid;\n> > +};\n> > +\n> > +/*\n> > + * Reads the patches into a string list, with the `util` field being populated\n> > + * as struct object_id (will need to be free()d).\n> > + */\n> > +static int read_patches(const char *range, struct string_list *list)\n> > +{\n> > +\tstruct child_process cp = CHILD_PROCESS_INIT;\n> > +\tFILE *in;\n> > +\tstruct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;\n> > +\tstruct patch_util *util = NULL;\n> > +\tint in_header = 1;\n> > +\n> > +\targv_array_pushl(&cp.args, \"log\", \"--no-color\", \"-p\", \"--no-merges\",\n> > +\t\t\t\"--reverse\", \"--date-order\", \"--decorate=no\",\n> > +\t\t\t\"--no-abbrev-commit\", range,\n> > +\t\t\tNULL);\n> \n> Compared to tbdiff, add \"--decorate=no\", and \"--no-abbrev-commit\".  I\n> see we're abbreviating the commit hashes later.  We don't want ref\n> names here, so \"--decorate=no\" makes sense as well.\n\nIndeed. Compare also to https://github.com/trast/tbdiff/pull/8\n\n> > +\tcp.out = -1;\n> > +\tcp.no_stdin = 1;\n> > +\tcp.git_cmd = 1;\n> > +\n> > +\tif (start_command(&cp))\n> > +\t\treturn error_errno(_(\"could not start `log`\"));\n> > +\tin = fdopen(cp.out, \"r\");\n> > +\tif (!in) {\n> > +\t\terror_errno(_(\"could not read `log` output\"));\n> > +\t\tfinish_command(&cp);\n> > +\t\treturn -1;\n> > +\t}\n> > +\n> > +\twhile (strbuf_getline(&line, in) != EOF) {\n> > +\t\tconst char *p;\n> > +\n> > +\t\tif (skip_prefix(line.buf, \"commit \", &p)) {\n> > +\t\t\tif (util) {\n> > +\t\t\t\tstring_list_append(list, buf.buf)->util = util;\n> > +\t\t\t\tstrbuf_reset(&buf);\n> > +\t\t\t}\n> > +\t\t\tutil = xcalloc(sizeof(*util), 1);\n> > +\t\t\tif (get_oid(p, &util->oid)) {\n> > +\t\t\t\terror(_(\"could not parse commit '%s'\"), p);\n> > +\t\t\t\tfree(util);\n> > +\t\t\t\tstring_list_clear(list, 1);\n> > +\t\t\t\tstrbuf_release(&buf);\n> > +\t\t\t\tstrbuf_release(&line);\n> > +\t\t\t\tfclose(in);\n> > +\t\t\t\tfinish_command(&cp);\n> > +\t\t\t\treturn -1;\n> > +\t\t\t}\n> > +\t\t\tutil->matching = -1;\n> > +\t\t\tin_header = 1;\n> > +\t\t\tcontinue;\n> > +\t\t}\n> > +\n> > +\t\tif (starts_with(line.buf, \"diff --git\")) {\n> > +\t\t\tin_header = 0;\n> > +\t\t\tstrbuf_addch(&buf, '\\n');\n> > +\t\t\tif (!util->diff_offset)\n> > +\t\t\t\tutil->diff_offset = buf.len;\n> > +\t\t\tstrbuf_addbuf(&buf, &line);\n> > +\t\t} else if (in_header) {\n> > +\t\t\tif (starts_with(line.buf, \"Author: \")) {\n> > +\t\t\t\tstrbuf_addbuf(&buf, &line);\n> > +\t\t\t\tstrbuf_addstr(&buf, \"\\n\\n\");\n> > +\t\t\t} else if (starts_with(line.buf, \"    \")) {\n> > +\t\t\t\tstrbuf_addbuf(&buf, &line);\n> > +\t\t\t\tstrbuf_addch(&buf, '\\n');\n> > +\t\t\t}\n> > +\t\t\tcontinue;\n> > +\t\t} else if (starts_with(line.buf, \"@@ \"))\n> > +\t\t\tstrbuf_addstr(&buf, \"@@\");\n> > +\t\telse if (!line.buf[0] || starts_with(line.buf, \"index \"))\n> > +\t\t\t/*\n> > +\t\t\t * A completely blank (not ' \\n', which is context)\n> > +\t\t\t * line is not valid in a diff.  We skip it\n> > +\t\t\t * silently, because this neatly handles the blank\n> > +\t\t\t * separator line between commits in git-log\n> > +\t\t\t * output.\n> > +\t\t\t *\n> > +\t\t\t * We also want to ignore the diff's `index` lines\n> > +\t\t\t * because they contain exact blob hashes in which\n> > +\t\t\t * we are not interested.\n> > +\t\t\t */\n> > +\t\t\tcontinue;\n> > +\t\telse\n> > +\t\t\tstrbuf_addbuf(&buf, &line);\n> > +\n> > +\t\tstrbuf_addch(&buf, '\\n');\n> > +\t\tutil->diffsize++;\n> > +\t}\n> \n> This seems to differ from tbdiff in the number of newlines we're\n> adding in various places, however I think as long as it's consistent\n> in itself, and with the way we're printing the output the differences\n> shouldn't matter.\n\nRight.\n\n> > +\tfclose(in);\n> > +\tstrbuf_release(&line);\n> > +\n> > +\tif (util)\n> > +\t\tstring_list_append(list, buf.buf)->util = util;\n> > +\tstrbuf_release(&buf);\n> > +\n> > +\tif (finish_command(&cp))\n> > +\t\treturn -1;\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +static int patch_util_cmp(const void *dummy, const struct patch_util *a,\n> > +\t\t     const struct patch_util *b, const char *keydata)\n> > +{\n> > +\treturn strcmp(a->diff, keydata ? keydata : b->diff);\n> > +}\n> > +\n> > +static void find_exact_matches(struct string_list *a, struct string_list *b)\n> > +{\n> > +\tstruct hashmap map;\n> > +\tint i;\n> > +\n> > +\thashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);\n> > +\n> > +\t/* First, add the patches of a to a hash map */\n> > +\tfor (i = 0; i < a->nr; i++) {\n> > +\t\tstruct patch_util *util = a->items[i].util;\n> > +\n> > +\t\tutil->i = i;\n> > +\t\tutil->patch = a->items[i].string;\n> > +\t\tutil->diff = util->patch + util->diff_offset;\n> > +\t\thashmap_entry_init(util, strhash(util->diff));\n> > +\t\thashmap_add(&map, util);\n> > +\t}\n> > +\n> > +\t/* Now try to find exact matches in b */\n> > +\tfor (i = 0; i < b->nr; i++) {\n> > +\t\tstruct patch_util *util = b->items[i].util, *other;\n> > +\n> > +\t\tutil->i = i;\n> > +\t\tutil->patch = b->items[i].string;\n> > +\t\tutil->diff = util->patch + util->diff_offset;\n> > +\t\thashmap_entry_init(util, strhash(util->diff));\n> > +\t\tother = hashmap_remove(&map, util, NULL);\n> > +\t\tif (other) {\n> > +\t\t\tif (other->matching >= 0)\n> > +\t\t\t\tBUG(\"already assigned!\");\n> > +\n> > +\t\t\tother->matching = i;\n> > +\t\t\tutil->matching = other->i;\n> > +\t\t}\n> > +\t}\n> \n> One possibly interesting corner case here is what happens when there\n> are two patches that have the exact same diff, for example in the\n> pathological case of commit A doing something, commit B reverting\n> commit A, and then commit C reverting commit B, so it ends up with the\n> same diff.\n> \n> Having those same commits unchanged in both ranges (e.g. if a commit\n> earlier in the range has been changed, and range B has been rebased on\n> top of that), we'd get the following mapping from range A to range B\n> for the commits in question:\n> \n> A -> C\n> B -> B\n> C -> A\n> \n> Which is not quite what I would expect as the user (even though it is\n> a valid mapping, and it probably doesn't matter too much for the end\n> result of the range diff, as nothing has changed between the commits\n> anyway).  So I'm not sure it's worth fixing this, as it is a\n> pathological case, and nothing really breaks.\n\nIndeed. As far as I am concerned, this falls squarely into the \"let's\ncross that bridge when, or if, we reach it\" category.\n\n> > +\n> > +\thashmap_free(&map, 0);\n> > +}\n> > +\n> > +static void diffsize_consume(void *data, char *line, unsigned long len)\n> > +{\n> > +\t(*(int *)data)++;\n> > +}\n> > +\n> > +static int diffsize(const char *a, const char *b)\n> > +{\n> > +\txpparam_t pp = { 0 };\n> > +\txdemitconf_t cfg = { 0 };\n> > +\tmmfile_t mf1, mf2;\n> > +\tint count = 0;\n> > +\n> > +\tmf1.ptr = (char *)a;\n> > +\tmf1.size = strlen(a);\n> > +\tmf2.ptr = (char *)b;\n> > +\tmf2.size = strlen(b);\n> > +\n> > +\tcfg.ctxlen = 3;\n> > +\tif (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))\n> > +\t\treturn count;\n> > +\n> > +\terror(_(\"failed to generate diff\"));\n> > +\treturn COST_MAX;\n> > +}\n> > +\n> > +static void get_correspondences(struct string_list *a, struct string_list *b,\n> > +\t\t\t\tint creation_factor)\n> > +{\n> > +\tint n = a->nr + b->nr;\n> > +\tint *cost, c, *a2b, *b2a;\n> > +\tint i, j;\n> > +\n> > +\tALLOC_ARRAY(cost, st_mult(n, n));\n> > +\tALLOC_ARRAY(a2b, n);\n> > +\tALLOC_ARRAY(b2a, n);\n> > +\n> > +\tfor (i = 0; i < a->nr; i++) {\n> > +\t\tstruct patch_util *a_util = a->items[i].util;\n> > +\n> > +\t\tfor (j = 0; j < b->nr; j++) {\n> > +\t\t\tstruct patch_util *b_util = b->items[j].util;\n> > +\n> > +\t\t\tif (a_util->matching == j)\n> > +\t\t\t\tc = 0;\n> > +\t\t\telse if (a_util->matching < 0 && b_util->matching < 0)\n> > +\t\t\t\tc = diffsize(a_util->diff, b_util->diff);\n> > +\t\t\telse\n> > +\t\t\t\tc = COST_MAX;\n> > +\t\t\tcost[i + n * j] = c;\n> > +\t\t}\n> > +\n> > +\t\tc = a_util->matching < 0 ?\n> > +\t\t\ta_util->diffsize * creation_factor / 100 : COST_MAX;\n> > +\t\tfor (j = b->nr; j < n; j++)\n> > +\t\t\tcost[i + n * j] = c;\n> > +\t}\n> > +\n> > +\tfor (j = 0; j < b->nr; j++) {\n> > +\t\tstruct patch_util *util = b->items[j].util;\n> > +\n> > +\t\tc = util->matching < 0 ?\n> > +\t\t\tutil->diffsize * creation_factor / 100 : COST_MAX;\n> > +\t\tfor (i = a->nr; i < n; i++)\n> > +\t\t\tcost[i + n * j] = c;\n> > +\t}\n> > +\n> > +\tfor (i = a->nr; i < n; i++)\n> > +\t\tfor (j = b->nr; j < n; j++)\n> > +\t\t\tcost[i + n * j] = 0;\n> > +\n> > +\tcompute_assignment(n, n, cost, a2b, b2a);\n> > +\n> > +\tfor (i = 0; i < a->nr; i++)\n> > +\t\tif (a2b[i] >= 0 && a2b[i] < b->nr) {\n> > +\t\t\tstruct patch_util *a_util = a->items[i].util;\n> > +\t\t\tstruct patch_util *b_util = b->items[a2b[i]].util;\n> > +\n> > +\t\t\ta_util->matching = a2b[i];\n> > +\t\t\tb_util->matching = i;\n> \n> So here we re-assign 'matching' in the struct regardless of whether it\n> was assigned before while searching for exact matches or not.\n\nIf `matching` were assigned here, it would indicate a bug in the code, I\nthink. Before this loop, all of the `matching` fields should be `NULL`,\nand the `compute_assignment()` function is expected to populate `a2b` and\n`b2a` appropriately, i.e. no \"double booking\".\n\n> Shouldn't diffsize for matching patches also be 0?  So are we doing\n> the 'find_exact_matches()' bit only as an optimization, or am I\n> missing some other reason why that is beneficial?\n\nAn optimization.\n\nRemember, the linear assignment algorithm runs in O(n^3). That's not a\nlaughing matter by any stretch of imagination. The more stuff we can get\nout of the way, as quickly as possible, the less it hurts to have a cubic\nruntime complexity.\n\n> > +\t\t}\n> > +\n> > +\tfree(cost);\n> > +\tfree(a2b);\n> > +\tfree(b2a);\n> > +}\n> > +\n> > +static const char *short_oid(struct patch_util *util)\n> > +{\n> > +\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n> > +}\n> > +\n> > +static void output(struct string_list *a, struct string_list *b)\n> > +{\n> > +\tint i;\n> > +\n> > +\tfor (i = 0; i < b->nr; i++) {\n> > +\t\tstruct patch_util *util = b->items[i].util, *prev;\n> > +\n> > +\t\tif (util->matching < 0)\n> > +\t\t\tprintf(\"-: -------- > %d: %s\\n\",\n> > +\t\t\t\t\ti + 1, short_oid(util));\n> > +\t\telse {\n> > +\t\t\tprev = a->items[util->matching].util;\n> > +\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n> > +\t\t\t       util->matching + 1, short_oid(prev),\n> > +\t\t\t       i + 1, short_oid(util));\n> > +\t\t}\n> > +\t}\n> > +\n> > +\tfor (i = 0; i < a->nr; i++) {\n> > +\t\tstruct patch_util *util = a->items[i].util;\n> > +\n> > +\t\tif (util->matching < 0)\n> > +\t\t\tprintf(\"%d: %s < -: --------\\n\",\n> > +\t\t\t       i + 1, short_oid(util));\n> > +\t}\n> > +}\n> > +\n> > +int show_range_diff(const char *range1, const char *range2,\n> > +\t\t    int creation_factor)\n> > +{\n> > +\tint res = 0;\n> > +\n> > +\tstruct string_list branch1 = STRING_LIST_INIT_DUP;\n> > +\tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n> > +\n> > +\tif (read_patches(range1, &branch1))\n> > +\t\tres = error(_(\"could not parse log for '%s'\"), range1);\n> > +\tif (!res && read_patches(range2, &branch2))\n> > +\t\tres = error(_(\"could not parse log for '%s'\"), range2);\n> > +\n> > +\tif (!res) {\n> > +\t\tfind_exact_matches(&branch1, &branch2);\n> \n> Note to self: here we assign the matching member of struct patch_util\n> for each patch in both ranges to a patch number in the other range if\n> it is an exact match.\n> \n> We also assign the patch and diff members, and number the patches\n> using the 'i' member of struct patch_util.  Let's see if that\n> numbering is still useful later.\n> \n> > +\t\tget_correspondences(&branch1, &branch2, creation_factor);\n> \n> And here we use the linear assignment algorithm to match the rest of\n> the commits.\n> \n> > +\t\toutput(&branch1, &branch2);\n> \n> And finally we print the output.  We don't seem to use the util->i\n> that's assigned for range b (or range 2) anywhere at the moment, which\n> I was wondering about earlier, so I assume it's there mainly for\n> symmetry, but it doesn't really hurt other than me wondering what it\n> was for.\n\nIt's there because it would require extra care to do it only for one side.\n\n> > +\t}\n> > +\n> > +\tstring_list_clear(&branch1, 1);\n> > +\tstring_list_clear(&branch2, 1);\n> > +\n> > +\treturn res;\n> > +}\n> > diff --git a/range-diff.h b/range-diff.h\n> > new file mode 100644\n> > index 000000000..dd30449c4\n> > --- /dev/null\n> > +++ b/range-diff.h\n> > @@ -0,0 +1,7 @@\n> > +#ifndef BRANCH_DIFF_H\n> > +#define BRANCH_DIFF_H\n> \n> s/BRANCH/RANGE/ above?\n\nGood eyes.\n\nThank you for your review. It is nice to see that you can follow the code\nand come to the same conclusions as I. Can I always have you as reviewer?\n\nCiao,\nDscho\n\n> > +int show_range_diff(const char *range1, const char *range2,\n> > +\t\t    int creation_factor);\n> > +\n> > +#endif\n> > -- \n> > gitgitgadget\n> > \n> \n"},{"id":"353907","messageId":"nycvar.QRO.7.76.6.1807301826480.10478@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180729214543.GD9955@hank.intra.tgummerer.com","subject":"Re: [PATCH v4 05/21] range-diff: also show the diff between patches","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-30T16:28:30Z","receivedAt":"2018-07-30T16:28:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Thomas & Eric,\n\nOn Sun, 29 Jul 2018, Thomas Gummerer wrote:\n\n> On 07/29, Eric Sunshine wrote:\n> > On Sun, Jul 29, 2018 at 3:04 PM Thomas Gummerer <t.gummerer@gmail.com> wrote:\n> > > On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> > > > Just like tbdiff, we now show the diff between matching patches. This is\n> > > > a \"diff of two diffs\", so it can be a bit daunting to read for the\n> > > > beginner.\n> > > > [...]\n> > > > Note also: while tbdiff accepts the `--no-patches` option to suppress\n> > > > these diffs between patches, we prefer the `-s` option that is\n> > > > automatically supported via our use of diff_opt_parse().\n> > >\n> > > One slightly unfortunate thing here is that we don't show these\n> > > options in 'git range-diff -h', which would be nice to have.  I don't\n> > > know if that's possible in git right now, if it's not easily possible,\n> > > I definitely wouldn't want to delay this series for that, and we could\n> > > just add it to the list of possible future enhancements that other\n> > > people mentioned.\n> > \n> > This issue is not specific to git-range-diff; it's shared by other\n> > commands which inherit diff options via diff_opt_parse(). For\n> > instance, \"git log -h\" doesn't show diff-related options either, yet\n> > it accepts them.\n> \n> Fair enough, that makes sense.  Thanks for the pointer!\n> \n> There's one more thing that I noticed here:\n> \n>     git range-diff --no-patches\n>     fatal: single arg format requires a symmetric range\n> \n> Which is a slightly confusing error message.  In contrast git log does\n> the following on an unrecognized argument:\n> \n>     git log --no-patches\n>     fatal: unrecognized argument: --no-patches\n> \n> which is a little better I think.  I do however also thing the \"fatal:\n> single arg format requires a symmetric range\" is useful when someone\n> genuinely tries to use the single argument version of the command.  So\n> I don't know what a good solution for this would be.\n\nI immediately thought of testing for a leading `-` of the remaining\nargument, but I could imagine that somebody enterprisey uses\n\n\tgit range-diff -- -my-first-attempt...-my-second-attempt\n\nand I do not really want to complexify the code... Ideas?\n\n> > > > diff --git a/range-diff.c b/range-diff.c\n> > > > @@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)\n> > > >                       printf(\"%d: %s ! %d: %s\\n\",\n> > > >                              b_util->matching + 1, short_oid(a_util),\n> > > >                              j + 1, short_oid(b_util));\n> > > > +                     if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n> > >\n> > > Looking at this line, it looks like it would be easy to support\n> > > '--no-patches' as well, which may be slightly easier to understand that\n> > > '-s' to someone new to the command.  But again that can be added later\n> > > if someone actually cares about it.\n> > \n> > What wasn't mentioned (but was implied) by the commit message is that\n> > \"-s\" is short for \"--no-patch\", which also comes for free via\n> > diff_opt_parse(). True, \"--no-patch\" isn't spelled exactly the same as\n> > \"--no-patches\", but git-range-diff isn't exactly a perfect tbdiff\n> > clone, so hopefully not a git problem. Moreover, \"--no-patch\" is\n> > internally consistent within the Git builtin commands.\n> \n> Makes sense, thanks!  \"--no-patch\" does make sense to me.  There's\n> still a lot of command line flags in git to learn for me, even after\n> all this time using it ;)  Might be nice to spell it out in the commit\n> message for someone like me, especially as \"--no-patches\" is already\n> mentioned.  Though I guess most regulars here would know about\n> \"--no-patch\", so maybe it's not worth it.  Anyway that is definitely\n> not worth another round here.\n\nSure, but not many users learn from reading the commit history...\n\n:-)\n\nCiao,\nDscho\n"},{"id":"353912","messageId":"nycvar.QRO.7.76.6.1807301830330.10478@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAPig+cRd2V_hN0BVCcevXhu1v_QpL76mhqTGQmWPLK7sAD4Ytw@mail.gmail.com","subject":"Re: [PATCH v4 11/21] range-diff: add tests","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-07-30T16:30:55Z","receivedAt":"2018-07-30T16:31:03Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Sun, 22 Jul 2018, Eric Sunshine wrote:\n\n> On Sat, Jul 21, 2018 at 6:05 PM Thomas Rast via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> > These are essentially lifted from https://github.com/trast/tbdiff, with\n> > light touch-ups to account for the command now being names `git\n> \n> s/names/named/\n\nThanks.\n\nI already pushed an update to https://github.com/gitgitgadget/git/pull/1.\n\nCiao,\nDscho\n\n> \n> > range-diff`.\n> >\n> > Apart from renaming `tbdiff` to `range-diff`, only one test case needed\n> > to be adjusted: 11 - 'changed message'.\n> >\n> > The underlying reason it had to be adjusted is that diff generation is\n> > sometimes ambiguous. In this case, a comment line and an empty line are\n> > added, but it is ambiguous whether they were added after the existing\n> > empty line, or whether an empty line and the comment line are added\n> > *before* the existing empty line. And apparently xdiff picks a different\n> > option here than Python's difflib.\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> \n"},{"id":"353948","messageId":"xmqq600wfpfl.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1807301830330.10478@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v4 11/21] range-diff: add tests","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-30T20:18:06Z","receivedAt":"2018-07-30T20:18:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> On Sun, 22 Jul 2018, Eric Sunshine wrote:\n>\n>> On Sat, Jul 21, 2018 at 6:05 PM Thomas Rast via GitGitGadget\n>> <gitgitgadget@gmail.com> wrote:\n>> > These are essentially lifted from https://github.com/trast/tbdiff, with\n>> > light touch-ups to account for the command now being names `git\n>> \n>> s/names/named/\n>\n> Thanks.\n>\n> I already pushed an update to https://github.com/gitgitgadget/git/pull/1.\n\nShould I take \"pushed to ... GGG\" to mean \"do not merge what you\nhave to 'next' yet, as there will be an updated series (not\nincremental) being prepared\"?\n"},{"id":"353962","messageId":"20180730211636.GK9955@hank.intra.tgummerer.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1807301759340.10478@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v4 03/21] range-diff: first rudimentary implementation","fromName":"Thomas Gummerer","fromEmail":"t.gummerer@gmail.com","sentAt":"2018-07-30T21:16:36Z","receivedAt":"2018-07-30T21:16:42Z","isPatch":true,"sender":{"key":"t.gummerer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/191004?v=4"},"body":"On 07/30, Johannes Schindelin wrote:\n> Hi Thomas,\n> \n> On Sun, 29 Jul 2018, Thomas Gummerer wrote:\n> \n> > On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> > > \n> > > [...]\n> > > \n> > > +static void find_exact_matches(struct string_list *a, struct string_list *b)\n> > > +{\n> > > +\tstruct hashmap map;\n> > > +\tint i;\n> > > +\n> > > +\thashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);\n> > > +\n> > > +\t/* First, add the patches of a to a hash map */\n> > > +\tfor (i = 0; i < a->nr; i++) {\n> > > +\t\tstruct patch_util *util = a->items[i].util;\n> > > +\n> > > +\t\tutil->i = i;\n> > > +\t\tutil->patch = a->items[i].string;\n> > > +\t\tutil->diff = util->patch + util->diff_offset;\n> > > +\t\thashmap_entry_init(util, strhash(util->diff));\n> > > +\t\thashmap_add(&map, util);\n> > > +\t}\n> > > +\n> > > +\t/* Now try to find exact matches in b */\n> > > +\tfor (i = 0; i < b->nr; i++) {\n> > > +\t\tstruct patch_util *util = b->items[i].util, *other;\n> > > +\n> > > +\t\tutil->i = i;\n> > > +\t\tutil->patch = b->items[i].string;\n> > > +\t\tutil->diff = util->patch + util->diff_offset;\n> > > +\t\thashmap_entry_init(util, strhash(util->diff));\n> > > +\t\tother = hashmap_remove(&map, util, NULL);\n> > > +\t\tif (other) {\n> > > +\t\t\tif (other->matching >= 0)\n> > > +\t\t\t\tBUG(\"already assigned!\");\n> > > +\n> > > +\t\t\tother->matching = i;\n> > > +\t\t\tutil->matching = other->i;\n> > > +\t\t}\n> > > +\t}\n> > \n> > One possibly interesting corner case here is what happens when there\n> > are two patches that have the exact same diff, for example in the\n> > pathological case of commit A doing something, commit B reverting\n> > commit A, and then commit C reverting commit B, so it ends up with the\n> > same diff.\n> > \n> > Having those same commits unchanged in both ranges (e.g. if a commit\n> > earlier in the range has been changed, and range B has been rebased on\n> > top of that), we'd get the following mapping from range A to range B\n> > for the commits in question:\n> > \n> > A -> C\n> > B -> B\n> > C -> A\n> > \n> > Which is not quite what I would expect as the user (even though it is\n> > a valid mapping, and it probably doesn't matter too much for the end\n> > result of the range diff, as nothing has changed between the commits\n> > anyway).  So I'm not sure it's worth fixing this, as it is a\n> > pathological case, and nothing really breaks.\n> \n> Indeed. As far as I am concerned, this falls squarely into the \"let's\n> cross that bridge when, or if, we reach it\" category.\n\nMakes sense, this can definitely be addressed later.\n\n> > > +\n> > > +\thashmap_free(&map, 0);\n> > > +}\n> > > +\n> > > +static void diffsize_consume(void *data, char *line, unsigned long len)\n> > > +{\n> > > +\t(*(int *)data)++;\n> > > +}\n> > > +\n> > > +static int diffsize(const char *a, const char *b)\n> > > +{\n> > > +\txpparam_t pp = { 0 };\n> > > +\txdemitconf_t cfg = { 0 };\n> > > +\tmmfile_t mf1, mf2;\n> > > +\tint count = 0;\n> > > +\n> > > +\tmf1.ptr = (char *)a;\n> > > +\tmf1.size = strlen(a);\n> > > +\tmf2.ptr = (char *)b;\n> > > +\tmf2.size = strlen(b);\n> > > +\n> > > +\tcfg.ctxlen = 3;\n> > > +\tif (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))\n> > > +\t\treturn count;\n> > > +\n> > > +\terror(_(\"failed to generate diff\"));\n> > > +\treturn COST_MAX;\n> > > +}\n> > > +\n> > > +static void get_correspondences(struct string_list *a, struct string_list *b,\n> > > +\t\t\t\tint creation_factor)\n> > > +{\n> > > +\tint n = a->nr + b->nr;\n> > > +\tint *cost, c, *a2b, *b2a;\n> > > +\tint i, j;\n> > > +\n> > > +\tALLOC_ARRAY(cost, st_mult(n, n));\n> > > +\tALLOC_ARRAY(a2b, n);\n> > > +\tALLOC_ARRAY(b2a, n);\n> > > +\n> > > +\tfor (i = 0; i < a->nr; i++) {\n> > > +\t\tstruct patch_util *a_util = a->items[i].util;\n> > > +\n> > > +\t\tfor (j = 0; j < b->nr; j++) {\n> > > +\t\t\tstruct patch_util *b_util = b->items[j].util;\n> > > +\n> > > +\t\t\tif (a_util->matching == j)\n> > > +\t\t\t\tc = 0;\n> > > +\t\t\telse if (a_util->matching < 0 && b_util->matching < 0)\n> > > +\t\t\t\tc = diffsize(a_util->diff, b_util->diff);\n> > > +\t\t\telse\n> > > +\t\t\t\tc = COST_MAX;\n> > > +\t\t\tcost[i + n * j] = c;\n> > > +\t\t}\n> > > +\n> > > +\t\tc = a_util->matching < 0 ?\n> > > +\t\t\ta_util->diffsize * creation_factor / 100 : COST_MAX;\n> > > +\t\tfor (j = b->nr; j < n; j++)\n> > > +\t\t\tcost[i + n * j] = c;\n> > > +\t}\n> > > +\n> > > +\tfor (j = 0; j < b->nr; j++) {\n> > > +\t\tstruct patch_util *util = b->items[j].util;\n> > > +\n> > > +\t\tc = util->matching < 0 ?\n> > > +\t\t\tutil->diffsize * creation_factor / 100 : COST_MAX;\n> > > +\t\tfor (i = a->nr; i < n; i++)\n> > > +\t\t\tcost[i + n * j] = c;\n> > > +\t}\n> > > +\n> > > +\tfor (i = a->nr; i < n; i++)\n> > > +\t\tfor (j = b->nr; j < n; j++)\n> > > +\t\t\tcost[i + n * j] = 0;\n> > > +\n> > > +\tcompute_assignment(n, n, cost, a2b, b2a);\n> > > +\n> > > +\tfor (i = 0; i < a->nr; i++)\n> > > +\t\tif (a2b[i] >= 0 && a2b[i] < b->nr) {\n> > > +\t\t\tstruct patch_util *a_util = a->items[i].util;\n> > > +\t\t\tstruct patch_util *b_util = b->items[a2b[i]].util;\n> > > +\n> > > +\t\t\ta_util->matching = a2b[i];\n> > > +\t\t\tb_util->matching = i;\n> > \n> > So here we re-assign 'matching' in the struct regardless of whether it\n> > was assigned before while searching for exact matches or not.\n> \n> If `matching` were assigned here, it would indicate a bug in the code, I\n> think. Before this loop, all of the `matching` fields should be `NULL`,\n> and the `compute_assignment()` function is expected to populate `a2b` and\n> `b2a` appropriately, i.e. no \"double booking\".\n\nHmm we are using the 'matching' fields in the loops above, e.g.:\n\n\tif (a_util->matching == j)\n\t\tc = 0;\n\nThey're also ints, so they can't be `NULL`, right?  So what I was\nthinking is that we could do\n\n\t if (a_util->matching < 0) {\n\t\ta_util->matching = a2b[i];\n\t\tb_util->matching = i;\n\t}\n\nAnyway, I don't think it really matters, as there is no \"double\nbooking\", so we should essentially get the same results.\n\n> > Shouldn't diffsize for matching patches also be 0?  So are we doing\n> > the 'find_exact_matches()' bit only as an optimization, or am I\n> > missing some other reason why that is beneficial?\n> \n> An optimization.\n> \n> Remember, the linear assignment algorithm runs in O(n^3). That's not a\n> laughing matter by any stretch of imagination. The more stuff we can get\n> out of the way, as quickly as possible, the less it hurts to have a cubic\n> runtime complexity.\n\nMakes sense, that's what I was expecting, just wanted to double check\nthat I didn't miss something.  Thanks for confirming!\n\n> > > +\t\t}\n> > > +\n> > > +\tfree(cost);\n> > > +\tfree(a2b);\n> > > +\tfree(b2a);\n> > > +}\n> > > +\n> > > +static const char *short_oid(struct patch_util *util)\n> > > +{\n> > > +\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n> > > +}\n> > > +\n> > > +static void output(struct string_list *a, struct string_list *b)\n> > > +{\n> > > +\tint i;\n> > > +\n> > > +\tfor (i = 0; i < b->nr; i++) {\n> > > +\t\tstruct patch_util *util = b->items[i].util, *prev;\n> > > +\n> > > +\t\tif (util->matching < 0)\n> > > +\t\t\tprintf(\"-: -------- > %d: %s\\n\",\n> > > +\t\t\t\t\ti + 1, short_oid(util));\n> > > +\t\telse {\n> > > +\t\t\tprev = a->items[util->matching].util;\n> > > +\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n> > > +\t\t\t       util->matching + 1, short_oid(prev),\n> > > +\t\t\t       i + 1, short_oid(util));\n> > > +\t\t}\n> > > +\t}\n> > > +\n> > > +\tfor (i = 0; i < a->nr; i++) {\n> > > +\t\tstruct patch_util *util = a->items[i].util;\n> > > +\n> > > +\t\tif (util->matching < 0)\n> > > +\t\t\tprintf(\"%d: %s < -: --------\\n\",\n> > > +\t\t\t       i + 1, short_oid(util));\n> > > +\t}\n> > > +}\n> > > +\n> > > +int show_range_diff(const char *range1, const char *range2,\n> > > +\t\t    int creation_factor)\n> > > +{\n> > > +\tint res = 0;\n> > > +\n> > > +\tstruct string_list branch1 = STRING_LIST_INIT_DUP;\n> > > +\tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n> > > +\n> > > +\tif (read_patches(range1, &branch1))\n> > > +\t\tres = error(_(\"could not parse log for '%s'\"), range1);\n> > > +\tif (!res && read_patches(range2, &branch2))\n> > > +\t\tres = error(_(\"could not parse log for '%s'\"), range2);\n> > > +\n> > > +\tif (!res) {\n> > > +\t\tfind_exact_matches(&branch1, &branch2);\n> > \n> > Note to self: here we assign the matching member of struct patch_util\n> > for each patch in both ranges to a patch number in the other range if\n> > it is an exact match.\n> > \n> > We also assign the patch and diff members, and number the patches\n> > using the 'i' member of struct patch_util.  Let's see if that\n> > numbering is still useful later.\n> > \n> > > +\t\tget_correspondences(&branch1, &branch2, creation_factor);\n> > \n> > And here we use the linear assignment algorithm to match the rest of\n> > the commits.\n> > \n> > > +\t\toutput(&branch1, &branch2);\n> > \n> > And finally we print the output.  We don't seem to use the util->i\n> > that's assigned for range b (or range 2) anywhere at the moment, which\n> > I was wondering about earlier, so I assume it's there mainly for\n> > symmetry, but it doesn't really hurt other than me wondering what it\n> > was for.\n> \n> It's there because it would require extra care to do it only for one side.\n> \n> > > +\t}\n> > > +\n> > > +\tstring_list_clear(&branch1, 1);\n> > > +\tstring_list_clear(&branch2, 1);\n> > > +\n> > > +\treturn res;\n> > > +}\n> > > diff --git a/range-diff.h b/range-diff.h\n> > > new file mode 100644\n> > > index 000000000..dd30449c4\n> > > --- /dev/null\n> > > +++ b/range-diff.h\n> > > @@ -0,0 +1,7 @@\n> > > +#ifndef BRANCH_DIFF_H\n> > > +#define BRANCH_DIFF_H\n> > \n> > s/BRANCH/RANGE/ above?\n> \n> Good eyes.\n> \n> Thank you for your review. It is nice to see that you can follow the code\n> and come to the same conclusions as I. Can I always have you as reviewer?\n\nGlad I could help.  Can I have more hours in a day? ;) I wish I had\nmore time for reviewing code, but sadly the time I have is limited.\nWhich is why I only got to review this now as well.  I did enjoy the\nread!\n\n> Ciao,\n> Dscho\n> \n> > > +int show_range_diff(const char *range1, const char *range2,\n> > > +\t\t    int creation_factor);\n> > > +\n> > > +#endif\n> > > -- \n> > > gitgitgadget\n> > > \n> > \n"},{"id":"353963","messageId":"20180730212606.GL9955@hank.intra.tgummerer.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1807301826480.10478@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v4 05/21] range-diff: also show the diff between patches","fromName":"Thomas Gummerer","fromEmail":"t.gummerer@gmail.com","sentAt":"2018-07-30T21:26:06Z","receivedAt":"2018-07-30T21:26:12Z","isPatch":true,"sender":{"key":"t.gummerer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/191004?v=4"},"body":"On 07/30, Johannes Schindelin wrote:\n> Hi Thomas & Eric,\n> \n> On Sun, 29 Jul 2018, Thomas Gummerer wrote:\n> \n> > On 07/29, Eric Sunshine wrote:\n> > > On Sun, Jul 29, 2018 at 3:04 PM Thomas Gummerer <t.gummerer@gmail.com> wrote:\n> > > > On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> > > > > Just like tbdiff, we now show the diff between matching patches. This is\n> > > > > a \"diff of two diffs\", so it can be a bit daunting to read for the\n> > > > > beginner.\n> > > > > [...]\n> > > > > Note also: while tbdiff accepts the `--no-patches` option to suppress\n> > > > > these diffs between patches, we prefer the `-s` option that is\n> > > > > automatically supported via our use of diff_opt_parse().\n> > > >\n> > > > One slightly unfortunate thing here is that we don't show these\n> > > > options in 'git range-diff -h', which would be nice to have.  I don't\n> > > > know if that's possible in git right now, if it's not easily possible,\n> > > > I definitely wouldn't want to delay this series for that, and we could\n> > > > just add it to the list of possible future enhancements that other\n> > > > people mentioned.\n> > > \n> > > This issue is not specific to git-range-diff; it's shared by other\n> > > commands which inherit diff options via diff_opt_parse(). For\n> > > instance, \"git log -h\" doesn't show diff-related options either, yet\n> > > it accepts them.\n> > \n> > Fair enough, that makes sense.  Thanks for the pointer!\n> > \n> > There's one more thing that I noticed here:\n> > \n> >     git range-diff --no-patches\n> >     fatal: single arg format requires a symmetric range\n> > \n> > Which is a slightly confusing error message.  In contrast git log does\n> > the following on an unrecognized argument:\n> > \n> >     git log --no-patches\n> >     fatal: unrecognized argument: --no-patches\n> > \n> > which is a little better I think.  I do however also thing the \"fatal:\n> > single arg format requires a symmetric range\" is useful when someone\n> > genuinely tries to use the single argument version of the command.  So\n> > I don't know what a good solution for this would be.\n> \n> I immediately thought of testing for a leading `-` of the remaining\n> argument, but I could imagine that somebody enterprisey uses\n> \n> \tgit range-diff -- -my-first-attempt...-my-second-attempt\n> \n> and I do not really want to complexify the code... Ideas?\n\nGood point.  I can't really come up with a good option right now\neither.  It's not too bad, as users just typed the command, so it\nshould be easy enough to see from the previous line what went wrong.\n\nOne potential option may be to turn \"die(_(\"single arg format requires\na symmetric range\"));\" into an 'error()', and show the usage?  I think\nthat may be nice anyway, as \"symmetric range\" may not be immediately\nobvious to everyone, but together with the usage it may be clearer?\n\n> > > > > diff --git a/range-diff.c b/range-diff.c\n> > > > > @@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)\n> > > > >                       printf(\"%d: %s ! %d: %s\\n\",\n> > > > >                              b_util->matching + 1, short_oid(a_util),\n> > > > >                              j + 1, short_oid(b_util));\n> > > > > +                     if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n> > > >\n> > > > Looking at this line, it looks like it would be easy to support\n> > > > '--no-patches' as well, which may be slightly easier to understand that\n> > > > '-s' to someone new to the command.  But again that can be added later\n> > > > if someone actually cares about it.\n> > > \n> > > What wasn't mentioned (but was implied) by the commit message is that\n> > > \"-s\" is short for \"--no-patch\", which also comes for free via\n> > > diff_opt_parse(). True, \"--no-patch\" isn't spelled exactly the same as\n> > > \"--no-patches\", but git-range-diff isn't exactly a perfect tbdiff\n> > > clone, so hopefully not a git problem. Moreover, \"--no-patch\" is\n> > > internally consistent within the Git builtin commands.\n> > \n> > Makes sense, thanks!  \"--no-patch\" does make sense to me.  There's\n> > still a lot of command line flags in git to learn for me, even after\n> > all this time using it ;)  Might be nice to spell it out in the commit\n> > message for someone like me, especially as \"--no-patches\" is already\n> > mentioned.  Though I guess most regulars here would know about\n> > \"--no-patch\", so maybe it's not worth it.  Anyway that is definitely\n> > not worth another round here.\n> \n> Sure, but not many users learn from reading the commit history...\n> \n> :-)\n> \n> Ciao,\n> Dscho\n"},{"id":"353966","messageId":"CAPig+cSeAUWFCBEbk0m7_gmATAaVDg-fi42kq49DuGm3g0L4=Q@mail.gmail.com","threadId":"48405","inReplyTo":"20180730212606.GL9955@hank.intra.tgummerer.com","subject":"Re: [PATCH v4 05/21] range-diff: also show the diff between patches","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-30T21:51:12Z","receivedAt":"2018-07-30T21:51:25Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Jul 30, 2018 at 5:26 PM Thomas Gummerer <t.gummerer@gmail.com> wrote:\n> On 07/30, Johannes Schindelin wrote:\n> > On Sun, 29 Jul 2018, Thomas Gummerer wrote:\n> > > There's one more thing that I noticed here:\n> > >\n> > >     git range-diff --no-patches\n> > >     fatal: single arg format requires a symmetric range\n> > >\n> > I immediately thought of testing for a leading `-` of the remaining\n> > argument, but I could imagine that somebody enterprisey uses\n> >\n> >       git range-diff -- -my-first-attempt...-my-second-attempt\n> >\n> > and I do not really want to complexify the code... Ideas?\n>\n> Good point.  I can't really come up with a good option right now\n> either.  It's not too bad, as users just typed the command, so it\n> should be easy enough to see from the previous line what went wrong.\n\nI think you can attain the desired behavior by making a final\nparse_options() call with empty 'options' list after the call to\ndiff_setup_done(). It's pretty much a one-line fix, but can probably\nbe done as an incremental change rather than rerolling.\n"},{"id":"353977","messageId":"CAGZ79kbnrBHscQUS8WdU9f6edGS=yH9wpVywLGyBxGHU0c_WfQ@mail.gmail.com","threadId":"48405","inReplyTo":"xmqq600wfpfl.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v4 11/21] range-diff: add tests","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-07-30T23:40:45Z","receivedAt":"2018-07-30T23:40:59Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Jul 30, 2018 at 1:18 PM Junio C Hamano <gitster@pobox.com> wrote:\n\n> > I already pushed an update to https://github.com/gitgitgadget/git/pull/1.\n>\n> Should I take \"pushed to ... GGG\" to mean \"do not merge what you\n> have to 'next' yet, as there will be an updated series (not\n> incremental) being prepared\"?\n\nNot speaking for Johannes, but I think that *could* work.\nOn the other hand full rerolls are more expensive to prep\n(at least for me, I guess GGG might change that)\n\ngit range-diff gitgitgadget/pr/1...origin/js/range-diff\nshows there is\n\n* a different header guard:\n   @@ -418,8 +419,8 @@\n     --- /dev/null\n     +++ b/range-diff.h\n     @@\n    -+#ifndef RANGE_DIFF_H\n    -+#define RANGE_DIFF_H\n    ++#ifndef BRANCH_DIFF_H\n    ++#define BRANCH_DIFF_H\n     +\n\n* another different header guard:\n    @@ -239,8 +240,8 @@\n     --- /dev/null\n     +++ b/linear-assignment.h\n     @@\n    -+#ifndef LINEAR_ASSIGNMENT_H\n    -+#define LINEAR_ASSIGNMENT_H\n    ++#ifndef HUNGARIAN_H\n    ++#define HUNGARIAN_H\n     +\n\nalthough this one is the other way round. Did you\nsquash in one header guard and Johannes fixed a\nheader guard in a different file?\n\nWith that said, I can send my series for more color testing\nand better diff.c code on top of either.\n(https://public-inbox.org/git/20180728030448.192177-1-sbeller@google.com/)\n\nStefan\n"},{"id":"354043","messageId":"xmqq36vze8ln.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"CAGZ79kbnrBHscQUS8WdU9f6edGS=yH9wpVywLGyBxGHU0c_WfQ@mail.gmail.com","subject":"Re: [PATCH v4 11/21] range-diff: add tests","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-31T15:19:16Z","receivedAt":"2018-07-31T15:19:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> On Mon, Jul 30, 2018 at 1:18 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n>> > I already pushed an update to https://github.com/gitgitgadget/git/pull/1.\n>>\n>> Should I take \"pushed to ... GGG\" to mean \"do not merge what you\n>> have to 'next' yet, as there will be an updated series (not\n>> incremental) being prepared\"?\n>\n> Not speaking for Johannes, but I think that *could* work.\n\nI wasn't asking if it could or could not work.  I was asking for his\nintentions.\n\nNot knowing it means I cannot merge the topic as-is merged to 'next'\nand have to wait just in case, but his not knowing my waiting would\nmean an update version may never come.\n\n"},{"id":"354799","messageId":"nycvar.QRO.7.76.6.1808081422160.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAGZ79kb4ki0cXLnJHeqzRvWaGWki1_epWOdCy49s_v9cy_tJ2A@mail.gmail.com","subject":"Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-08-08T13:05:25Z","receivedAt":"2018-08-08T13:05:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Mon, 23 Jul 2018, Stefan Beller wrote:\n\n> On Sat, Jul 21, 2018 at 3:04 PM Johannes Schindelin via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> \n> >   1:  39272eefc !  1:  f7e70689e linear-assignment: a function to solve least-cost assignment problems\n> >      @@ -223,9 +223,7 @@\n> >       +                         BUG(\"negative j: %d\", j);\n> >       +                 i = pred[j];\n> >       +                 column2row[j] = i;\n> >      -+                 k = j;\n> >      -+                 j = row2column[i];\n> >      -+                 row2column[i] = k;\n> >      ++                 SWAP(j, row2column[i]);\n> \n> The dual color option (as a default) really helps here. Thanks for that!\n> Does it have to be renamed though? (It's more than two colors; originally\n> it was inverting the beginning signs)\n> \n> Maybe --color=emphasize-later\n> assuming there will be other modes for coloring, such as \"diff-only\",\n> which would correspond with --no-dual-color, or \"none\" that will correspond\n> would be --no-color. I imagine there could be more fancy things, hence I would\n> propose a mode rather than a switch.\n> (Feel free to push back if discussing naming here feels like bike shedding)\n\nI do feel free to push back on that.\n\n> 2:  7f15b26d4ea !  82:  88134121d2a Introduce `range-diff` to compare\n> iterations of a topic branch\n> [...]\n> >       diff --git a/Makefile b/Makefile\n> >       --- a/Makefile\n> >       +++ b/Makefile\n> \n> The line starting with --- is red (default removed color) and the line\n> with +++ is green (default add color).\n> \n> Ideally these two lines and the third line above starting with \"diff --git\"\n> would render in GIT_COLOR_BOLD (\"METAINFO\").\n\nI agree that is not the best coloring here, but as you remarked elsewhere,\nit would require content-aware dual coloring, and I am loathe to try to\nimplement that for two reasons: 1) it would take most likely a long time\nto design and implement that, and 2) I don't have that time.\n\nSo I would like to declare that good enough is good enough in this case.\n\n> >   3:  076e1192d !  3:  4e3fb47a1 range-diff: first rudimentary implementation\n> >      @@ -4,7 +4,7 @@\n> >\n> >           At this stage, `git range-diff` can determine corresponding commits\n> >           of two related commit ranges. This makes use of the recently introduced\n> >      -    implementation of the Hungarian algorithm.\n> >      +    implementation of the linear assignment algorithm.\n> >\n> >           The core of this patch is a straight port of the ideas of tbdiff, the\n> >           apparently dormant project at https://github.com/trast/tbdiff.\n> >      @@ -51,19 +51,17 @@\n> >       + int res = 0;\n> >       + struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n> >\n> >      -- argc = parse_options(argc, argv, NULL, options,\n> >      --                      builtin_range_diff_usage, 0);\n> >      -+ argc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,\n> >      -+                      0);\n> >      +  argc = parse_options(argc, argv, NULL, options,\n> >      +                       builtin_range_diff_usage, 0);\n> \n> This is really nice in colors when viewed locally.\n> \n> >  16:  dfa7b1e71 <  -:  --------- range-diff --dual-color: work around bogus white-space warning\n> >   -:  --------- > 16:  f4252f2b2 range-diff --dual-color: fix bogus white-space warning\n> \n> Ah; here my initial assumption of only reviewing the range-diff breaks down now.\n> I'll dig into patch 16 separately.\n\nRight. This was an almost complete rewrite, and then next iteration will\nhopefully bring another complete rewrite: disabling whitespace warnings in\ndual color mode.\n\n> Maybe it is worth having an option to expand all \"new\" patches.\n\nSure.\n\nAnd I also have a use case for --left-only/--right-only.\n\nAnd I also have a strong use case (and so does Junio, it seems, or for\nthat matter, anybody contributing to Git due to Junio's insistence on\nsigning off on each patch, rather than on the merge commit) for something\nlike --ignore-lines=<regex>.\n\nAnd you probably guess what I will say next: these features will make for\nreally fantastic patch series *on top* of mine. There really is no good\nreason to delay the current patch series just to cram more features into\nit that had not been planned in the first place.\n\n> (Given that the range-diff\n> pr-1/dscho/branch-diff-v3...pr-1/dscho/branch-diff-v4 told me you used a\n> different base, this is a hard problem, as I certainly would want to\n> skip over all new base commits, but this one is interesting to look at.\n> An easy way out: Maybe an option to expand any new commits/patches after\n> the first expansion? Asking for opinions rather than implementing it)\n> \n> >  19:  144363006 <  -:  --------- range-diff: left-pad patch numbers\n> >   -:  --------- > 19:  07ec215e8 range-diff: left-pad patch numbers\n> \n> >   -:  --------- > 21:  d8498fb32 range-diff: use dim/bold cues to improve dual color mode\n> \n> Those are interesting, I'll look at them separately, too.\n\nThanks,\nDscho\n"},{"id":"354872","messageId":"CAGZ79kbj2sgKOmouvLDuXic3vq9RG1LZ_retOqMwX_YZtMP+1Q@mail.gmail.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1808081422160.71@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-08-08T17:33:23Z","receivedAt":"2018-08-08T17:33:37Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Wed, Aug 8, 2018 at 6:05 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n\n> > [...]\n> > >       diff --git a/Makefile b/Makefile\n> > >       --- a/Makefile\n> > >       +++ b/Makefile\n> >\n> > The line starting with --- is red (default removed color) and the line\n> > with +++ is green (default add color).\n> >\n> > Ideally these two lines and the third line above starting with \"diff --git\"\n> > would render in GIT_COLOR_BOLD (\"METAINFO\").\n>\n> I agree that is not the best coloring here, but as you remarked elsewhere,\n> it would require content-aware dual coloring, and I am loathe to try to\n> implement that for two reasons: 1) it would take most likely a long time\n> to design and implement that, and 2) I don't have that time.\n>\n> So I would like to declare that good enough is good enough in this case.\n\nI anticipated this answer, so I wrote some patches myself, starting at\nhttps://public-inbox.org/git/20180804015317.182683-1-sbeller@google.com/\nspecifically\nhttps://public-inbox.org/git/20180804015317.182683-5-sbeller@google.com/\n\nI plan on resending these on top of your resend (if any) at a later convenient\ntime for both you and Junio, as noted in\nhttps://public-inbox.org/git/CAGZ79kZnVEsvpicNu7LXkRcHuRqGvESfvG3DL5O_2kPVYrW-Gg@mail.gmail.com/\n\n\n>\n> > >   3:  076e1192d !  3:  4e3fb47a1 range-diff: first rudimentary implementation\n> > >      @@ -4,7 +4,7 @@\n> > >\n> > >           At this stage, `git range-diff` can determine corresponding commits\n> > >           of two related commit ranges. This makes use of the recently introduced\n> > >      -    implementation of the Hungarian algorithm.\n> > >      +    implementation of the linear assignment algorithm.\n> > >\n> > >           The core of this patch is a straight port of the ideas of tbdiff, the\n> > >           apparently dormant project at https://github.com/trast/tbdiff.\n> > >      @@ -51,19 +51,17 @@\n> > >       + int res = 0;\n> > >       + struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n> > >\n> > >      -- argc = parse_options(argc, argv, NULL, options,\n> > >      --                      builtin_range_diff_usage, 0);\n> > >      -+ argc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,\n> > >      -+                      0);\n> > >      +  argc = parse_options(argc, argv, NULL, options,\n> > >      +                       builtin_range_diff_usage, 0);\n> >\n> > This is really nice in colors when viewed locally.\n> >\n> > >  16:  dfa7b1e71 <  -:  --------- range-diff --dual-color: work around bogus white-space warning\n> > >   -:  --------- > 16:  f4252f2b2 range-diff --dual-color: fix bogus white-space warning\n> >\n> > Ah; here my initial assumption of only reviewing the range-diff breaks down now.\n> > I'll dig into patch 16 separately.\n>\n> Right. This was an almost complete rewrite, and then next iteration will\n> hopefully bring another complete rewrite: disabling whitespace warnings in\n> dual color mode.\n>\n> > Maybe it is worth having an option to expand all \"new\" patches.\n>\n> Sure.\n>\n> And I also have a use case for --left-only/--right-only.\n>\n> And I also have a strong use case (and so does Junio, it seems, or for\n> that matter, anybody contributing to Git due to Junio's insistence on\n> signing off on each patch, rather than on the merge commit) for something\n> like --ignore-lines=<regex>.\n>\n> And you probably guess what I will say next: these features will make for\n> really fantastic patch series *on top* of mine. There really is no good\n> reason to delay the current patch series just to cram more features into\n> it that had not been planned in the first place.\n\nYes, I agree. I am unsure about the current state of your series, though;\n\nJunio thinks (expects?) a resend, whereas you seem to call it good enough\nbut also said (some time back) that you want to resend due to Thomas\nfeedback.\n\nI do have 2 series on top of the current range-diff.\n* The first (queued by Junio as origin/sb/range-diff-colors)\n   adds a basic test for colors and improves diff.c readability\n* The second (linked above) changes colors for some lines.\n\nI do not want to build more on top as long as I do not know if\nyou resend (and how much it'll change)\n\nThanks,\nStefan\n"},{"id":"355148","messageId":"nycvar.QRO.7.76.6.1808102223580.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAPig+cQ6H1ys6MrZ3qV_93WCQxz0mxnQJMm23XRTrDh1WHHnGg@mail.gmail.com","subject":"Re: [PATCH v4 18/21] completion: support `git range-diff`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-08-10T20:24:46Z","receivedAt":"2018-08-10T20:24:53Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\n\nOn Sun, 22 Jul 2018, Eric Sunshine wrote:\n\n> On Sat, Jul 21, 2018 at 6:05 PM Johannes Schindelin via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> > Tab completion of `git range-diff` is very convenient, especially\n> > given that the revision arguments to specify the commit ranges to\n> > compare are typically more complex than, say, what is normally passed\n> > to `git log`.\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> > diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n> > @@ -1976,6 +1976,20 @@ _git_push ()\n> > +_git_range_diff ()\n> > +{\n> > +  case \"$cur\" in\n> > +  --*)\n> > +          __gitcomp \"\n> > +               --creation-factor= --dual-color\n> > +                  $__git_diff_common_options\n> > +                  \"\n> \n> This is indented with a mix of spaces and tabs.\n> \n>     Applying: completion: support `git range-diff`\n>     .git/rebase-apply/patch:18: space before tab in indent.\n>                 --creation-factor= --dual-color\n>     warning: 1 line adds whitespace errors.\n>     Applying: range-diff: make --dual-color the default mode\n>     .git/rebase-apply/patch:105: space before tab in indent.\n>                 --creation-factor= --no-dual-color\n>     warning: 1 line adds whitespace errors.\n> \n> Other parts of this script seem to use tabs for indentation.\n\nThanks.\n\nI guess that this is due to my playing with VS Code and failing to adjust\nindentation rules of anything but C code...\n\nWill be fixed in v5.\n\nCiao,\nDscho\n"},{"id":"355150","messageId":"nycvar.QRO.7.76.6.1808102233490.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180730212606.GL9955@hank.intra.tgummerer.com","subject":"Re: [PATCH v4 05/21] range-diff: also show the diff between patches","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-08-10T20:36:55Z","receivedAt":"2018-08-10T20:37:01Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Thomas,\n\nOn Mon, 30 Jul 2018, Thomas Gummerer wrote:\n\n> On 07/30, Johannes Schindelin wrote:\n> > \n> > On Sun, 29 Jul 2018, Thomas Gummerer wrote:\n> > \n> > > On 07/29, Eric Sunshine wrote:\n> > > > On Sun, Jul 29, 2018 at 3:04 PM Thomas Gummerer <t.gummerer@gmail.com> wrote:\n> > > > > On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> > > > > > Just like tbdiff, we now show the diff between matching patches. This is\n> > > > > > a \"diff of two diffs\", so it can be a bit daunting to read for the\n> > > > > > beginner.\n> > > > > > [...]\n> > > > > > Note also: while tbdiff accepts the `--no-patches` option to suppress\n> > > > > > these diffs between patches, we prefer the `-s` option that is\n> > > > > > automatically supported via our use of diff_opt_parse().\n> > > > >\n> > > > > One slightly unfortunate thing here is that we don't show these\n> > > > > options in 'git range-diff -h', which would be nice to have.  I don't\n> > > > > know if that's possible in git right now, if it's not easily possible,\n> > > > > I definitely wouldn't want to delay this series for that, and we could\n> > > > > just add it to the list of possible future enhancements that other\n> > > > > people mentioned.\n> > > > \n> > > > This issue is not specific to git-range-diff; it's shared by other\n> > > > commands which inherit diff options via diff_opt_parse(). For\n> > > > instance, \"git log -h\" doesn't show diff-related options either, yet\n> > > > it accepts them.\n> > > \n> > > Fair enough, that makes sense.  Thanks for the pointer!\n> > > \n> > > There's one more thing that I noticed here:\n> > > \n> > >     git range-diff --no-patches\n> > >     fatal: single arg format requires a symmetric range\n> > > \n> > > Which is a slightly confusing error message.  In contrast git log does\n> > > the following on an unrecognized argument:\n> > > \n> > >     git log --no-patches\n> > >     fatal: unrecognized argument: --no-patches\n> > > \n> > > which is a little better I think.  I do however also thing the \"fatal:\n> > > single arg format requires a symmetric range\" is useful when someone\n> > > genuinely tries to use the single argument version of the command.  So\n> > > I don't know what a good solution for this would be.\n> > \n> > I immediately thought of testing for a leading `-` of the remaining\n> > argument, but I could imagine that somebody enterprisey uses\n> > \n> > \tgit range-diff -- -my-first-attempt...-my-second-attempt\n> > \n> > and I do not really want to complexify the code... Ideas?\n> \n> Good point.  I can't really come up with a good option right now\n> either.  It's not too bad, as users just typed the command, so it\n> should be easy enough to see from the previous line what went wrong.\n> \n> One potential option may be to turn \"die(_(\"single arg format requires\n> a symmetric range\"));\" into an 'error()', and show the usage?  I think\n> that may be nice anyway, as \"symmetric range\" may not be immediately\n> obvious to everyone, but together with the usage it may be clearer?\n\nAgreed. Will be made so.\n\nCiao,\nDscho\n\n> \n> > > > > > diff --git a/range-diff.c b/range-diff.c\n> > > > > > @@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)\n> > > > > >                       printf(\"%d: %s ! %d: %s\\n\",\n> > > > > >                              b_util->matching + 1, short_oid(a_util),\n> > > > > >                              j + 1, short_oid(b_util));\n> > > > > > +                     if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n> > > > >\n> > > > > Looking at this line, it looks like it would be easy to support\n> > > > > '--no-patches' as well, which may be slightly easier to understand that\n> > > > > '-s' to someone new to the command.  But again that can be added later\n> > > > > if someone actually cares about it.\n> > > > \n> > > > What wasn't mentioned (but was implied) by the commit message is that\n> > > > \"-s\" is short for \"--no-patch\", which also comes for free via\n> > > > diff_opt_parse(). True, \"--no-patch\" isn't spelled exactly the same as\n> > > > \"--no-patches\", but git-range-diff isn't exactly a perfect tbdiff\n> > > > clone, so hopefully not a git problem. Moreover, \"--no-patch\" is\n> > > > internally consistent within the Git builtin commands.\n> > > \n> > > Makes sense, thanks!  \"--no-patch\" does make sense to me.  There's\n> > > still a lot of command line flags in git to learn for me, even after\n> > > all this time using it ;)  Might be nice to spell it out in the commit\n> > > message for someone like me, especially as \"--no-patches\" is already\n> > > mentioned.  Though I guess most regulars here would know about\n> > > \"--no-patch\", so maybe it's not worth it.  Anyway that is definitely\n> > > not worth another round here.\n> > \n> > Sure, but not many users learn from reading the commit history...\n> > \n> > :-)\n> > \n> > Ciao,\n> > Dscho\n> \n"},{"id":"355151","messageId":"nycvar.QRO.7.76.6.1808102237540.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180730211636.GK9955@hank.intra.tgummerer.com","subject":"Re: [PATCH v4 03/21] range-diff: first rudimentary implementation","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-08-10T20:50:31Z","receivedAt":"2018-08-10T20:50:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Thomas,\n\nOn Mon, 30 Jul 2018, Thomas Gummerer wrote:\n\n> On 07/30, Johannes Schindelin wrote:\n> > \n> > On Sun, 29 Jul 2018, Thomas Gummerer wrote:\n> > \n> > > On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> > > > \n> > > > [...]\n> > > > \n> > > > +static void find_exact_matches(struct string_list *a, struct string_list *b)\n> > > > +{\n> > > > +\tstruct hashmap map;\n> > > > +\tint i;\n> > > > +\n> > > > +\thashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);\n> > > > +\n> > > > +\t/* First, add the patches of a to a hash map */\n> > > > +\tfor (i = 0; i < a->nr; i++) {\n> > > > +\t\tstruct patch_util *util = a->items[i].util;\n> > > > +\n> > > > +\t\tutil->i = i;\n> > > > +\t\tutil->patch = a->items[i].string;\n> > > > +\t\tutil->diff = util->patch + util->diff_offset;\n> > > > +\t\thashmap_entry_init(util, strhash(util->diff));\n> > > > +\t\thashmap_add(&map, util);\n> > > > +\t}\n> > > > +\n> > > > +\t/* Now try to find exact matches in b */\n> > > > +\tfor (i = 0; i < b->nr; i++) {\n> > > > +\t\tstruct patch_util *util = b->items[i].util, *other;\n> > > > +\n> > > > +\t\tutil->i = i;\n> > > > +\t\tutil->patch = b->items[i].string;\n> > > > +\t\tutil->diff = util->patch + util->diff_offset;\n> > > > +\t\thashmap_entry_init(util, strhash(util->diff));\n> > > > +\t\tother = hashmap_remove(&map, util, NULL);\n> > > > +\t\tif (other) {\n> > > > +\t\t\tif (other->matching >= 0)\n> > > > +\t\t\t\tBUG(\"already assigned!\");\n> > > > +\n> > > > +\t\t\tother->matching = i;\n> > > > +\t\t\tutil->matching = other->i;\n> > > > +\t\t}\n> > > > +\t}\n> > > \n> > > One possibly interesting corner case here is what happens when there\n> > > are two patches that have the exact same diff, for example in the\n> > > pathological case of commit A doing something, commit B reverting\n> > > commit A, and then commit C reverting commit B, so it ends up with the\n> > > same diff.\n> > > \n> > > Having those same commits unchanged in both ranges (e.g. if a commit\n> > > earlier in the range has been changed, and range B has been rebased on\n> > > top of that), we'd get the following mapping from range A to range B\n> > > for the commits in question:\n> > > \n> > > A -> C\n> > > B -> B\n> > > C -> A\n> > > \n> > > Which is not quite what I would expect as the user (even though it is\n> > > a valid mapping, and it probably doesn't matter too much for the end\n> > > result of the range diff, as nothing has changed between the commits\n> > > anyway).  So I'm not sure it's worth fixing this, as it is a\n> > > pathological case, and nothing really breaks.\n> > \n> > Indeed. As far as I am concerned, this falls squarely into the \"let's\n> > cross that bridge when, or if, we reach it\" category.\n> \n> Makes sense, this can definitely be addressed later.\n> \n> > > > +\n> > > > +\thashmap_free(&map, 0);\n> > > > +}\n> > > > +\n> > > > +static void diffsize_consume(void *data, char *line, unsigned long len)\n> > > > +{\n> > > > +\t(*(int *)data)++;\n> > > > +}\n> > > > +\n> > > > +static int diffsize(const char *a, const char *b)\n> > > > +{\n> > > > +\txpparam_t pp = { 0 };\n> > > > +\txdemitconf_t cfg = { 0 };\n> > > > +\tmmfile_t mf1, mf2;\n> > > > +\tint count = 0;\n> > > > +\n> > > > +\tmf1.ptr = (char *)a;\n> > > > +\tmf1.size = strlen(a);\n> > > > +\tmf2.ptr = (char *)b;\n> > > > +\tmf2.size = strlen(b);\n> > > > +\n> > > > +\tcfg.ctxlen = 3;\n> > > > +\tif (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))\n> > > > +\t\treturn count;\n> > > > +\n> > > > +\terror(_(\"failed to generate diff\"));\n> > > > +\treturn COST_MAX;\n> > > > +}\n> > > > +\n> > > > +static void get_correspondences(struct string_list *a, struct string_list *b,\n> > > > +\t\t\t\tint creation_factor)\n> > > > +{\n> > > > +\tint n = a->nr + b->nr;\n> > > > +\tint *cost, c, *a2b, *b2a;\n> > > > +\tint i, j;\n> > > > +\n> > > > +\tALLOC_ARRAY(cost, st_mult(n, n));\n> > > > +\tALLOC_ARRAY(a2b, n);\n> > > > +\tALLOC_ARRAY(b2a, n);\n> > > > +\n> > > > +\tfor (i = 0; i < a->nr; i++) {\n> > > > +\t\tstruct patch_util *a_util = a->items[i].util;\n> > > > +\n> > > > +\t\tfor (j = 0; j < b->nr; j++) {\n> > > > +\t\t\tstruct patch_util *b_util = b->items[j].util;\n> > > > +\n> > > > +\t\t\tif (a_util->matching == j)\n> > > > +\t\t\t\tc = 0;\n> > > > +\t\t\telse if (a_util->matching < 0 && b_util->matching < 0)\n> > > > +\t\t\t\tc = diffsize(a_util->diff, b_util->diff);\n> > > > +\t\t\telse\n> > > > +\t\t\t\tc = COST_MAX;\n> > > > +\t\t\tcost[i + n * j] = c;\n> > > > +\t\t}\n> > > > +\n> > > > +\t\tc = a_util->matching < 0 ?\n> > > > +\t\t\ta_util->diffsize * creation_factor / 100 : COST_MAX;\n> > > > +\t\tfor (j = b->nr; j < n; j++)\n> > > > +\t\t\tcost[i + n * j] = c;\n> > > > +\t}\n> > > > +\n> > > > +\tfor (j = 0; j < b->nr; j++) {\n> > > > +\t\tstruct patch_util *util = b->items[j].util;\n> > > > +\n> > > > +\t\tc = util->matching < 0 ?\n> > > > +\t\t\tutil->diffsize * creation_factor / 100 : COST_MAX;\n> > > > +\t\tfor (i = a->nr; i < n; i++)\n> > > > +\t\t\tcost[i + n * j] = c;\n> > > > +\t}\n> > > > +\n> > > > +\tfor (i = a->nr; i < n; i++)\n> > > > +\t\tfor (j = b->nr; j < n; j++)\n> > > > +\t\t\tcost[i + n * j] = 0;\n> > > > +\n> > > > +\tcompute_assignment(n, n, cost, a2b, b2a);\n> > > > +\n> > > > +\tfor (i = 0; i < a->nr; i++)\n> > > > +\t\tif (a2b[i] >= 0 && a2b[i] < b->nr) {\n> > > > +\t\t\tstruct patch_util *a_util = a->items[i].util;\n> > > > +\t\t\tstruct patch_util *b_util = b->items[a2b[i]].util;\n> > > > +\n> > > > +\t\t\ta_util->matching = a2b[i];\n> > > > +\t\t\tb_util->matching = i;\n> > > \n> > > So here we re-assign 'matching' in the struct regardless of whether it\n> > > was assigned before while searching for exact matches or not.\n> > \n> > If `matching` were assigned here, it would indicate a bug in the code, I\n> > think. Before this loop, all of the `matching` fields should be `NULL`,\n> > and the `compute_assignment()` function is expected to populate `a2b` and\n> > `b2a` appropriately, i.e. no \"double booking\".\n> \n> Hmm we are using the 'matching' fields in the loops above, e.g.:\n> \n> \tif (a_util->matching == j)\n> \t\tc = 0;\n> \n> They're also ints, so they can't be `NULL`, right?\n\nRight, I was confused, they are not pointers. They are initialized to\n`-1`, though.\n\n> So what I was thinking is that we could do\n> \n> \t if (a_util->matching < 0) {\n> \t\ta_util->matching = a2b[i];\n> \t\tb_util->matching = i;\n> \t}\n\nYes, we could have that safeguard. But then, we should actually rather say\n\n\tif (a_util->matching >= 0)\n\t\tBUG(\"a[%d] already assigned to %d, cannot assign to %d\",\n\t\t    i, a_util->matching, a2b[i]);\n\nThe reason this is a bug is that we really just calculated a linear\nassignment, and a2b was initialized accordingly: it contains a 1:1 mapping\nbetween a->items and b->items.\n\nSide note: technically, the linear assignment is able to handle two\ndifferently-sized sets. However, due to implementation details, we do not\nuse this, as a->nr and b->nr are both set to `n`. The reason is that we\nwant to avoid forcing matches where there are none, i.e. we add specific\nentries to the cost matrix to allow for the \"creation\" or \"destruction\" if\nthat is cheaper than to match two completely unrelated commits. It just\nso happens that these filler entries result in identical set sizes.\n\nSo practically, that `if()` guard is not necessary, and honestly, I would\nstumble over it every time (\"Why is this needed? Should `a2b` and `b2a`\nnot have a complete assignment already?\")), so I'd rather not have it.\n\n> Anyway, I don't think it really matters, as there is no \"double\n> booking\", so we should essentially get the same results.\n\nExactly.\n\nIf you were worried about it, I would introduce that guarded BUG(), but it\nis not an off-by-one error, so I probably did not introduce a bug here.\n\n> Glad I could help.  Can I have more hours in a day? ;) I wish I had\n> more time for reviewing code, but sadly the time I have is limited.\n> Which is why I only got to review this now as well.  I did enjoy the\n> read!\n\nThank you so much! I appreciate your help, and it was a very pleasant and\nuseful review that really made the patch series better. There were three\ndistinct issues that would have otherwise been missed before this hit\n`next`.\n\nThanks!\nDscho\n"},{"id":"355152","messageId":"nycvar.QRO.7.76.6.1808081507040.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180729193818.GE2734@hank.intra.tgummerer.com","subject":"Re: [PATCH v4 09/21] range-diff: adjust the output of the commit pairs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-08-10T21:01:03Z","receivedAt":"2018-08-10T21:01:09Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Thomas,\n\nOn Sun, 29 Jul 2018, Thomas Gummerer wrote:\n\n> On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> > From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > \n> > This change brings `git range-diff` yet another step closer to\n> > feature parity with tbdiff: it now shows the oneline, too, and indicates\n> > with `=` when the commits have identical diffs.\n> > \n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> >  range-diff.c | 64 ++++++++++++++++++++++++++++++++++++++++++++--------\n> >  1 file changed, 55 insertions(+), 9 deletions(-)\n> > \n> > diff --git a/range-diff.c b/range-diff.c\n> > index 1ecee2c09..8329f52e7 100644\n> > --- a/range-diff.c\n> > +++ b/range-diff.c\n> > @@ -7,6 +7,8 @@\n> >  #include \"xdiff-interface.h\"\n> >  #include \"linear-assignment.h\"\n> >  #include \"diffcore.h\"\n> > +#include \"commit.h\"\n> > +#include \"pretty.h\"\n> >  \n> >  struct patch_util {\n> >  \t/* For the search for an exact match */\n> > @@ -255,9 +257,54 @@ static void get_correspondences(struct string_list *a, struct string_list *b,\n> >  \tfree(b2a);\n> >  }\n> >  \n> > -static const char *short_oid(struct patch_util *util)\n> > +static void output_pair_header(struct strbuf *buf,\n> > +\t\t\t       struct strbuf *dashes,\n> > +\t\t\t       struct patch_util *a_util,\n> > +\t\t\t       struct patch_util *b_util)\n> >  {\n> > -\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n> > +\tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n> > +\tstruct commit *commit;\n> > +\n> > +\tif (!dashes->len)\n> > +\t\tstrbuf_addchars(dashes, '-',\n> > +\t\t\t\tstrlen(find_unique_abbrev(oid,\n> > +\t\t\t\t\t\t\t  DEFAULT_ABBREV)));\n> \n> We're doing this only once, which makes sense.  What's a bit\n> unfortunate here I guess is that if the first commit we're dealing\n> with in the range-diff has a longer unique abbreviation, the dashes\n> will be longer for all commits, even if all the others have a shorter\n> abbreviation.\n> \n> Tbh I don't really know what the right thing to do here is, so this is\n> probably as good a heuristic as any.  It would probably be worse to\n> have different length dashes lines, than guessing based on the first\n> commit.\n\nYes, I had the same reaction as you did. But still, I think it is the best\nwe can do, and I don't think it is worth spending a lot of thought about\nways to fix this, up and until the day when somebody experiences a real\nproblem there (and that it will be *their* responsibility to fix it).\n\n> > +\n> > +\tstrbuf_reset(buf);\n> > +\tif (!a_util)\n> > +\t\tstrbuf_addf(buf, \"-:  %s \", dashes->buf);\n> > +\telse\n> > +\t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n> > +\t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n> > +\n> > +\tif (!a_util)\n> > +\t\tstrbuf_addch(buf, '>');\n> > +\telse if (!b_util)\n> > +\t\tstrbuf_addch(buf, '<');\n> > +\telse if (strcmp(a_util->patch, b_util->patch))\n> > +\t\tstrbuf_addch(buf, '!');\n> > +\telse\n> > +\t\tstrbuf_addch(buf, '=');\n> > +\n> > +\tif (!b_util)\n> > +\t\tstrbuf_addf(buf, \" -:  %s\", dashes->buf);\n> > +\telse\n> > +\t\tstrbuf_addf(buf, \" %d:  %s\", b_util->i + 1,\n> > +\t\t\t    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));\n> > +\n> > +\tcommit = lookup_commit_reference(oid);\n> \n> This bit surprised me slightly.  May be worth mentioning that we now\n> also show the first line of the commit message here.\n\nRight...\n\n> \n> > +\tif (commit) {\n> > +\t\tconst char *commit_buffer = get_commit_buffer(commit, NULL);\n> > +\t\tconst char *subject;\n> > +\n> > +\t\tfind_commit_subject(commit_buffer, &subject);\n> > +\t\tstrbuf_addch(buf, ' ');\n> > +\t\tformat_subject(buf, subject, \" \");\n> > +\t\tunuse_commit_buffer(commit, commit_buffer);\n> \n> I think the above could be written slightly shorter as\n> \n>     strbuf_addch(buf, ' ');\n>     pp_commit_easy(CMIT_FMT_ONELINE, commit, &buf);\n\nI guess so. I shied away from the pretty-printing machinery because last\ntime I tried to use it in a libified manner I had to put in a major fight\nto get the code into git.git. But I guess that was because of a user\nformat (which uses global state, something I still would like to fix, but\nthat fight just cost me too much time), which is not the case here.\n\n> Not sure if it's worth changing this at this stage of the series\n> though, or if there is something in the above that I'm missing, that\n> would make the shorter version not workable.\n\nI think your version is not only shorter, but also possibly safer.\n\nCiao,\nDscho\n"},{"id":"355153","messageId":"nycvar.QRO.7.76.6.1808102301500.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180729205202.GA9955@hank.intra.tgummerer.com","subject":"Re: [PATCH v4 10/21] range-diff: do not show \"function names\" in hunk headers","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-08-10T21:03:00Z","receivedAt":"2018-08-10T21:03:07Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Thomas,\n\nOn Sun, 29 Jul 2018, Thomas Gummerer wrote:\n\n> On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> > From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > \n> > We are comparing complete, formatted commit messages with patches. There\n> > are no function names here, so stop looking for them.\n> \n> While there are no function names here, trying out range-diff without\n> this patch applied, the headers were getting here do seem kind of\n> useful:\n> \n>     1: 92588fc6b6 ! 3: 43c9ef552c\n>         @@ -8,8 +8,16 @@ diff --git a/read-cache.c b/read-cache.c\n>     \t[...]\n> \n> The filename can be quite useful in this output.  I guess this is a\n> bit brittle though, so I'm also happy to defer changing this to show\n> something useful to the list of possible future enhancements\n> (obviously doesn't necessarily have to be implemented by you at that\n> point).\n\nTo be honest, I never thought about this, I just assumed that tbdiff had\ngood reasons to strip this.\n\nI agree, though, that we (as in: you :-)) can look at this after\n`range-diff` lands in `next`.\n\nCiao,\nDscho\n"},{"id":"355154","messageId":"nycvar.QRO.7.76.6.1808102303270.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAGZ79kZRoN6DmKYPyvQ33yXqxz8ukfuXVROw9pzZBvob-vjHAQ@mail.gmail.com","subject":"Re: [PATCH v4 16/21] range-diff --dual-color: fix bogus white-space warning","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-08-10T21:05:24Z","receivedAt":"2018-08-10T21:05:31Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Mon, 23 Jul 2018, Stefan Beller wrote:\n\n> On Sat, Jul 21, 2018 at 3:05 PM Johannes Schindelin via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> >\n> > From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> >\n> > When displaying a diff of diffs, it is possible that there is an outer\n> > `+` before a context line. That happens when the context changed between\n> > old and new commit. When that context line starts with a tab (after the\n> > space that marks it as context line), our diff machinery spits out a\n> > white-space error (space before tab), but in this case, that is\n> > incorrect.\n> >\n> > Fix this by adding a specific whitespace flag that simply ignores the\n> > first space in the output.\n> \n> That sounds like a simple (not easy) solution, which sounds acceptable\n> to me here.\n> \n> I guess you dropped all ideas that I originally proposed for the cleanup\n> regarding ws. that is fine, I can roll the cleanup on top of your patches\n> here.\n\nYes, sorry, I got the impression after our chat on IRC that you tried to\naddress something different from what I needed, anyway?\n\n> > Note: as the original code did not leave any space in the bit mask\n> > before the WSEH_* bits, the diff of this commit looks unnecessarily\n> > involved: the diff is dominated by making room for one more bit to be\n> > used by the whitespace rules.\n> \n> It took me some minutes, but I am reasonably convinced this patch\n> is correct (and doesn't collide with other series in flight, sb/diff-color-more\n> adds another flag to move detection in another bit field at (1<<23))\n> \n> Thanks for writing this patch instead of the other, though I'll leave\n> it to Junio to weigh in if this approach is the best design.\n\nI am sorry that your time was wasted in addition to mine: I will go with a\nsimple one-line patch in v5 instead, a single line that simply disables\nwhite-space errors altogether in dual color mode.\n\nCiao,\nDscho\n"},{"id":"355155","messageId":"nycvar.QRO.7.76.6.1808102306080.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180729212354.GB9955@hank.intra.tgummerer.com","subject":"Re: [PATCH v4 17/21] range-diff: populate the man page","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-08-10T21:06:37Z","receivedAt":"2018-08-10T21:06:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Thomas,\n\nOn Sun, 29 Jul 2018, Thomas Gummerer wrote:\n\n> On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> > From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > \n> >  Documentation/git-range-diff.txt | 229 +++++++++++++++++++++++++++++++\n> >  1 file changed, 229 insertions(+)\n> > \n> > [...]\n> >\n> > +CONFIGURATION\n> > +-------------\n> > +This command uses the `diff.color.*` and `pager.range-diff` settings\n> > +(the latter is on by default).\n> > +See linkgit:git-config[1].\n> \n> Would it be worth implementing a `rangeDiff.dualColor` configuration\n> at some point?  Dual color mode seems like something I would like to\n> have on by default, even if we are not making it the default for the\n> command itself.\n> \n> (Again this is something that can be a future enhancement).\n\nSure, go wild!\n\nCiao,\nDscho\n"},{"id":"355156","messageId":"nycvar.QRO.7.76.6.1808102307010.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180729213325.GC9955@hank.intra.tgummerer.com","subject":"Re: [PATCH v4 20/21] range-diff: make --dual-color the default mode","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-08-10T21:07:19Z","receivedAt":"2018-08-10T21:07:21Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Thomas,\n\nOn Sun, 29 Jul 2018, Thomas Gummerer wrote:\n\n> On 07/21, Johannes Schindelin via GitGitGadget wrote:\n> > From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > \n> > After using this command extensively for the last two months, this\n> > developer came to the conclusion that even if the dual color mode still\n> > leaves a lot of room for confusion about what was actually changed, the\n> > non-dual color mode is substantially worse in that regard.\n> > \n> > Therefore, we really want to make the dual color mode the default.\n> \n> Ah and here we're making it default, so I wouldn't need a\n> `rangeDiff.dualColor` config variable anymore.  Even better!\n\n... except if you want to switch it off ;-)\n\nCiao,\nDscho\n"},{"id":"355157","messageId":"nycvar.QRO.7.76.6.1808102308140.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAPig+cSeAUWFCBEbk0m7_gmATAaVDg-fi42kq49DuGm3g0L4=Q@mail.gmail.com","subject":"Re: [PATCH v4 05/21] range-diff: also show the diff between patches","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-08-10T21:12:03Z","receivedAt":"2018-08-10T21:12:08Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Mon, 30 Jul 2018, Eric Sunshine wrote:\n\n> On Mon, Jul 30, 2018 at 5:26 PM Thomas Gummerer <t.gummerer@gmail.com> wrote:\n> > On 07/30, Johannes Schindelin wrote:\n> > > On Sun, 29 Jul 2018, Thomas Gummerer wrote:\n> > > > There's one more thing that I noticed here:\n> > > >\n> > > >     git range-diff --no-patches\n> > > >     fatal: single arg format requires a symmetric range\n> > > >\n> > > I immediately thought of testing for a leading `-` of the remaining\n> > > argument, but I could imagine that somebody enterprisey uses\n> > >\n> > >       git range-diff -- -my-first-attempt...-my-second-attempt\n> > >\n> > > and I do not really want to complexify the code... Ideas?\n> >\n> > Good point.  I can't really come up with a good option right now\n> > either.  It's not too bad, as users just typed the command, so it\n> > should be easy enough to see from the previous line what went wrong.\n> \n> I think you can attain the desired behavior by making a final\n> parse_options() call with empty 'options' list after the call to\n> diff_setup_done(). It's pretty much a one-line fix, but can probably\n> be done as an incremental change rather than rerolling.\n\nBut then we would have to keep `--` in the first, and not in the second\nparse_options() call, right? We would also have to handle that `--`\nproperly in the loop that calls diff_opt_parse(), I think.\n\nA bit more involved than just a one-line fix, but I guess I'll give it a\ntry.\n\nCiao,\nDscho\n"},{"id":"355159","messageId":"nycvar.QRO.7.76.6.1808102313070.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAGZ79kbj2sgKOmouvLDuXic3vq9RG1LZ_retOqMwX_YZtMP+1Q@mail.gmail.com","subject":"Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-08-10T21:18:59Z","receivedAt":"2018-08-10T21:19:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Wed, 8 Aug 2018, Stefan Beller wrote:\n\n> On Wed, Aug 8, 2018 at 6:05 AM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> \n> > > [...]\n> > > >       diff --git a/Makefile b/Makefile\n> > > >       --- a/Makefile\n> > > >       +++ b/Makefile\n> > >\n> > > The line starting with --- is red (default removed color) and the line\n> > > with +++ is green (default add color).\n> > >\n> > > Ideally these two lines and the third line above starting with \"diff --git\"\n> > > would render in GIT_COLOR_BOLD (\"METAINFO\").\n> >\n> > I agree that is not the best coloring here, but as you remarked elsewhere,\n> > it would require content-aware dual coloring, and I am loathe to try to\n> > implement that for two reasons: 1) it would take most likely a long time\n> > to design and implement that, and 2) I don't have that time.\n> >\n> > So I would like to declare that good enough is good enough in this case.\n> \n> I anticipated this answer, so I wrote some patches myself, starting at\n> https://public-inbox.org/git/20180804015317.182683-1-sbeller@google.com/\n> specifically\n> https://public-inbox.org/git/20180804015317.182683-5-sbeller@google.com/\n> \n> I plan on resending these on top of your resend (if any) at a later convenient\n> time for both you and Junio, as noted in\n> https://public-inbox.org/git/CAGZ79kZnVEsvpicNu7LXkRcHuRqGvESfvG3DL5O_2kPVYrW-Gg@mail.gmail.com/\n\nThank you!\n\n> > > >   3:  076e1192d !  3:  4e3fb47a1 range-diff: first rudimentary implementation\n> > > >      @@ -4,7 +4,7 @@\n> > > >\n> > > >           At this stage, `git range-diff` can determine corresponding commits\n> > > >           of two related commit ranges. This makes use of the recently introduced\n> > > >      -    implementation of the Hungarian algorithm.\n> > > >      +    implementation of the linear assignment algorithm.\n> > > >\n> > > >           The core of this patch is a straight port of the ideas of tbdiff, the\n> > > >           apparently dormant project at https://github.com/trast/tbdiff.\n> > > >      @@ -51,19 +51,17 @@\n> > > >       + int res = 0;\n> > > >       + struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n> > > >\n> > > >      -- argc = parse_options(argc, argv, NULL, options,\n> > > >      --                      builtin_range_diff_usage, 0);\n> > > >      -+ argc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,\n> > > >      -+                      0);\n> > > >      +  argc = parse_options(argc, argv, NULL, options,\n> > > >      +                       builtin_range_diff_usage, 0);\n> > >\n> > > This is really nice in colors when viewed locally.\n> > >\n> > > >  16:  dfa7b1e71 <  -:  --------- range-diff --dual-color: work around bogus white-space warning\n> > > >   -:  --------- > 16:  f4252f2b2 range-diff --dual-color: fix bogus white-space warning\n> > >\n> > > Ah; here my initial assumption of only reviewing the range-diff breaks down now.\n> > > I'll dig into patch 16 separately.\n> >\n> > Right. This was an almost complete rewrite, and then next iteration will\n> > hopefully bring another complete rewrite: disabling whitespace warnings in\n> > dual color mode.\n> >\n> > > Maybe it is worth having an option to expand all \"new\" patches.\n> >\n> > Sure.\n> >\n> > And I also have a use case for --left-only/--right-only.\n> >\n> > And I also have a strong use case (and so does Junio, it seems, or for\n> > that matter, anybody contributing to Git due to Junio's insistence on\n> > signing off on each patch, rather than on the merge commit) for something\n> > like --ignore-lines=<regex>.\n> >\n> > And you probably guess what I will say next: these features will make for\n> > really fantastic patch series *on top* of mine. There really is no good\n> > reason to delay the current patch series just to cram more features into\n> > it that had not been planned in the first place.\n> \n> Yes, I agree.\n\nI am happy to hear that.\n\n> I am unsure about the current state of your series, though;\n> \n> Junio thinks (expects?) a resend, whereas you seem to call it good enough\n> but also said (some time back) that you want to resend due to Thomas\n> feedback.\n\nSorry about being so unclear. When time gets scarce, sometimes I get too\nstressed (and too short on time) to communicate properly.\n\nYes, I want to limit the new features put into this patch series, in the\ninterest of getting things into `next` (and maybe still into `master`\nbefore v2.19, but I am not allowing myself to hope for that too much).\n\nAnd yes, I want to send another iteration, as there have been too many\nchanges that I do not want to ask Junio to touch up, in particular because\nI am a little bit of a detail-oriented person and want my fixes just so.\n\n> I do have 2 series on top of the current range-diff.\n> * The first (queued by Junio as origin/sb/range-diff-colors)\n>    adds a basic test for colors and improves diff.c readability\n> * The second (linked above) changes colors for some lines.\n> \n> I do not want to build more on top as long as I do not know if\n> you resend (and how much it'll change)\n\nIt will not change much. The biggest change is that the white-space\nwarning thing is done completely differently, so that I do not even have\nto be in ws.c's author list.\n\nI'll just try to get that option parsing change in that Eric suggested,\nforce-push, then wait for macOS and Linux builds to pass (trusting that\nWindows will follow suite) and hit /submit.\n\nCiao,\nDscho\n"},{"id":"355162","messageId":"xmqqlg9d7vsy.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1808102313070.71@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-08-10T21:31:41Z","receivedAt":"2018-08-10T21:31:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> I'll just try to get that option parsing change in that Eric suggested,\n> force-push, then wait for macOS and Linux builds to pass (trusting that\n> Windows will follow suite) and hit /submit.\n\nOK.  Obviously receiving, applying and inspecting that result will\nnot be done in time for today's integration cycle, but having a\nversion that limits its cope and is suitable for 'next' is a good\nthing to have at this point during the cycle.  Correct whitespace\nerror colouring, etc., can be done on top after the basics settle,\nand the basics were good in a few versions ago already IIRC.\n\n"},{"id":"355163","messageId":"CAPig+cQ9DcFPosPcjo6MbF_sF9DXuZQ_gZe5jxyx0vbH932sdA@mail.gmail.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1808102308140.71@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v4 05/21] range-diff: also show the diff between patches","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-10T21:31:37Z","receivedAt":"2018-08-10T21:31:50Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Aug 10, 2018 at 5:12 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> On Mon, 30 Jul 2018, Eric Sunshine wrote:\n> > I think you can attain the desired behavior by making a final\n> > parse_options() call with empty 'options' list after the call to\n> > diff_setup_done(). It's pretty much a one-line fix, but can probably\n> > be done as an incremental change rather than rerolling.\n>\n> But then we would have to keep `--` in the first, and not in the second\n> parse_options() call, right? We would also have to handle that `--`\n> properly in the loop that calls diff_opt_parse(), I think.\n> A bit more involved than just a one-line fix, but I guess I'll give it a\n> try.\n\nIt's something that could easily wait until after this series lands.\nAfter all, it's just a slightly confusing error message, not some\nfundamental problem.\n\nAs for '--', I'll have to go back and look at the code. I thought I\nhad thought it all through at the time I made the suggestion, but my\nbrain needs a refresh by now.\n"},{"id":"355168","messageId":"nycvar.QRO.7.76.6.1808110000260.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"xmqqlg9d7vsy.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-08-10T22:00:58Z","receivedAt":"2018-08-10T22:01:05Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Fri, 10 Aug 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > I'll just try to get that option parsing change in that Eric suggested,\n> > force-push, then wait for macOS and Linux builds to pass (trusting that\n> > Windows will follow suite) and hit /submit.\n> \n> OK.  Obviously receiving, applying and inspecting that result will\n> not be done in time for today's integration cycle, but having a\n> version that limits its cope and is suitable for 'next' is a good\n> thing to have at this point during the cycle.  Correct whitespace\n> error colouring, etc., can be done on top after the basics settle,\n> and the basics were good in a few versions ago already IIRC.\n\nNo, a couple of issues were identified, still, that merited several dozen\nrebases on my side.\n\nCiao,\nDscho\n"},{"id":"355169","messageId":"nycvar.QRO.7.76.6.1808110001150.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"CAPig+cQ9DcFPosPcjo6MbF_sF9DXuZQ_gZe5jxyx0vbH932sdA@mail.gmail.com","subject":"Re: [PATCH v4 05/21] range-diff: also show the diff between patches","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-08-10T22:02:49Z","receivedAt":"2018-08-10T22:02:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Fri, 10 Aug 2018, Eric Sunshine wrote:\n\n> On Fri, Aug 10, 2018 at 5:12 PM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> > On Mon, 30 Jul 2018, Eric Sunshine wrote:\n> > > I think you can attain the desired behavior by making a final\n> > > parse_options() call with empty 'options' list after the call to\n> > > diff_setup_done(). It's pretty much a one-line fix, but can probably\n> > > be done as an incremental change rather than rerolling.\n> >\n> > But then we would have to keep `--` in the first, and not in the second\n> > parse_options() call, right? We would also have to handle that `--`\n> > properly in the loop that calls diff_opt_parse(), I think.\n> > A bit more involved than just a one-line fix, but I guess I'll give it a\n> > try.\n> \n> It's something that could easily wait until after this series lands.\n> After all, it's just a slightly confusing error message, not some\n> fundamental problem.\n> \n> As for '--', I'll have to go back and look at the code. I thought I\n> had thought it all through at the time I made the suggestion, but my\n> brain needs a refresh by now.\n\nYour suggestion might have started out by trying to fix the error message,\nbut after staring at the code for a couple of minutes, I think the issue\nwould have left all kinds of opportunities for bad user experience.\n\nSo I fixed it ;-)\n\nCiao,\nDscho\n"},{"id":"355171","messageId":"pull.1.v5.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v5 00/21] Add range-diff, a tbdiff lookalike","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:25Z","receivedAt":"2018-08-10T22:14:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The incredibly useful git-tbdiff [https://github.com/trast/tbdiff] tool to\ncompare patch series (say, to see what changed between two iterations sent\nto the Git mailing list) is slightly less useful for this developer due to\nthe fact that it requires the hungarian and numpy Python packages which are\nfor some reason really hard to build in MSYS2. So hard that I even had to\ngive up, because it was simply easier to re-implement the whole shebang as a\nbuiltin command.\n\nThe project at https://github.com/trast/tbdiff seems to be dormant, anyway.\nFunny (and true) story: I looked at the open Pull Requests to see how active\nthat project is, only to find to my surprise that I had submitted one in\nAugust 2015, and that it was still unanswered let alone merged.\n\nWhile at it, I forward-ported AEvar's patch to force --decorate=no because \ngit -p tbdiff would fail otherwise.\n\nSide note: I work on implementing range-diff not only to make life easier\nfor reviewers who have to suffer through v2, v3, ... of my patch series, but\nalso to verify my changes before submitting a new iteration. And also, maybe\neven more importantly, I plan to use it to verify my merging-rebases of Git\nfor Windows (for which I previously used to redirect the\npre-rebase/post-rebase diffs vs upstream and then compare them using git\ndiff --no-index). And of course any interested person can see what changes\nwere necessary e.g. in the merging-rebase of Git for Windows onto v2.17.0 by\nrunning a command like:\n\n        base=^{/Start.the.merging-rebase}\n        tag=v2.17.0.windows.1\n        pre=$tag$base^2\n        git range-diff $pre$base..$pre $tag$base..$tag\n\nThe command uses what it calls the \"dual color mode\" (can be disabled via \n--no-dual-color) which helps identifying what actually changed: it prefixes\nlines with a - (and red background) that correspond to the first commit\nrange, and with a + (and green background) that correspond to the second\nrange. The rest of the lines will be colored according to the original\ndiffs.\n\nChanges since v4:\n\n * Fixed a typo in the commit message of \"range-diff: add tests\" that was\n   introduced in v4.\n * White-space fixes.\n * Fixed the length of the first header underline in the man page.\n * Changed the preprocessor guard in linear-assignment.h to reflect the new\n   name (instead of the old name, which was hungarian.h).\n * Likewise, changed the preprocessor guards in range-diff.h to hide the\n   history of the thrice-renamed command.\n * Fixed indentation in the completion.\n * Instead of trying to paper over white-space error handling that does not\n   apply to \"diffs of diffs\", dual color mode now simply disables all\n   white-space warnings.\n * When showing the \"single arg must be symmetric range\" error message, git\n   range-diff now also shows the usage.\n * Adjusted the commit message of \"range-diff: adjust the output of the\n   commit pairs\" to avoid the surprise of the reviewer when onelines are\n   printed all of a sudden, too.\n * \"range-diff: adjust the output of the commit pairs\" is now using a\n   simpler way to print onelines.\n * We are now sandwiching the diff_opt_parse() loop between two \n   parse_options(), to make sure that we caught all options, and that the -- \n   separator is handled.\n * Adjusted the lookup_commit_reference() call to the newest master (it now\n   takes a the_repository parameter).\n\nChanges since v3:\n\n * The cover letter was adjusted to reflect the new reality (the command is\n   called range-diff now, not branch-diff, and --dual-color is the default).\n * The documentation was adjusted a bit more in the patch that makes \n   --dual-color the default.\n * Clarified the calculation of the cost matrix, as per Stefan Beller's\n   request.\n * The man page now spells out that merge commits are ignored in the commit\n   ranges (not merges per se).\n * The code in linear-assignment.c was adjusted to use the SWAP() macro.\n * The commit message of the patch introducing the first rudimentary\n   implementation no longer talks about the \"Hungarian\" algorithm, but about\n   the \"linear assignment algorithm\" instead.\n * A bogus indentation change was backed out from the patch introducing the\n   first rudimentary implementation.\n * Instead of merely warning about missing .. in the 2-parameter invocation,\n   we now exit with the error message.\n * The diff_opt_parse() function is allowed to return a value larger than 1,\n   indicating that more than just one command-line parameter was parsed. We\n   now advance by the indicated value instead of always advancing exactly 1\n   (which is still correct much of the time).\n * A lengthy if...else if...else if...else was simplified (from a logical\n   point of view) by reordering it.\n * The unnecessarily static variable dashes was turned into a local variable\n   of the caller.\n * The commit message talking about the new man page still referred to git\n   branch --diff, which has been fixed.\n * A forgotten t7910 reference was changed to t3206.\n * An unbalanced double-tick was fixed in the man page.\n * Fixed grammar both of the commit message and the description of the \n   --no-dual-color option.\n * To fix the build, a blank man page is now introduced together with the\n   new range-diff command, even if it is populated for real only at a later\n   patch (i.e. at the same time as before).\n * The headaches Junio fears would be incurred by that simple workaround to\n   avoid bogus white-space error reporting are fended off: a more complex\n   patch is now in place that adds (and uses) a new white-space flag. Sadly,\n   as is all too common when Junio \"encourages\" me to replace a simple\n   workaround by something \"proper\", it caused all kinds of headaches to get\n   this right, so I am rather less certain that the \"proper\" fix will cause\n   us less headaches than the simple workaround would have done. But\n   whatever.\n * The dual color mode now also dims the changes that are exclusively in the\n   first specified commit range, and uses bold face on the changes\n   exclusively in the second one. This matches the intuition when using \n   range-diff to compare an older iteration of a patch series to a newer\n   one: the changes from the previous iteration that were replaced by new\n   ones \"fade\", while the changes that replace them are \"shiny new\".\n\nChanges since v2:\n\n * Right-aligned the patch numbers in the commit pairs.\n * Used ALLOC_ARRAY() in hungarian.c instead of xmalloc(sizeof()*size).\n * Changed compute_assignment()s return type from int to void, as it always\n   succeeds.\n * Changed the Hungarian Algorithm to use an integer cost matrix.\n * Changed the --creation-weight option to --creation-factor where is an\n   integer.\n * Retitled 1/19 and 2/19 to better conform with the current conventions, as\n   pointed out (and suggested) by Junio.\n * Shut up Coverity, and at the same time avoided passing the unnecessary i \n   and j parameters to output_pair_header().\n * Removed support for the --no-patches option: we inherit diff_options'\n   support for -s already (and much more).\n * Removed the ugly _INV enum values, and introduced a beautiful\n   GIT_COLOR_REVERSE instead. This way, whatever the user configured as\n   color.diff.new (or .old) will be used in reverse in the dual color mode.\n * Instead of overriding the fragment header color, the dual color mode will\n   now reverse the \"outer\" fragment headers, too.\n * Turned the stand-alone branch-diff command into the --diff option of git\n   branch. Adjusted pretty much all commit messages to account for this.\n   This change should no longer be visible: see below.\n * Pretty much re-wrote the completion, to support the new --diff mode of\n   git-branch. See below: it was reverted for range-diff.\n * Renamed t7910 to t3206, to be closer to the git-branch tests.\n * Ensured that git_diff_ui_config() gets called, and therefore color.diff.*\n   respected.\n * Avoided leaking four_spaces.\n * Fixed a declaration in a for (;;) statement (which Junio had as a fixup!\n   that I almost missed).\n * Renamed branch --diff, which had been renamed from branch-diff (which was\n   picked to avoid re-using tbdiff) to range-diff.\n * Renamed hungarian.c and its header to linear-assignment.c\n * Made --dual-color the default, and changed it to still auto-detect\n   whether color should be used rather than forcing it\n\nJohannes Schindelin (20):\n  linear-assignment: a function to solve least-cost assignment problems\n  Introduce `range-diff` to compare iterations of a topic branch\n  range-diff: first rudimentary implementation\n  range-diff: improve the order of the shown commits\n  range-diff: also show the diff between patches\n  range-diff: right-trim commit messages\n  range-diff: indent the diffs just like tbdiff\n  range-diff: suppress the diff headers\n  range-diff: adjust the output of the commit pairs\n  range-diff: do not show \"function names\" in hunk headers\n  range-diff: use color for the commit pairs\n  color: add the meta color GIT_COLOR_REVERSE\n  diff: add an internal option to dual-color diffs of diffs\n  range-diff: offer to dual-color the diffs\n  range-diff --dual-color: skip white-space warnings\n  range-diff: populate the man page\n  completion: support `git range-diff`\n  range-diff: left-pad patch numbers\n  range-diff: make --dual-color the default mode\n  range-diff: use dim/bold cues to improve dual color mode\n\nThomas Rast (1):\n  range-diff: add tests\n\n .gitignore                             |   1 +\n Documentation/config.txt               |   6 +-\n Documentation/git-range-diff.txt       | 252 +++++++++++\n Makefile                               |   3 +\n builtin.h                              |   1 +\n builtin/range-diff.c                   | 114 +++++\n color.h                                |   7 +\n command-list.txt                       |   1 +\n contrib/completion/git-completion.bash |  14 +\n diff.c                                 | 105 ++++-\n diff.h                                 |  10 +-\n git.c                                  |   1 +\n linear-assignment.c                    | 201 ++++++++\n linear-assignment.h                    |  22 +\n range-diff.c                           | 435 ++++++++++++++++++\n range-diff.h                           |   9 +\n t/.gitattributes                       |   1 +\n t/t3206-range-diff.sh                  | 145 ++++++\n t/t3206/history.export                 | 604 +++++++++++++++++++++++++\n 19 files changed, 1913 insertions(+), 19 deletions(-)\n create mode 100644 Documentation/git-range-diff.txt\n create mode 100644 builtin/range-diff.c\n create mode 100644 linear-assignment.c\n create mode 100644 linear-assignment.h\n create mode 100644 range-diff.c\n create mode 100644 range-diff.h\n create mode 100755 t/t3206-range-diff.sh\n create mode 100644 t/t3206/history.export\n\n\nbase-commit: 1d89318c48d233d52f1db230cf622935ac3c69fa\nPublished-As: https://github.com/gitgitgadget/git/releases/tags/pr-1%2Fdscho%2Fbranch-diff-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1/dscho/branch-diff-v5\nPull-Request: https://github.com/gitgitgadget/git/pull/1\n\nRange-diff vs v4:\n\n  1:  f7e70689e !  1:  f168da3a3 linear-assignment: a function to solve least-cost assignment problems\n     @@ -239,8 +239,8 @@\n      --- /dev/null\n      +++ b/linear-assignment.h\n      @@\n     -+#ifndef HUNGARIAN_H\n     -+#define HUNGARIAN_H\n     ++#ifndef LINEAR_ASSIGNMENT_H\n     ++#define LINEAR_ASSIGNMENT_H\n      +\n      +/*\n      + * Compute an assignment of columns -> rows (and vice versa) such that every\n  2:  88134121d !  2:  33758f361 Introduce `range-diff` to compare iterations of a topic branch\n     @@ -34,7 +34,7 @@\n      +++ b/Documentation/git-range-diff.txt\n      @@\n      +git-range-diff(1)\n     -+==================\n     ++=================\n      +\n      +NAME\n      +----\n  3:  4e3fb47a1 !  3:  08b8c3fc4 range-diff: first rudimentary implementation\n     @@ -70,8 +70,10 @@\n      +\t\tconst char *b = strstr(argv[0], \"...\"), *a = argv[0];\n      +\t\tint a_len;\n      +\n     -+\t\tif (!b)\n     -+\t\t\tdie(_(\"single arg format requires a symmetric range\"));\n     ++\t\tif (!b) {\n     ++\t\t\terror(_(\"single arg format must be symmetric range\"));\n     ++\t\t\tusage_with_options(builtin_range_diff_usage, options);\n     ++\t\t}\n      +\n      +\t\ta_len = (int)(b - a);\n      +\t\tif (!a_len) {\n     @@ -418,8 +420,8 @@\n      --- /dev/null\n      +++ b/range-diff.h\n      @@\n     -+#ifndef BRANCH_DIFF_H\n     -+#define BRANCH_DIFF_H\n     ++#ifndef RANGE_DIFF_H\n     ++#define RANGE_DIFF_H\n      +\n      +int show_range_diff(const char *range1, const char *range2,\n      +\t\t    int creation_factor);\n  4:  47bee09b0 =  4:  7b9091968 range-diff: improve the order of the shown commits\n  5:  94afaeaf2 !  5:  9e1e66007 range-diff: also show the diff between patches\n     @@ -10,7 +10,7 @@\n          diff which is the result of first reverting the old diff and then\n          applying the new diff.\n      \n     -    Especially when rebasing often, an interdiff is often not feasible,\n     +    Especially when rebasing frequently, an interdiff is often not feasible,\n          though: if the old diff cannot be applied in reverse (due to a moving\n          upstream), an interdiff can simply not be inferred.\n      \n     @@ -25,9 +25,17 @@\n          This is left for a later commit.\n      \n          Note also: while tbdiff accepts the `--no-patches` option to suppress\n     -    these diffs between patches, we prefer the `-s` option that is\n     -    automatically supported via our use of diff_opt_parse().\n     +    these diffs between patches, we prefer the `-s` (or `--no-patch`) option\n     +    that is automatically supported via our use of diff_opt_parse().\n      \n     +    And finally note: to support diff options, we have to call\n     +    `parse_options()` such that it keeps unknown options, and then loop over\n     +    those and let `diff_opt_parse()` handle them. After that loop, we have\n     +    to call `parse_options()` again, to make sure that no unknown options\n     +    are left.\n     +\n     +    Helped-by: Thomas Gummerer <t.gummerer@gmail.com>\n     +    Helped-by: Eric Sunshine <sunshine@sunshineco.com>\n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n      diff --git a/builtin/range-diff.c b/builtin/range-diff.c\n     @@ -61,10 +69,10 @@\n      +\tdiffopt.output_format = DIFF_FORMAT_PATCH;\n      +\n       \targc = parse_options(argc, argv, NULL, options,\n     --\t\t\t     builtin_range_diff_usage, 0);\n     -+\t\t\t     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n     ++\t\t\t     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN |\n     ++\t\t\t     PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);\n      +\n     -+\tfor (i = j = 0; i < argc; ) {\n     ++\tfor (i = j = 1; i < argc && strcmp(\"--\", argv[i]); ) {\n      +\t\tint c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);\n      +\n      +\t\tif (!c)\n     @@ -74,9 +82,13 @@\n      +\t}\n      +\targc = j;\n      +\tdiff_setup_done(&diffopt);\n     ++\n     ++\t/* Make sure that there are no unparsed options */\n     ++\targc = parse_options(argc, argv, NULL,\n     ++\t\t\t     options + ARRAY_SIZE(options) - 1, /* OPT_END */\n     + \t\t\t     builtin_range_diff_usage, 0);\n       \n       \tif (argc == 2) {\n     - \t\tif (!strstr(argv[0], \"..\"))\n      @@\n       \t\tusage_with_options(builtin_range_diff_usage, options);\n       \t}\n     @@ -165,8 +177,8 @@\n      --- a/range-diff.h\n      +++ b/range-diff.h\n      @@\n     - #ifndef BRANCH_DIFF_H\n     - #define BRANCH_DIFF_H\n     + #ifndef RANGE_DIFF_H\n     + #define RANGE_DIFF_H\n       \n      +#include \"diff.h\"\n      +\n  6:  41ab875a3 =  6:  167ca02a3 range-diff: right-trim commit messages\n  7:  a3dd99509 !  7:  ca8de8c75 range-diff: indent the diffs just like tbdiff\n     @@ -39,7 +39,7 @@\n      +\tdiffopt.output_prefix_data = &four_spaces;\n       \n       \targc = parse_options(argc, argv, NULL, options,\n     - \t\t\t     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);\n     + \t\t\t     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN |\n      @@\n       \n       \tstrbuf_release(&range1);\n  8:  61b2ff2f7 =  8:  eb94d1982 range-diff: suppress the diff headers\n  9:  9641ab5c0 !  9:  6330afad9 range-diff: adjust the output of the commit pairs\n     @@ -2,6 +2,10 @@\n      \n          range-diff: adjust the output of the commit pairs\n      \n     +    This not only uses \"dashed stand-ins\" for \"pairs\" where one side is\n     +    missing (i.e. unmatched commits that are present only in one of the two\n     +    commit ranges), but also adds onelines for the reader's pleasure.\n     +\n          This change brings `git range-diff` yet another step closer to\n          feature parity with tbdiff: it now shows the oneline, too, and indicates\n          with `=` when the commits have identical diffs.\n     @@ -61,15 +65,10 @@\n      +\t\tstrbuf_addf(buf, \" %d:  %s\", b_util->i + 1,\n      +\t\t\t    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));\n      +\n     -+\tcommit = lookup_commit_reference(oid);\n     ++\tcommit = lookup_commit_reference(the_repository, oid);\n      +\tif (commit) {\n     -+\t\tconst char *commit_buffer = get_commit_buffer(commit, NULL);\n     -+\t\tconst char *subject;\n     -+\n     -+\t\tfind_commit_subject(commit_buffer, &subject);\n      +\t\tstrbuf_addch(buf, ' ');\n     -+\t\tformat_subject(buf, subject, \" \");\n     -+\t\tunuse_commit_buffer(commit, commit_buffer);\n     ++\t\tpp_commit_easy(CMIT_FMT_ONELINE, commit, buf);\n      +\t}\n      +\tstrbuf_addch(buf, '\\n');\n      +\n 10:  0a52f8878 = 10:  c296675eb range-diff: do not show \"function names\" in hunk headers\n 11:  2b8d09020 ! 11:  85e0ab82f range-diff: add tests\n     @@ -3,7 +3,7 @@\n          range-diff: add tests\n      \n          These are essentially lifted from https://github.com/trast/tbdiff, with\n     -    light touch-ups to account for the command now being names `git\n     +    light touch-ups to account for the command now being named `git\n          range-diff`.\n      \n          Apart from renaming `tbdiff` to `range-diff`, only one test case needed\n 12:  fb83ce71a ! 12:  f48b62644 range-diff: use color for the commit pairs\n     @@ -79,16 +79,14 @@\n       \tif (!b_util)\n       \t\tstrbuf_addf(buf, \" -:  %s\", dashes->buf);\n      @@\n     - \t\tconst char *commit_buffer = get_commit_buffer(commit, NULL);\n     - \t\tconst char *subject;\n       \n     + \tcommit = lookup_commit_reference(the_repository, oid);\n     + \tif (commit) {\n      +\t\tif (status == '!')\n      +\t\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color);\n      +\n     - \t\tfind_commit_subject(commit_buffer, &subject);\n       \t\tstrbuf_addch(buf, ' ');\n     - \t\tformat_subject(buf, subject, \" \");\n     - \t\tunuse_commit_buffer(commit, commit_buffer);\n     + \t\tpp_commit_easy(CMIT_FMT_ONELINE, commit, buf);\n       \t}\n      -\tstrbuf_addch(buf, '\\n');\n      +\tstrbuf_addf(buf, \"%s\\n\", color_reset);\n 13:  9ccb9516a = 13:  1ad74f939 color: add the meta color GIT_COLOR_REVERSE\n 14:  9de5bd229 = 14:  39a0ecd28 diff: add an internal option to dual-color diffs of diffs\n 15:  21b2f9e4b ! 15:  c32a24f6a range-diff: offer to dual-color the diffs\n     @@ -30,8 +30,8 @@\n       \t};\n       \tint i, j, res = 0;\n      @@\n     - \targc = j;\n     - \tdiff_setup_done(&diffopt);\n     + \t\t\t     options + ARRAY_SIZE(options) - 1, /* OPT_END */\n     + \t\t\t     builtin_range_diff_usage, 0);\n       \n      +\tif (dual_color) {\n      +\t\tdiffopt.use_color = 1;\n 16:  f4252f2b2 <  -:  --------- range-diff --dual-color: fix bogus white-space warning\n  -:  --------- > 16:  05947781f range-diff --dual-color: skip white-space warnings\n 17:  9e09c6be6 = 17:  3147c4440 range-diff: populate the man page\n 18:  9b3632324 ! 18:  b08e6d937 completion: support `git range-diff`\n     @@ -18,16 +18,16 @@\n       \n      +_git_range_diff ()\n      +{\n     -+  case \"$cur\" in\n     -+  --*)\n     -+          __gitcomp \"\n     -+\t  \t--creation-factor= --dual-color\n     -+                  $__git_diff_common_options\n     -+                  \"\n     -+          return\n     -+          ;;\n     -+  esac\n     -+  __git_complete_revlist\n     ++\tcase \"$cur\" in\n     ++\t--*)\n     ++\t\t__gitcomp \"\n     ++\t\t\t--creation-factor= --dual-color\n     ++\t\t\t$__git_diff_common_options\n     ++\t\t\"\n     ++\t\treturn\n     ++\t\t;;\n     ++\tesac\n     ++\t__git_complete_revlist\n      +}\n      +\n       _git_rebase ()\n 19:  07ec215e8 ! 19:  19406283e range-diff: left-pad patch numbers\n     @@ -48,7 +48,7 @@\n      +\t\tstrbuf_addf(buf, \" %*d:  %s\", patch_no_width, b_util->i + 1,\n       \t\t\t    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));\n       \n     - \tcommit = lookup_commit_reference(oid);\n     + \tcommit = lookup_commit_reference(the_repository, oid);\n      @@\n       \t\t   struct diff_options *diffopt)\n       {\n 20:  b370468e7 ! 20:  6b3552386 range-diff: make --dual-color the default mode\n     @@ -87,8 +87,8 @@\n       \t\tOPT_END()\n       \t};\n      @@\n     - \targc = j;\n     - \tdiff_setup_done(&diffopt);\n     + \t\t\t     options + ARRAY_SIZE(options) - 1, /* OPT_END */\n     + \t\t\t     builtin_range_diff_usage, 0);\n       \n      -\tif (dual_color) {\n      -\t\tdiffopt.use_color = 1;\n     @@ -104,11 +104,11 @@\n      --- a/contrib/completion/git-completion.bash\n      +++ b/contrib/completion/git-completion.bash\n      @@\n     -   case \"$cur\" in\n     -   --*)\n     -           __gitcomp \"\n     --\t  \t--creation-factor= --dual-color\n     -+\t  \t--creation-factor= --no-dual-color\n     -                   $__git_diff_common_options\n     -                   \"\n     -           return\n     + \tcase \"$cur\" in\n     + \t--*)\n     + \t\t__gitcomp \"\n     +-\t\t\t--creation-factor= --dual-color\n     ++\t\t\t--creation-factor= --no-dual-color\n     + \t\t\t$__git_diff_common_options\n     + \t\t\"\n     + \t\treturn\n 21:  d8498fb32 ! 21:  ccf8c1bb2 range-diff: use dim/bold cues to improve dual color mode\n     @@ -144,7 +144,7 @@\n      +\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW_BOLD);\n      +\t\t\telse\n      +\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT_BOLD);\n     - \t\t\tflags |= WS_IGNORE_FIRST_SPACE;\n     + \t\t\tflags &= ~DIFF_SYMBOL_CONTENT_WS_MASK;\n       \t\t}\n       \t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n      @@\n\n-- \ngitgitgadget\n"},{"id":"355172","messageId":"f168da3a3c5d6ce9b7c1ea036ca184c20729153e.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 01/21] linear-assignment: a function to solve least-cost assignment problems","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:27Z","receivedAt":"2018-08-10T22:14:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe problem solved by the code introduced in this commit goes like this:\ngiven two sets of items, and a cost matrix which says how much it\n\"costs\" to assign any given item of the first set to any given item of\nthe second, assign all items (except when the sets have different size)\nin the cheapest way.\n\nWe use the Jonker-Volgenant algorithm to solve the assignment problem to\nanswer questions such as: given two different versions of a topic branch\n(or iterations of a patch series), what is the best pairing of\ncommits/patches between the different versions?\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Makefile            |   1 +\n linear-assignment.c | 201 ++++++++++++++++++++++++++++++++++++++++++++\n linear-assignment.h |  22 +++++\n 3 files changed, 224 insertions(+)\n create mode 100644 linear-assignment.c\n create mode 100644 linear-assignment.h\n\ndiff --git a/Makefile b/Makefile\nindex bc4fc8eea..1af719b44 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -870,6 +870,7 @@ LIB_OBJS += gpg-interface.o\n LIB_OBJS += graph.o\n LIB_OBJS += grep.o\n LIB_OBJS += hashmap.o\n+LIB_OBJS += linear-assignment.o\n LIB_OBJS += help.o\n LIB_OBJS += hex.o\n LIB_OBJS += ident.o\ndiff --git a/linear-assignment.c b/linear-assignment.c\nnew file mode 100644\nindex 000000000..9b3e56e28\n--- /dev/null\n+++ b/linear-assignment.c\n@@ -0,0 +1,201 @@\n+/*\n+ * Based on: Jonker, R., & Volgenant, A. (1987). <i>A shortest augmenting path\n+ * algorithm for dense and sparse linear assignment problems</i>. Computing,\n+ * 38(4), 325-340.\n+ */\n+#include \"cache.h\"\n+#include \"linear-assignment.h\"\n+\n+#define COST(column, row) cost[(column) + column_count * (row)]\n+\n+/*\n+ * The parameter `cost` is the cost matrix: the cost to assign column j to row\n+ * i is `cost[j + column_count * i].\n+ */\n+void compute_assignment(int column_count, int row_count, int *cost,\n+\t\t\tint *column2row, int *row2column)\n+{\n+\tint *v, *d;\n+\tint *free_row, free_count = 0, saved_free_count, *pred, *col;\n+\tint i, j, phase;\n+\n+\tmemset(column2row, -1, sizeof(int) * column_count);\n+\tmemset(row2column, -1, sizeof(int) * row_count);\n+\tALLOC_ARRAY(v, column_count);\n+\n+\t/* column reduction */\n+\tfor (j = column_count - 1; j >= 0; j--) {\n+\t\tint i1 = 0;\n+\n+\t\tfor (i = 1; i < row_count; i++)\n+\t\t\tif (COST(j, i1) > COST(j, i))\n+\t\t\t\ti1 = i;\n+\t\tv[j] = COST(j, i1);\n+\t\tif (row2column[i1] == -1) {\n+\t\t\t/* row i1 unassigned */\n+\t\t\trow2column[i1] = j;\n+\t\t\tcolumn2row[j] = i1;\n+\t\t} else {\n+\t\t\tif (row2column[i1] >= 0)\n+\t\t\t\trow2column[i1] = -2 - row2column[i1];\n+\t\t\tcolumn2row[j] = -1;\n+\t\t}\n+\t}\n+\n+\t/* reduction transfer */\n+\tALLOC_ARRAY(free_row, row_count);\n+\tfor (i = 0; i < row_count; i++) {\n+\t\tint j1 = row2column[i];\n+\t\tif (j1 == -1)\n+\t\t\tfree_row[free_count++] = i;\n+\t\telse if (j1 < -1)\n+\t\t\trow2column[i] = -2 - j1;\n+\t\telse {\n+\t\t\tint min = COST(!j1, i) - v[!j1];\n+\t\t\tfor (j = 1; j < column_count; j++)\n+\t\t\t\tif (j != j1 && min > COST(j, i) - v[j])\n+\t\t\t\t\tmin = COST(j, i) - v[j];\n+\t\t\tv[j1] -= min;\n+\t\t}\n+\t}\n+\n+\tif (free_count ==\n+\t    (column_count < row_count ? row_count - column_count : 0)) {\n+\t\tfree(v);\n+\t\tfree(free_row);\n+\t\treturn;\n+\t}\n+\n+\t/* augmenting row reduction */\n+\tfor (phase = 0; phase < 2; phase++) {\n+\t\tint k = 0;\n+\n+\t\tsaved_free_count = free_count;\n+\t\tfree_count = 0;\n+\t\twhile (k < saved_free_count) {\n+\t\t\tint u1, u2;\n+\t\t\tint j1 = 0, j2, i0;\n+\n+\t\t\ti = free_row[k++];\n+\t\t\tu1 = COST(j1, i) - v[j1];\n+\t\t\tj2 = -1;\n+\t\t\tu2 = INT_MAX;\n+\t\t\tfor (j = 1; j < column_count; j++) {\n+\t\t\t\tint c = COST(j, i) - v[j];\n+\t\t\t\tif (u2 > c) {\n+\t\t\t\t\tif (u1 < c) {\n+\t\t\t\t\t\tu2 = c;\n+\t\t\t\t\t\tj2 = j;\n+\t\t\t\t\t} else {\n+\t\t\t\t\t\tu2 = u1;\n+\t\t\t\t\t\tu1 = c;\n+\t\t\t\t\t\tj2 = j1;\n+\t\t\t\t\t\tj1 = j;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tif (j2 < 0) {\n+\t\t\t\tj2 = j1;\n+\t\t\t\tu2 = u1;\n+\t\t\t}\n+\n+\t\t\ti0 = column2row[j1];\n+\t\t\tif (u1 < u2)\n+\t\t\t\tv[j1] -= u2 - u1;\n+\t\t\telse if (i0 >= 0) {\n+\t\t\t\tj1 = j2;\n+\t\t\t\ti0 = column2row[j1];\n+\t\t\t}\n+\n+\t\t\tif (i0 >= 0) {\n+\t\t\t\tif (u1 < u2)\n+\t\t\t\t\tfree_row[--k] = i0;\n+\t\t\t\telse\n+\t\t\t\t\tfree_row[free_count++] = i0;\n+\t\t\t}\n+\t\t\trow2column[i] = j1;\n+\t\t\tcolumn2row[j1] = i;\n+\t\t}\n+\t}\n+\n+\t/* augmentation */\n+\tsaved_free_count = free_count;\n+\tALLOC_ARRAY(d, column_count);\n+\tALLOC_ARRAY(pred, column_count);\n+\tALLOC_ARRAY(col, column_count);\n+\tfor (free_count = 0; free_count < saved_free_count; free_count++) {\n+\t\tint i1 = free_row[free_count], low = 0, up = 0, last, k;\n+\t\tint min, c, u1;\n+\n+\t\tfor (j = 0; j < column_count; j++) {\n+\t\t\td[j] = COST(j, i1) - v[j];\n+\t\t\tpred[j] = i1;\n+\t\t\tcol[j] = j;\n+\t\t}\n+\n+\t\tj = -1;\n+\t\tdo {\n+\t\t\tlast = low;\n+\t\t\tmin = d[col[up++]];\n+\t\t\tfor (k = up; k < column_count; k++) {\n+\t\t\t\tj = col[k];\n+\t\t\t\tc = d[j];\n+\t\t\t\tif (c <= min) {\n+\t\t\t\t\tif (c < min) {\n+\t\t\t\t\t\tup = low;\n+\t\t\t\t\t\tmin = c;\n+\t\t\t\t\t}\n+\t\t\t\t\tcol[k] = col[up];\n+\t\t\t\t\tcol[up++] = j;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tfor (k = low; k < up; k++)\n+\t\t\t\tif (column2row[col[k]] == -1)\n+\t\t\t\t\tgoto update;\n+\n+\t\t\t/* scan a row */\n+\t\t\tdo {\n+\t\t\t\tint j1 = col[low++];\n+\n+\t\t\t\ti = column2row[j1];\n+\t\t\t\tu1 = COST(j1, i) - v[j1] - min;\n+\t\t\t\tfor (k = up; k < column_count; k++) {\n+\t\t\t\t\tj = col[k];\n+\t\t\t\t\tc = COST(j, i) - v[j] - u1;\n+\t\t\t\t\tif (c < d[j]) {\n+\t\t\t\t\t\td[j] = c;\n+\t\t\t\t\t\tpred[j] = i;\n+\t\t\t\t\t\tif (c == min) {\n+\t\t\t\t\t\t\tif (column2row[j] == -1)\n+\t\t\t\t\t\t\t\tgoto update;\n+\t\t\t\t\t\t\tcol[k] = col[up];\n+\t\t\t\t\t\t\tcol[up++] = j;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t} while (low != up);\n+\t\t} while (low == up);\n+\n+update:\n+\t\t/* updating of the column pieces */\n+\t\tfor (k = 0; k < last; k++) {\n+\t\t\tint j1 = col[k];\n+\t\t\tv[j1] += d[j1] - min;\n+\t\t}\n+\n+\t\t/* augmentation */\n+\t\tdo {\n+\t\t\tif (j < 0)\n+\t\t\t\tBUG(\"negative j: %d\", j);\n+\t\t\ti = pred[j];\n+\t\t\tcolumn2row[j] = i;\n+\t\t\tSWAP(j, row2column[i]);\n+\t\t} while (i1 != i);\n+\t}\n+\n+\tfree(col);\n+\tfree(pred);\n+\tfree(d);\n+\tfree(v);\n+\tfree(free_row);\n+}\ndiff --git a/linear-assignment.h b/linear-assignment.h\nnew file mode 100644\nindex 000000000..1dfea7662\n--- /dev/null\n+++ b/linear-assignment.h\n@@ -0,0 +1,22 @@\n+#ifndef LINEAR_ASSIGNMENT_H\n+#define LINEAR_ASSIGNMENT_H\n+\n+/*\n+ * Compute an assignment of columns -> rows (and vice versa) such that every\n+ * column is assigned to at most one row (and vice versa) minimizing the\n+ * overall cost.\n+ *\n+ * The parameter `cost` is the cost matrix: the cost to assign column j to row\n+ * i is `cost[j + column_count * i].\n+ *\n+ * The arrays column2row and row2column will be populated with the respective\n+ * assignments (-1 for unassigned, which can happen only if column_count !=\n+ * row_count).\n+ */\n+void compute_assignment(int column_count, int row_count, int *cost,\n+\t\t\tint *column2row, int *row2column);\n+\n+/* The maximal cost in the cost matrix (to prevent integer overflows). */\n+#define COST_MAX (1<<16)\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"355173","messageId":"33758f361c4cbe47bca50d10d61ff36bf6da2b5a.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 02/21] Introduce `range-diff` to compare iterations of a topic branch","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:28Z","receivedAt":"2018-08-10T22:14:32Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis command does not do a whole lot so far, apart from showing a usage\nthat is oddly similar to that of `git tbdiff`. And for a good reason:\nthe next commits will turn `range-branch` into a full-blown replacement\nfor `tbdiff`.\n\nAt this point, we ignore tbdiff's color options, as they will all be\nimplemented later using diff_options.\n\nSince f318d739159 (generate-cmds.sh: export all commands to\ncommand-list.h, 2018-05-10), every new command *requires* a man page to\nbuild right away, so let's also add a blank man page, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n .gitignore                       |  1 +\n Documentation/git-range-diff.txt | 10 ++++++++++\n Makefile                         |  1 +\n builtin.h                        |  1 +\n builtin/range-diff.c             | 25 +++++++++++++++++++++++++\n command-list.txt                 |  1 +\n git.c                            |  1 +\n 7 files changed, 40 insertions(+)\n create mode 100644 Documentation/git-range-diff.txt\n create mode 100644 builtin/range-diff.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 3284a1e9b..cc0ad74b4 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -113,6 +113,7 @@\n /git-pull\n /git-push\n /git-quiltimport\n+/git-range-diff\n /git-read-tree\n /git-rebase\n /git-rebase--am\ndiff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\nnew file mode 100644\nindex 000000000..49f717db8\n--- /dev/null\n+++ b/Documentation/git-range-diff.txt\n@@ -0,0 +1,10 @@\n+git-range-diff(1)\n+=================\n+\n+NAME\n+----\n+git-range-diff - Compare two commit ranges (e.g. two versions of a branch)\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex 1af719b44..7ff7eba42 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1063,6 +1063,7 @@ BUILTIN_OBJS += builtin/prune-packed.o\n BUILTIN_OBJS += builtin/prune.o\n BUILTIN_OBJS += builtin/pull.o\n BUILTIN_OBJS += builtin/push.o\n+BUILTIN_OBJS += builtin/range-diff.o\n BUILTIN_OBJS += builtin/read-tree.o\n BUILTIN_OBJS += builtin/rebase--helper.o\n BUILTIN_OBJS += builtin/receive-pack.o\ndiff --git a/builtin.h b/builtin.h\nindex 0362f1ce2..99206df4b 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -201,6 +201,7 @@ extern int cmd_prune(int argc, const char **argv, const char *prefix);\n extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);\n extern int cmd_pull(int argc, const char **argv, const char *prefix);\n extern int cmd_push(int argc, const char **argv, const char *prefix);\n+extern int cmd_range_diff(int argc, const char **argv, const char *prefix);\n extern int cmd_read_tree(int argc, const char **argv, const char *prefix);\n extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix);\n extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nnew file mode 100644\nindex 000000000..36788ea4f\n--- /dev/null\n+++ b/builtin/range-diff.c\n@@ -0,0 +1,25 @@\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"parse-options.h\"\n+\n+static const char * const builtin_range_diff_usage[] = {\n+N_(\"git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n+N_(\"git range-diff [<options>] <old-tip>...<new-tip>\"),\n+N_(\"git range-diff [<options>] <base> <old-tip> <new-tip>\"),\n+NULL\n+};\n+\n+int cmd_range_diff(int argc, const char **argv, const char *prefix)\n+{\n+\tint creation_factor = 60;\n+\tstruct option options[] = {\n+\t\tOPT_INTEGER(0, \"creation-factor\", &creation_factor,\n+\t\t\t    N_(\"Percentage by which creation is weighted\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, options,\n+\t\t\t     builtin_range_diff_usage, 0);\n+\n+\treturn 0;\n+}\ndiff --git a/command-list.txt b/command-list.txt\nindex e1c26c1bb..a9dda3b8a 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -139,6 +139,7 @@ git-prune-packed                        plumbingmanipulators\n git-pull                                mainporcelain           remote\n git-push                                mainporcelain           remote\n git-quiltimport                         foreignscminterface\n+git-range-diff                          mainporcelain\n git-read-tree                           plumbingmanipulators\n git-rebase                              mainporcelain           history\n git-receive-pack                        synchelpers\ndiff --git a/git.c b/git.c\nindex fc7d15d54..5b48cac3a 100644\n--- a/git.c\n+++ b/git.c\n@@ -520,6 +520,7 @@ static struct cmd_struct commands[] = {\n \t{ \"prune-packed\", cmd_prune_packed, RUN_SETUP },\n \t{ \"pull\", cmd_pull, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"push\", cmd_push, RUN_SETUP },\n+\t{ \"range-diff\", cmd_range_diff, RUN_SETUP | USE_PAGER },\n \t{ \"read-tree\", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX},\n \t{ \"rebase--helper\", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"receive-pack\", cmd_receive_pack },\n-- \ngitgitgadget\n\n"},{"id":"355174","messageId":"08b8c3fc45253737ef6ca860e6cbe3ee6211d7a6.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 03/21] range-diff: first rudimentary implementation","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:30Z","receivedAt":"2018-08-10T22:14:34Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nAt this stage, `git range-diff` can determine corresponding commits\nof two related commit ranges. This makes use of the recently introduced\nimplementation of the linear assignment algorithm.\n\nThe core of this patch is a straight port of the ideas of tbdiff, the\napparently dormant project at https://github.com/trast/tbdiff.\n\nThe output does not at all match `tbdiff`'s output yet, as this patch\nreally concentrates on getting the patch matching part right.\n\nNote: due to differences in the diff algorithm (`tbdiff` uses the Python\nmodule `difflib`, Git uses its xdiff fork), the cost matrix calculated\nby `range-diff` is different (but very similar) to the one calculated\nby `tbdiff`. Therefore, it is possible that they find different matching\ncommits in corner cases (e.g. when a patch was split into two patches of\nroughly equal length).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Makefile             |   1 +\n builtin/range-diff.c |  45 ++++++-\n range-diff.c         | 311 +++++++++++++++++++++++++++++++++++++++++++\n range-diff.h         |   7 +\n 4 files changed, 363 insertions(+), 1 deletion(-)\n create mode 100644 range-diff.c\n create mode 100644 range-diff.h\n\ndiff --git a/Makefile b/Makefile\nindex 7ff7eba42..72f16882e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -925,6 +925,7 @@ LIB_OBJS += progress.o\n LIB_OBJS += prompt.o\n LIB_OBJS += protocol.o\n LIB_OBJS += quote.o\n+LIB_OBJS += range-diff.o\n LIB_OBJS += reachable.o\n LIB_OBJS += read-cache.o\n LIB_OBJS += reflog-walk.o\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 36788ea4f..94c1f362c 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -1,6 +1,7 @@\n #include \"cache.h\"\n #include \"builtin.h\"\n #include \"parse-options.h\"\n+#include \"range-diff.h\"\n \n static const char * const builtin_range_diff_usage[] = {\n N_(\"git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n@@ -17,9 +18,51 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"Percentage by which creation is weighted\")),\n \t\tOPT_END()\n \t};\n+\tint res = 0;\n+\tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n \n \targc = parse_options(argc, argv, NULL, options,\n \t\t\t     builtin_range_diff_usage, 0);\n \n-\treturn 0;\n+\tif (argc == 2) {\n+\t\tif (!strstr(argv[0], \"..\"))\n+\t\t\tdie(_(\"no .. in range: '%s'\"), argv[0]);\n+\t\tstrbuf_addstr(&range1, argv[0]);\n+\n+\t\tif (!strstr(argv[1], \"..\"))\n+\t\t\tdie(_(\"no .. in range: '%s'\"), argv[1]);\n+\t\tstrbuf_addstr(&range2, argv[1]);\n+\t} else if (argc == 3) {\n+\t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n+\t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n+\t} else if (argc == 1) {\n+\t\tconst char *b = strstr(argv[0], \"...\"), *a = argv[0];\n+\t\tint a_len;\n+\n+\t\tif (!b) {\n+\t\t\terror(_(\"single arg format must be symmetric range\"));\n+\t\t\tusage_with_options(builtin_range_diff_usage, options);\n+\t\t}\n+\n+\t\ta_len = (int)(b - a);\n+\t\tif (!a_len) {\n+\t\t\ta = \"HEAD\";\n+\t\t\ta_len = strlen(a);\n+\t\t}\n+\t\tb += 3;\n+\t\tif (!*b)\n+\t\t\tb = \"HEAD\";\n+\t\tstrbuf_addf(&range1, \"%s..%.*s\", b, a_len, a);\n+\t\tstrbuf_addf(&range2, \"%.*s..%s\", a_len, a, b);\n+\t} else {\n+\t\terror(_(\"need two commit ranges\"));\n+\t\tusage_with_options(builtin_range_diff_usage, options);\n+\t}\n+\n+\tres = show_range_diff(range1.buf, range2.buf, creation_factor);\n+\n+\tstrbuf_release(&range1);\n+\tstrbuf_release(&range2);\n+\n+\treturn res;\n }\ndiff --git a/range-diff.c b/range-diff.c\nnew file mode 100644\nindex 000000000..15d418afa\n--- /dev/null\n+++ b/range-diff.c\n@@ -0,0 +1,311 @@\n+#include \"cache.h\"\n+#include \"range-diff.h\"\n+#include \"string-list.h\"\n+#include \"run-command.h\"\n+#include \"argv-array.h\"\n+#include \"hashmap.h\"\n+#include \"xdiff-interface.h\"\n+#include \"linear-assignment.h\"\n+\n+struct patch_util {\n+\t/* For the search for an exact match */\n+\tstruct hashmap_entry e;\n+\tconst char *diff, *patch;\n+\n+\tint i;\n+\tint diffsize;\n+\tsize_t diff_offset;\n+\t/* the index of the matching item in the other branch, or -1 */\n+\tint matching;\n+\tstruct object_id oid;\n+};\n+\n+/*\n+ * Reads the patches into a string list, with the `util` field being populated\n+ * as struct object_id (will need to be free()d).\n+ */\n+static int read_patches(const char *range, struct string_list *list)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tFILE *in;\n+\tstruct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;\n+\tstruct patch_util *util = NULL;\n+\tint in_header = 1;\n+\n+\targv_array_pushl(&cp.args, \"log\", \"--no-color\", \"-p\", \"--no-merges\",\n+\t\t\t\"--reverse\", \"--date-order\", \"--decorate=no\",\n+\t\t\t\"--no-abbrev-commit\", range,\n+\t\t\tNULL);\n+\tcp.out = -1;\n+\tcp.no_stdin = 1;\n+\tcp.git_cmd = 1;\n+\n+\tif (start_command(&cp))\n+\t\treturn error_errno(_(\"could not start `log`\"));\n+\tin = fdopen(cp.out, \"r\");\n+\tif (!in) {\n+\t\terror_errno(_(\"could not read `log` output\"));\n+\t\tfinish_command(&cp);\n+\t\treturn -1;\n+\t}\n+\n+\twhile (strbuf_getline(&line, in) != EOF) {\n+\t\tconst char *p;\n+\n+\t\tif (skip_prefix(line.buf, \"commit \", &p)) {\n+\t\t\tif (util) {\n+\t\t\t\tstring_list_append(list, buf.buf)->util = util;\n+\t\t\t\tstrbuf_reset(&buf);\n+\t\t\t}\n+\t\t\tutil = xcalloc(sizeof(*util), 1);\n+\t\t\tif (get_oid(p, &util->oid)) {\n+\t\t\t\terror(_(\"could not parse commit '%s'\"), p);\n+\t\t\t\tfree(util);\n+\t\t\t\tstring_list_clear(list, 1);\n+\t\t\t\tstrbuf_release(&buf);\n+\t\t\t\tstrbuf_release(&line);\n+\t\t\t\tfclose(in);\n+\t\t\t\tfinish_command(&cp);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t\tutil->matching = -1;\n+\t\t\tin_header = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (starts_with(line.buf, \"diff --git\")) {\n+\t\t\tin_header = 0;\n+\t\t\tstrbuf_addch(&buf, '\\n');\n+\t\t\tif (!util->diff_offset)\n+\t\t\t\tutil->diff_offset = buf.len;\n+\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\t} else if (in_header) {\n+\t\t\tif (starts_with(line.buf, \"Author: \")) {\n+\t\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\t\t\tstrbuf_addstr(&buf, \"\\n\\n\");\n+\t\t\t} else if (starts_with(line.buf, \"    \")) {\n+\t\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\t\t\tstrbuf_addch(&buf, '\\n');\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t} else if (starts_with(line.buf, \"@@ \"))\n+\t\t\tstrbuf_addstr(&buf, \"@@\");\n+\t\telse if (!line.buf[0] || starts_with(line.buf, \"index \"))\n+\t\t\t/*\n+\t\t\t * A completely blank (not ' \\n', which is context)\n+\t\t\t * line is not valid in a diff.  We skip it\n+\t\t\t * silently, because this neatly handles the blank\n+\t\t\t * separator line between commits in git-log\n+\t\t\t * output.\n+\t\t\t *\n+\t\t\t * We also want to ignore the diff's `index` lines\n+\t\t\t * because they contain exact blob hashes in which\n+\t\t\t * we are not interested.\n+\t\t\t */\n+\t\t\tcontinue;\n+\t\telse\n+\t\t\tstrbuf_addbuf(&buf, &line);\n+\n+\t\tstrbuf_addch(&buf, '\\n');\n+\t\tutil->diffsize++;\n+\t}\n+\tfclose(in);\n+\tstrbuf_release(&line);\n+\n+\tif (util)\n+\t\tstring_list_append(list, buf.buf)->util = util;\n+\tstrbuf_release(&buf);\n+\n+\tif (finish_command(&cp))\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+static int patch_util_cmp(const void *dummy, const struct patch_util *a,\n+\t\t     const struct patch_util *b, const char *keydata)\n+{\n+\treturn strcmp(a->diff, keydata ? keydata : b->diff);\n+}\n+\n+static void find_exact_matches(struct string_list *a, struct string_list *b)\n+{\n+\tstruct hashmap map;\n+\tint i;\n+\n+\thashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);\n+\n+\t/* First, add the patches of a to a hash map */\n+\tfor (i = 0; i < a->nr; i++) {\n+\t\tstruct patch_util *util = a->items[i].util;\n+\n+\t\tutil->i = i;\n+\t\tutil->patch = a->items[i].string;\n+\t\tutil->diff = util->patch + util->diff_offset;\n+\t\thashmap_entry_init(util, strhash(util->diff));\n+\t\thashmap_add(&map, util);\n+\t}\n+\n+\t/* Now try to find exact matches in b */\n+\tfor (i = 0; i < b->nr; i++) {\n+\t\tstruct patch_util *util = b->items[i].util, *other;\n+\n+\t\tutil->i = i;\n+\t\tutil->patch = b->items[i].string;\n+\t\tutil->diff = util->patch + util->diff_offset;\n+\t\thashmap_entry_init(util, strhash(util->diff));\n+\t\tother = hashmap_remove(&map, util, NULL);\n+\t\tif (other) {\n+\t\t\tif (other->matching >= 0)\n+\t\t\t\tBUG(\"already assigned!\");\n+\n+\t\t\tother->matching = i;\n+\t\t\tutil->matching = other->i;\n+\t\t}\n+\t}\n+\n+\thashmap_free(&map, 0);\n+}\n+\n+static void diffsize_consume(void *data, char *line, unsigned long len)\n+{\n+\t(*(int *)data)++;\n+}\n+\n+static int diffsize(const char *a, const char *b)\n+{\n+\txpparam_t pp = { 0 };\n+\txdemitconf_t cfg = { 0 };\n+\tmmfile_t mf1, mf2;\n+\tint count = 0;\n+\n+\tmf1.ptr = (char *)a;\n+\tmf1.size = strlen(a);\n+\tmf2.ptr = (char *)b;\n+\tmf2.size = strlen(b);\n+\n+\tcfg.ctxlen = 3;\n+\tif (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))\n+\t\treturn count;\n+\n+\terror(_(\"failed to generate diff\"));\n+\treturn COST_MAX;\n+}\n+\n+static void get_correspondences(struct string_list *a, struct string_list *b,\n+\t\t\t\tint creation_factor)\n+{\n+\tint n = a->nr + b->nr;\n+\tint *cost, c, *a2b, *b2a;\n+\tint i, j;\n+\n+\tALLOC_ARRAY(cost, st_mult(n, n));\n+\tALLOC_ARRAY(a2b, n);\n+\tALLOC_ARRAY(b2a, n);\n+\n+\tfor (i = 0; i < a->nr; i++) {\n+\t\tstruct patch_util *a_util = a->items[i].util;\n+\n+\t\tfor (j = 0; j < b->nr; j++) {\n+\t\t\tstruct patch_util *b_util = b->items[j].util;\n+\n+\t\t\tif (a_util->matching == j)\n+\t\t\t\tc = 0;\n+\t\t\telse if (a_util->matching < 0 && b_util->matching < 0)\n+\t\t\t\tc = diffsize(a_util->diff, b_util->diff);\n+\t\t\telse\n+\t\t\t\tc = COST_MAX;\n+\t\t\tcost[i + n * j] = c;\n+\t\t}\n+\n+\t\tc = a_util->matching < 0 ?\n+\t\t\ta_util->diffsize * creation_factor / 100 : COST_MAX;\n+\t\tfor (j = b->nr; j < n; j++)\n+\t\t\tcost[i + n * j] = c;\n+\t}\n+\n+\tfor (j = 0; j < b->nr; j++) {\n+\t\tstruct patch_util *util = b->items[j].util;\n+\n+\t\tc = util->matching < 0 ?\n+\t\t\tutil->diffsize * creation_factor / 100 : COST_MAX;\n+\t\tfor (i = a->nr; i < n; i++)\n+\t\t\tcost[i + n * j] = c;\n+\t}\n+\n+\tfor (i = a->nr; i < n; i++)\n+\t\tfor (j = b->nr; j < n; j++)\n+\t\t\tcost[i + n * j] = 0;\n+\n+\tcompute_assignment(n, n, cost, a2b, b2a);\n+\n+\tfor (i = 0; i < a->nr; i++)\n+\t\tif (a2b[i] >= 0 && a2b[i] < b->nr) {\n+\t\t\tstruct patch_util *a_util = a->items[i].util;\n+\t\t\tstruct patch_util *b_util = b->items[a2b[i]].util;\n+\n+\t\t\ta_util->matching = a2b[i];\n+\t\t\tb_util->matching = i;\n+\t\t}\n+\n+\tfree(cost);\n+\tfree(a2b);\n+\tfree(b2a);\n+}\n+\n+static const char *short_oid(struct patch_util *util)\n+{\n+\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n+}\n+\n+static void output(struct string_list *a, struct string_list *b)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < b->nr; i++) {\n+\t\tstruct patch_util *util = b->items[i].util, *prev;\n+\n+\t\tif (util->matching < 0)\n+\t\t\tprintf(\"-: -------- > %d: %s\\n\",\n+\t\t\t\t\ti + 1, short_oid(util));\n+\t\telse {\n+\t\t\tprev = a->items[util->matching].util;\n+\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n+\t\t\t       util->matching + 1, short_oid(prev),\n+\t\t\t       i + 1, short_oid(util));\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i < a->nr; i++) {\n+\t\tstruct patch_util *util = a->items[i].util;\n+\n+\t\tif (util->matching < 0)\n+\t\t\tprintf(\"%d: %s < -: --------\\n\",\n+\t\t\t       i + 1, short_oid(util));\n+\t}\n+}\n+\n+int show_range_diff(const char *range1, const char *range2,\n+\t\t    int creation_factor)\n+{\n+\tint res = 0;\n+\n+\tstruct string_list branch1 = STRING_LIST_INIT_DUP;\n+\tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n+\n+\tif (read_patches(range1, &branch1))\n+\t\tres = error(_(\"could not parse log for '%s'\"), range1);\n+\tif (!res && read_patches(range2, &branch2))\n+\t\tres = error(_(\"could not parse log for '%s'\"), range2);\n+\n+\tif (!res) {\n+\t\tfind_exact_matches(&branch1, &branch2);\n+\t\tget_correspondences(&branch1, &branch2, creation_factor);\n+\t\toutput(&branch1, &branch2);\n+\t}\n+\n+\tstring_list_clear(&branch1, 1);\n+\tstring_list_clear(&branch2, 1);\n+\n+\treturn res;\n+}\ndiff --git a/range-diff.h b/range-diff.h\nnew file mode 100644\nindex 000000000..7b6eef303\n--- /dev/null\n+++ b/range-diff.h\n@@ -0,0 +1,7 @@\n+#ifndef RANGE_DIFF_H\n+#define RANGE_DIFF_H\n+\n+int show_range_diff(const char *range1, const char *range2,\n+\t\t    int creation_factor);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"355177","messageId":"7b90919685d1e94b50f5278ec1b57a59cba1f8e5.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 04/21] range-diff: improve the order of the shown commits","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:31Z","receivedAt":"2018-08-10T22:14:36Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis patch lets `git range-diff` use the same order as tbdiff.\n\nThe idea is simple: for left-to-right readers, it is natural to assume\nthat the `git range-diff` is performed between an older vs a newer\nversion of the branch. As such, the user is probably more interested in\nthe question \"where did this come from?\" rather than \"where did that one\ngo?\".\n\nTo that end, we list the commits in the order of the second commit range\n(\"the newer version\"), inserting the unmatched commits of the first\ncommit range as soon as all their predecessors have been shown.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 59 +++++++++++++++++++++++++++++++++++-----------------\n 1 file changed, 40 insertions(+), 19 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 15d418afa..2d94200d3 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -12,7 +12,7 @@ struct patch_util {\n \tstruct hashmap_entry e;\n \tconst char *diff, *patch;\n \n-\tint i;\n+\tint i, shown;\n \tint diffsize;\n \tsize_t diff_offset;\n \t/* the index of the matching item in the other branch, or -1 */\n@@ -260,28 +260,49 @@ static const char *short_oid(struct patch_util *util)\n \n static void output(struct string_list *a, struct string_list *b)\n {\n-\tint i;\n-\n-\tfor (i = 0; i < b->nr; i++) {\n-\t\tstruct patch_util *util = b->items[i].util, *prev;\n+\tint i = 0, j = 0;\n+\n+\t/*\n+\t * We assume the user is really more interested in the second argument\n+\t * (\"newer\" version). To that end, we print the output in the order of\n+\t * the RHS (the `b` parameter). To put the LHS (the `a` parameter)\n+\t * commits that are no longer in the RHS into a good place, we place\n+\t * them once we have shown all of their predecessors in the LHS.\n+\t */\n+\n+\twhile (i < a->nr || j < b->nr) {\n+\t\tstruct patch_util *a_util, *b_util;\n+\t\ta_util = i < a->nr ? a->items[i].util : NULL;\n+\t\tb_util = j < b->nr ? b->items[j].util : NULL;\n+\n+\t\t/* Skip all the already-shown commits from the LHS. */\n+\t\twhile (i < a->nr && a_util->shown)\n+\t\t\ta_util = ++i < a->nr ? a->items[i].util : NULL;\n+\n+\t\t/* Show unmatched LHS commit whose predecessors were shown. */\n+\t\tif (i < a->nr && a_util->matching < 0) {\n+\t\t\tprintf(\"%d: %s < -: --------\\n\",\n+\t\t\t       i + 1, short_oid(a_util));\n+\t\t\ti++;\n+\t\t\tcontinue;\n+\t\t}\n \n-\t\tif (util->matching < 0)\n+\t\t/* Show unmatched RHS commits. */\n+\t\twhile (j < b->nr && b_util->matching < 0) {\n \t\t\tprintf(\"-: -------- > %d: %s\\n\",\n-\t\t\t\t\ti + 1, short_oid(util));\n-\t\telse {\n-\t\t\tprev = a->items[util->matching].util;\n-\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n-\t\t\t       util->matching + 1, short_oid(prev),\n-\t\t\t       i + 1, short_oid(util));\n+\t\t\t       j + 1, short_oid(b_util));\n+\t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n-\t}\n-\n-\tfor (i = 0; i < a->nr; i++) {\n-\t\tstruct patch_util *util = a->items[i].util;\n \n-\t\tif (util->matching < 0)\n-\t\t\tprintf(\"%d: %s < -: --------\\n\",\n-\t\t\t       i + 1, short_oid(util));\n+\t\t/* Show matching LHS/RHS pair. */\n+\t\tif (j < b->nr) {\n+\t\t\ta_util = a->items[b_util->matching].util;\n+\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n+\t\t\t       b_util->matching + 1, short_oid(a_util),\n+\t\t\t       j + 1, short_oid(b_util));\n+\t\t\ta_util->shown = 1;\n+\t\t\tj++;\n+\t\t}\n \t}\n }\n \n-- \ngitgitgadget\n\n"},{"id":"355175","messageId":"167ca02a35551e48a8ba1ca65d5c46ba78134fcb.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 06/21] range-diff: right-trim commit messages","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:34Z","receivedAt":"2018-08-10T22:14:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen comparing commit messages, we need to keep in mind that they are\nindented by four spaces. That is, empty lines are no longer empty, but\nhave \"trailing whitespace\". When displaying them in color, that results\nin those nagging red lines.\n\nLet's just right-trim the lines in the commit message, it's not like\ntrailing white-space in the commit messages are important enough to care\nabout in `git range-diff`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 71883a4b7..1ecee2c09 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -85,6 +85,7 @@ static int read_patches(const char *range, struct string_list *list)\n \t\t\t\tstrbuf_addbuf(&buf, &line);\n \t\t\t\tstrbuf_addstr(&buf, \"\\n\\n\");\n \t\t\t} else if (starts_with(line.buf, \"    \")) {\n+\t\t\t\tstrbuf_rtrim(&line);\n \t\t\t\tstrbuf_addbuf(&buf, &line);\n \t\t\t\tstrbuf_addch(&buf, '\\n');\n \t\t\t}\n-- \ngitgitgadget\n\n"},{"id":"355176","messageId":"9e1e660077d41c479ae46eb07371204c01dff4cd.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 05/21] range-diff: also show the diff between patches","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:32Z","receivedAt":"2018-08-10T22:14:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nJust like tbdiff, we now show the diff between matching patches. This is\na \"diff of two diffs\", so it can be a bit daunting to read for the\nbeginner.\n\nAn alternative would be to display an interdiff, i.e. the hypothetical\ndiff which is the result of first reverting the old diff and then\napplying the new diff.\n\nEspecially when rebasing frequently, an interdiff is often not feasible,\nthough: if the old diff cannot be applied in reverse (due to a moving\nupstream), an interdiff can simply not be inferred.\n\nThis commit brings `range-diff` closer to feature parity with regard\nto tbdiff.\n\nTo make `git range-diff` respect e.g. color.diff.* settings, we have\nto adjust git_branch_config() accordingly.\n\nNote: while we now parse diff options such as --color, the effect is not\nyet the same as in tbdiff, where also the commit pairs would be colored.\nThis is left for a later commit.\n\nNote also: while tbdiff accepts the `--no-patches` option to suppress\nthese diffs between patches, we prefer the `-s` (or `--no-patch`) option\nthat is automatically supported via our use of diff_opt_parse().\n\nAnd finally note: to support diff options, we have to call\n`parse_options()` such that it keeps unknown options, and then loop over\nthose and let `diff_opt_parse()` handle them. After that loop, we have\nto call `parse_options()` again, to make sure that no unknown options\nare left.\n\nHelped-by: Thomas Gummerer <t.gummerer@gmail.com>\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/range-diff.c | 29 +++++++++++++++++++++++++++--\n range-diff.c         | 34 +++++++++++++++++++++++++++++++---\n range-diff.h         |  4 +++-\n 3 files changed, 61 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 94c1f362c..19192ab31 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -2,6 +2,7 @@\n #include \"builtin.h\"\n #include \"parse-options.h\"\n #include \"range-diff.h\"\n+#include \"config.h\"\n \n static const char * const builtin_range_diff_usage[] = {\n N_(\"git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n@@ -13,15 +14,38 @@ NULL\n int cmd_range_diff(int argc, const char **argv, const char *prefix)\n {\n \tint creation_factor = 60;\n+\tstruct diff_options diffopt = { NULL };\n \tstruct option options[] = {\n \t\tOPT_INTEGER(0, \"creation-factor\", &creation_factor,\n \t\t\t    N_(\"Percentage by which creation is weighted\")),\n \t\tOPT_END()\n \t};\n-\tint res = 0;\n+\tint i, j, res = 0;\n \tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n \n+\tgit_config(git_diff_ui_config, NULL);\n+\n+\tdiff_setup(&diffopt);\n+\tdiffopt.output_format = DIFF_FORMAT_PATCH;\n+\n \targc = parse_options(argc, argv, NULL, options,\n+\t\t\t     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN |\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);\n+\n+\tfor (i = j = 1; i < argc && strcmp(\"--\", argv[i]); ) {\n+\t\tint c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);\n+\n+\t\tif (!c)\n+\t\t\targv[j++] = argv[i++];\n+\t\telse\n+\t\t\ti += c;\n+\t}\n+\targc = j;\n+\tdiff_setup_done(&diffopt);\n+\n+\t/* Make sure that there are no unparsed options */\n+\targc = parse_options(argc, argv, NULL,\n+\t\t\t     options + ARRAY_SIZE(options) - 1, /* OPT_END */\n \t\t\t     builtin_range_diff_usage, 0);\n \n \tif (argc == 2) {\n@@ -59,7 +83,8 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(builtin_range_diff_usage, options);\n \t}\n \n-\tres = show_range_diff(range1.buf, range2.buf, creation_factor);\n+\tres = show_range_diff(range1.buf, range2.buf, creation_factor,\n+\t\t\t      &diffopt);\n \n \tstrbuf_release(&range1);\n \tstrbuf_release(&range2);\ndiff --git a/range-diff.c b/range-diff.c\nindex 2d94200d3..71883a4b7 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -6,6 +6,7 @@\n #include \"hashmap.h\"\n #include \"xdiff-interface.h\"\n #include \"linear-assignment.h\"\n+#include \"diffcore.h\"\n \n struct patch_util {\n \t/* For the search for an exact match */\n@@ -258,7 +259,31 @@ static const char *short_oid(struct patch_util *util)\n \treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n }\n \n-static void output(struct string_list *a, struct string_list *b)\n+static struct diff_filespec *get_filespec(const char *name, const char *p)\n+{\n+\tstruct diff_filespec *spec = alloc_filespec(name);\n+\n+\tfill_filespec(spec, &null_oid, 0, 0644);\n+\tspec->data = (char *)p;\n+\tspec->size = strlen(p);\n+\tspec->should_munmap = 0;\n+\tspec->is_stdin = 1;\n+\n+\treturn spec;\n+}\n+\n+static void patch_diff(const char *a, const char *b,\n+\t\t\t      struct diff_options *diffopt)\n+{\n+\tdiff_queue(&diff_queued_diff,\n+\t\t   get_filespec(\"a\", a), get_filespec(\"b\", b));\n+\n+\tdiffcore_std(diffopt);\n+\tdiff_flush(diffopt);\n+}\n+\n+static void output(struct string_list *a, struct string_list *b,\n+\t\t   struct diff_options *diffopt)\n {\n \tint i = 0, j = 0;\n \n@@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)\n \t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n \t\t\t       b_util->matching + 1, short_oid(a_util),\n \t\t\t       j + 1, short_oid(b_util));\n+\t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n+\t\t\t\tpatch_diff(a->items[b_util->matching].string,\n+\t\t\t\t\t   b->items[j].string, diffopt);\n \t\t\ta_util->shown = 1;\n \t\t\tj++;\n \t\t}\n@@ -307,7 +335,7 @@ static void output(struct string_list *a, struct string_list *b)\n }\n \n int show_range_diff(const char *range1, const char *range2,\n-\t\t    int creation_factor)\n+\t\t    int creation_factor, struct diff_options *diffopt)\n {\n \tint res = 0;\n \n@@ -322,7 +350,7 @@ int show_range_diff(const char *range1, const char *range2,\n \tif (!res) {\n \t\tfind_exact_matches(&branch1, &branch2);\n \t\tget_correspondences(&branch1, &branch2, creation_factor);\n-\t\toutput(&branch1, &branch2);\n+\t\toutput(&branch1, &branch2, diffopt);\n \t}\n \n \tstring_list_clear(&branch1, 1);\ndiff --git a/range-diff.h b/range-diff.h\nindex 7b6eef303..2407d46a3 100644\n--- a/range-diff.h\n+++ b/range-diff.h\n@@ -1,7 +1,9 @@\n #ifndef RANGE_DIFF_H\n #define RANGE_DIFF_H\n \n+#include \"diff.h\"\n+\n int show_range_diff(const char *range1, const char *range2,\n-\t\t    int creation_factor);\n+\t\t    int creation_factor, struct diff_options *diffopt);\n \n #endif\n-- \ngitgitgadget\n\n"},{"id":"355178","messageId":"ca8de8c75da1fd2cc2dcb4bf1cc3e4b11696c6f6.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 07/21] range-diff: indent the diffs just like tbdiff","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:35Z","receivedAt":"2018-08-10T22:14:39Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe main information in the `range-diff` view comes from the list of\nmatching and non-matching commits, the diffs are additional information.\nIndenting them helps with the reading flow.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/range-diff.c | 10 ++++++++++\n 1 file changed, 10 insertions(+)\n\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 19192ab31..f6df3f19a 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -11,6 +11,11 @@ N_(\"git range-diff [<options>] <base> <old-tip> <new-tip>\"),\n NULL\n };\n \n+static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)\n+{\n+\treturn data;\n+}\n+\n int cmd_range_diff(int argc, const char **argv, const char *prefix)\n {\n \tint creation_factor = 60;\n@@ -21,12 +26,16 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \tint i, j, res = 0;\n+\tstruct strbuf four_spaces = STRBUF_INIT;\n \tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n \n \tgit_config(git_diff_ui_config, NULL);\n \n \tdiff_setup(&diffopt);\n \tdiffopt.output_format = DIFF_FORMAT_PATCH;\n+\tdiffopt.output_prefix = output_prefix_cb;\n+\tstrbuf_addstr(&four_spaces, \"    \");\n+\tdiffopt.output_prefix_data = &four_spaces;\n \n \targc = parse_options(argc, argv, NULL, options,\n \t\t\t     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN |\n@@ -88,6 +97,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \n \tstrbuf_release(&range1);\n \tstrbuf_release(&range2);\n+\tstrbuf_release(&four_spaces);\n \n \treturn res;\n }\n-- \ngitgitgadget\n\n"},{"id":"355179","messageId":"eb94d1982ea06abd11ff3f2e120cf7b4ef09a869.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 08/21] range-diff: suppress the diff headers","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:37Z","receivedAt":"2018-08-10T22:14:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen showing the diff between corresponding patches of the two branch\nversions, we have to make up a fake filename to run the diff machinery.\n\nThat filename does not carry any meaningful information, hence tbdiff\nsuppresses it. So we should, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/range-diff.c | 1 +\n diff.c               | 5 ++++-\n diff.h               | 1 +\n 3 files changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex f6df3f19a..bc7a2fb76 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -33,6 +33,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \n \tdiff_setup(&diffopt);\n \tdiffopt.output_format = DIFF_FORMAT_PATCH;\n+\tdiffopt.flags.suppress_diff_headers = 1;\n \tdiffopt.output_prefix = output_prefix_cb;\n \tstrbuf_addstr(&four_spaces, \"    \");\n \tdiffopt.output_prefix_data = &four_spaces;\ndiff --git a/diff.c b/diff.c\nindex 04d044bbb..9c4bd9fa1 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -3395,13 +3395,16 @@ static void builtin_diff(const char *name_a,\n \t\tmemset(&xpp, 0, sizeof(xpp));\n \t\tmemset(&xecfg, 0, sizeof(xecfg));\n \t\tmemset(&ecbdata, 0, sizeof(ecbdata));\n+\t\tif (o->flags.suppress_diff_headers)\n+\t\t\tlbl[0] = NULL;\n \t\tecbdata.label_path = lbl;\n \t\tecbdata.color_diff = want_color(o->use_color);\n \t\tecbdata.ws_rule = whitespace_rule(name_b);\n \t\tif (ecbdata.ws_rule & WS_BLANK_AT_EOF)\n \t\t\tcheck_blank_at_eof(&mf1, &mf2, &ecbdata);\n \t\tecbdata.opt = o;\n-\t\tecbdata.header = header.len ? &header : NULL;\n+\t\tif (header.len && !o->flags.suppress_diff_headers)\n+\t\t\tecbdata.header = &header;\n \t\txpp.flags = o->xdl_opts;\n \t\txpp.anchors = o->anchors;\n \t\txpp.anchors_nr = o->anchors_nr;\ndiff --git a/diff.h b/diff.h\nindex a14895bb8..d88ceb357 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -94,6 +94,7 @@ struct diff_flags {\n \tunsigned funccontext:1;\n \tunsigned default_follow_renames:1;\n \tunsigned stat_with_summary:1;\n+\tunsigned suppress_diff_headers:1;\n };\n \n static inline void diff_flags_or(struct diff_flags *a,\n-- \ngitgitgadget\n\n"},{"id":"355180","messageId":"6330afad9e727e6940385cc60661739ec55e0ced.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 09/21] range-diff: adjust the output of the commit pairs","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:38Z","receivedAt":"2018-08-10T22:14:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis not only uses \"dashed stand-ins\" for \"pairs\" where one side is\nmissing (i.e. unmatched commits that are present only in one of the two\ncommit ranges), but also adds onelines for the reader's pleasure.\n\nThis change brings `git range-diff` yet another step closer to\nfeature parity with tbdiff: it now shows the oneline, too, and indicates\nwith `=` when the commits have identical diffs.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 59 ++++++++++++++++++++++++++++++++++++++++++++--------\n 1 file changed, 50 insertions(+), 9 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 1ecee2c09..23aa61af5 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -7,6 +7,8 @@\n #include \"xdiff-interface.h\"\n #include \"linear-assignment.h\"\n #include \"diffcore.h\"\n+#include \"commit.h\"\n+#include \"pretty.h\"\n \n struct patch_util {\n \t/* For the search for an exact match */\n@@ -255,9 +257,49 @@ static void get_correspondences(struct string_list *a, struct string_list *b,\n \tfree(b2a);\n }\n \n-static const char *short_oid(struct patch_util *util)\n+static void output_pair_header(struct strbuf *buf,\n+\t\t\t       struct strbuf *dashes,\n+\t\t\t       struct patch_util *a_util,\n+\t\t\t       struct patch_util *b_util)\n {\n-\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n+\tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n+\tstruct commit *commit;\n+\n+\tif (!dashes->len)\n+\t\tstrbuf_addchars(dashes, '-',\n+\t\t\t\tstrlen(find_unique_abbrev(oid,\n+\t\t\t\t\t\t\t  DEFAULT_ABBREV)));\n+\n+\tstrbuf_reset(buf);\n+\tif (!a_util)\n+\t\tstrbuf_addf(buf, \"-:  %s \", dashes->buf);\n+\telse\n+\t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n+\t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n+\n+\tif (!a_util)\n+\t\tstrbuf_addch(buf, '>');\n+\telse if (!b_util)\n+\t\tstrbuf_addch(buf, '<');\n+\telse if (strcmp(a_util->patch, b_util->patch))\n+\t\tstrbuf_addch(buf, '!');\n+\telse\n+\t\tstrbuf_addch(buf, '=');\n+\n+\tif (!b_util)\n+\t\tstrbuf_addf(buf, \" -:  %s\", dashes->buf);\n+\telse\n+\t\tstrbuf_addf(buf, \" %d:  %s\", b_util->i + 1,\n+\t\t\t    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));\n+\n+\tcommit = lookup_commit_reference(the_repository, oid);\n+\tif (commit) {\n+\t\tstrbuf_addch(buf, ' ');\n+\t\tpp_commit_easy(CMIT_FMT_ONELINE, commit, buf);\n+\t}\n+\tstrbuf_addch(buf, '\\n');\n+\n+\tfwrite(buf->buf, buf->len, 1, stdout);\n }\n \n static struct diff_filespec *get_filespec(const char *name, const char *p)\n@@ -286,6 +328,7 @@ static void patch_diff(const char *a, const char *b,\n static void output(struct string_list *a, struct string_list *b,\n \t\t   struct diff_options *diffopt)\n {\n+\tstruct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;\n \tint i = 0, j = 0;\n \n \t/*\n@@ -307,25 +350,21 @@ static void output(struct string_list *a, struct string_list *b,\n \n \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n \t\tif (i < a->nr && a_util->matching < 0) {\n-\t\t\tprintf(\"%d: %s < -: --------\\n\",\n-\t\t\t       i + 1, short_oid(a_util));\n+\t\t\toutput_pair_header(&buf, &dashes, a_util, NULL);\n \t\t\ti++;\n \t\t\tcontinue;\n \t\t}\n \n \t\t/* Show unmatched RHS commits. */\n \t\twhile (j < b->nr && b_util->matching < 0) {\n-\t\t\tprintf(\"-: -------- > %d: %s\\n\",\n-\t\t\t       j + 1, short_oid(b_util));\n+\t\t\toutput_pair_header(&buf, &dashes, NULL, b_util);\n \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n \n \t\t/* Show matching LHS/RHS pair. */\n \t\tif (j < b->nr) {\n \t\t\ta_util = a->items[b_util->matching].util;\n-\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n-\t\t\t       b_util->matching + 1, short_oid(a_util),\n-\t\t\t       j + 1, short_oid(b_util));\n+\t\t\toutput_pair_header(&buf, &dashes, a_util, b_util);\n \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n \t\t\t\t\t   b->items[j].string, diffopt);\n@@ -333,6 +372,8 @@ static void output(struct string_list *a, struct string_list *b,\n \t\t\tj++;\n \t\t}\n \t}\n+\tstrbuf_release(&buf);\n+\tstrbuf_release(&dashes);\n }\n \n int show_range_diff(const char *range1, const char *range2,\n-- \ngitgitgadget\n\n"},{"id":"355181","messageId":"c296675eb0d0f825213e948655ac13dc9bf51ec9.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 10/21] range-diff: do not show \"function names\" in hunk headers","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:40Z","receivedAt":"2018-08-10T22:14:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWe are comparing complete, formatted commit messages with patches. There\nare no function names here, so stop looking for them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 23aa61af5..6d75563f4 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -9,6 +9,7 @@\n #include \"diffcore.h\"\n #include \"commit.h\"\n #include \"pretty.h\"\n+#include \"userdiff.h\"\n \n struct patch_util {\n \t/* For the search for an exact match */\n@@ -302,6 +303,10 @@ static void output_pair_header(struct strbuf *buf,\n \tfwrite(buf->buf, buf->len, 1, stdout);\n }\n \n+static struct userdiff_driver no_func_name = {\n+\t.funcname = { \"$^\", 0 }\n+};\n+\n static struct diff_filespec *get_filespec(const char *name, const char *p)\n {\n \tstruct diff_filespec *spec = alloc_filespec(name);\n@@ -311,6 +316,7 @@ static struct diff_filespec *get_filespec(const char *name, const char *p)\n \tspec->size = strlen(p);\n \tspec->should_munmap = 0;\n \tspec->is_stdin = 1;\n+\tspec->driver = &no_func_name;\n \n \treturn spec;\n }\n-- \ngitgitgadget\n\n"},{"id":"355182","messageId":"85e0ab82f601b8594495737882187b2531f379f3.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 11/21] range-diff: add tests","fromName":"Thomas Rast via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:41Z","receivedAt":"2018-08-10T22:14:45Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"From: Thomas Rast <tr@thomasrast.ch>\n\nThese are essentially lifted from https://github.com/trast/tbdiff, with\nlight touch-ups to account for the command now being named `git\nrange-diff`.\n\nApart from renaming `tbdiff` to `range-diff`, only one test case needed\nto be adjusted: 11 - 'changed message'.\n\nThe underlying reason it had to be adjusted is that diff generation is\nsometimes ambiguous. In this case, a comment line and an empty line are\nadded, but it is ambiguous whether they were added after the existing\nempty line, or whether an empty line and the comment line are added\n*before* the existing empty line. And apparently xdiff picks a different\noption here than Python's difflib.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/.gitattributes       |   1 +\n t/t3206-range-diff.sh  | 145 ++++++++++\n t/t3206/history.export | 604 +++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 750 insertions(+)\n create mode 100755 t/t3206-range-diff.sh\n create mode 100644 t/t3206/history.export\n\ndiff --git a/t/.gitattributes b/t/.gitattributes\nindex 3bd959ae5..b17bf71b8 100644\n--- a/t/.gitattributes\n+++ b/t/.gitattributes\n@@ -1,6 +1,7 @@\n t[0-9][0-9][0-9][0-9]/* -whitespace\n /diff-lib/* eol=lf\n /t0110/url-* binary\n+/t3206/* eol=lf\n /t3900/*.txt eol=lf\n /t3901/*.txt eol=lf\n /t4034/*/* eol=lf\ndiff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh\nnew file mode 100755\nindex 000000000..2237c7f4a\n--- /dev/null\n+++ b/t/t3206-range-diff.sh\n@@ -0,0 +1,145 @@\n+#!/bin/sh\n+\n+test_description='range-diff tests'\n+\n+. ./test-lib.sh\n+\n+# Note that because of the range-diff's heuristics, test_commit does more\n+# harm than good.  We need some real history.\n+\n+test_expect_success 'setup' '\n+\tgit fast-import < \"$TEST_DIRECTORY\"/t3206/history.export\n+'\n+\n+test_expect_success 'simple A..B A..C (unmodified)' '\n+\tgit range-diff --no-color master..topic master..unmodified \\\n+\t\t>actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  35b9b25 s/5/A/\n+\t2:  fccce22 = 2:  de345ab s/4/A/\n+\t3:  147e64e = 3:  9af6654 s/11/B/\n+\t4:  a63e992 = 4:  2901f77 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'simple B...C (unmodified)' '\n+\tgit range-diff --no-color topic...unmodified >actual &&\n+\t# same \"expected\" as above\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'simple A B C (unmodified)' '\n+\tgit range-diff --no-color master topic unmodified >actual &&\n+\t# same \"expected\" as above\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'trivial reordering' '\n+\tgit range-diff --no-color master topic reordered >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  aca177a s/5/A/\n+\t3:  147e64e = 2:  14ad629 s/11/B/\n+\t4:  a63e992 = 3:  ee58208 s/12/B/\n+\t2:  fccce22 = 4:  307b27a s/4/A/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'removed a commit' '\n+\tgit range-diff --no-color master topic removed >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  7657159 s/5/A/\n+\t2:  fccce22 < -:  ------- s/4/A/\n+\t3:  147e64e = 2:  43d84d3 s/11/B/\n+\t4:  a63e992 = 3:  a740396 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'added a commit' '\n+\tgit range-diff --no-color master topic added >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  2716022 s/5/A/\n+\t2:  fccce22 = 2:  b62accd s/4/A/\n+\t-:  ------- > 3:  df46cfa s/6/A/\n+\t3:  147e64e = 4:  3e64548 s/11/B/\n+\t4:  a63e992 = 5:  12b4063 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'new base, A B C' '\n+\tgit range-diff --no-color master topic rebased >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  cc9c443 s/5/A/\n+\t2:  fccce22 = 2:  c5d9641 s/4/A/\n+\t3:  147e64e = 3:  28cc2b6 s/11/B/\n+\t4:  a63e992 = 4:  5628ab7 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'new base, B...C' '\n+\t# this syntax includes the commits from master!\n+\tgit range-diff --no-color topic...rebased >actual &&\n+\tcat >expected <<-EOF &&\n+\t-:  ------- > 1:  a31b12e unrelated\n+\t1:  4de457d = 2:  cc9c443 s/5/A/\n+\t2:  fccce22 = 3:  c5d9641 s/4/A/\n+\t3:  147e64e = 4:  28cc2b6 s/11/B/\n+\t4:  a63e992 = 5:  5628ab7 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'changed commit' '\n+\tgit range-diff --no-color topic...changed >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  a4b3333 s/5/A/\n+\t2:  fccce22 = 2:  f51d370 s/4/A/\n+\t3:  147e64e ! 3:  0559556 s/11/B/\n+\t    @@ -10,7 +10,7 @@\n+\t      9\n+\t      10\n+\t     -11\n+\t    -+B\n+\t    ++BB\n+\t      12\n+\t      13\n+\t      14\n+\t4:  a63e992 ! 4:  d966c5c s/12/B/\n+\t    @@ -8,7 +8,7 @@\n+\t     @@\n+\t      9\n+\t      10\n+\t    - B\n+\t    + BB\n+\t     -12\n+\t     +B\n+\t      13\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'changed message' '\n+\tgit range-diff --no-color topic...changed-message >actual &&\n+\tsed s/Z/\\ /g >expected <<-EOF &&\n+\t1:  4de457d = 1:  f686024 s/5/A/\n+\t2:  fccce22 ! 2:  4ab067d s/4/A/\n+\t    @@ -2,6 +2,8 @@\n+\t    Z\n+\t    Z    s/4/A/\n+\t    Z\n+\t    +    Also a silly comment here!\n+\t    +\n+\t    Zdiff --git a/file b/file\n+\t    Z--- a/file\n+\t    Z+++ b/file\n+\t3:  147e64e = 3:  b9cb956 s/11/B/\n+\t4:  a63e992 = 4:  8add5f1 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_done\ndiff --git a/t/t3206/history.export b/t/t3206/history.export\nnew file mode 100644\nindex 000000000..b8ffff094\n--- /dev/null\n+++ b/t/t3206/history.export\n@@ -0,0 +1,604 @@\n+blob\n+mark :1\n+data 51\n+1\n+2\n+3\n+4\n+5\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+reset refs/heads/removed\n+commit refs/heads/removed\n+mark :2\n+author Thomas Rast <trast@inf.ethz.ch> 1374424921 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374484724 +0200\n+data 8\n+initial\n+M 100644 :1 file\n+\n+blob\n+mark :3\n+data 51\n+1\n+2\n+3\n+4\n+A\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :4\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+blob\n+mark :5\n+data 51\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :6\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+data 7\n+s/4/A/\n+from :4\n+M 100644 :5 file\n+\n+blob\n+mark :7\n+data 50\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :8\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+data 8\n+s/11/B/\n+from :6\n+M 100644 :7 file\n+\n+blob\n+mark :9\n+data 49\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :10\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+data 8\n+s/12/B/\n+from :8\n+M 100644 :9 file\n+\n+blob\n+mark :11\n+data 10\n+unrelated\n+\n+commit refs/heads/master\n+mark :12\n+author Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200\n+data 10\n+unrelated\n+from :2\n+M 100644 :11 otherfile\n+\n+commit refs/heads/rebased\n+mark :13\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485137 +0200\n+data 7\n+s/5/A/\n+from :12\n+M 100644 :3 file\n+\n+commit refs/heads/rebased\n+mark :14\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n+data 7\n+s/4/A/\n+from :13\n+M 100644 :5 file\n+\n+commit refs/heads/rebased\n+mark :15\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n+data 8\n+s/11/B/\n+from :14\n+M 100644 :7 file\n+\n+commit refs/heads/rebased\n+mark :16\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n+data 8\n+s/12/B/\n+from :15\n+M 100644 :9 file\n+\n+commit refs/heads/added\n+mark :17\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/added\n+mark :18\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 7\n+s/4/A/\n+from :17\n+M 100644 :5 file\n+\n+blob\n+mark :19\n+data 51\n+1\n+2\n+3\n+A\n+A\n+A\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/added\n+mark :20\n+author Thomas Rast <trast@inf.ethz.ch> 1374485186 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 7\n+s/6/A/\n+from :18\n+M 100644 :19 file\n+\n+blob\n+mark :21\n+data 50\n+1\n+2\n+3\n+A\n+A\n+A\n+7\n+8\n+9\n+10\n+B\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/added\n+mark :22\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 8\n+s/11/B/\n+from :20\n+M 100644 :21 file\n+\n+blob\n+mark :23\n+data 49\n+1\n+2\n+3\n+A\n+A\n+A\n+7\n+8\n+9\n+10\n+B\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/added\n+mark :24\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 8\n+s/12/B/\n+from :22\n+M 100644 :23 file\n+\n+commit refs/heads/reordered\n+mark :25\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+blob\n+mark :26\n+data 50\n+1\n+2\n+3\n+4\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/reordered\n+mark :27\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 8\n+s/11/B/\n+from :25\n+M 100644 :26 file\n+\n+blob\n+mark :28\n+data 49\n+1\n+2\n+3\n+4\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/reordered\n+mark :29\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 8\n+s/12/B/\n+from :27\n+M 100644 :28 file\n+\n+commit refs/heads/reordered\n+mark :30\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 7\n+s/4/A/\n+from :29\n+M 100644 :9 file\n+\n+commit refs/heads/changed\n+mark :31\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/changed\n+mark :32\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 7\n+s/4/A/\n+from :31\n+M 100644 :5 file\n+\n+blob\n+mark :33\n+data 51\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+BB\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/changed\n+mark :34\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 8\n+s/11/B/\n+from :32\n+M 100644 :33 file\n+\n+blob\n+mark :35\n+data 50\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+BB\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/changed\n+mark :36\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 8\n+s/12/B/\n+from :34\n+M 100644 :35 file\n+\n+commit refs/heads/changed-message\n+mark :37\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/changed-message\n+mark :38\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200\n+data 35\n+s/4/A/\n+\n+Also a silly comment here!\n+from :37\n+M 100644 :5 file\n+\n+commit refs/heads/changed-message\n+mark :39\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200\n+data 8\n+s/11/B/\n+from :38\n+M 100644 :7 file\n+\n+commit refs/heads/changed-message\n+mark :40\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200\n+data 8\n+s/12/B/\n+from :39\n+M 100644 :9 file\n+\n+commit refs/heads/unmodified\n+mark :41\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/unmodified\n+mark :42\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200\n+data 7\n+s/4/A/\n+from :41\n+M 100644 :5 file\n+\n+commit refs/heads/unmodified\n+mark :43\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200\n+data 8\n+s/11/B/\n+from :42\n+M 100644 :7 file\n+\n+commit refs/heads/unmodified\n+mark :44\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200\n+data 8\n+s/12/B/\n+from :43\n+M 100644 :9 file\n+\n+commit refs/heads/removed\n+mark :45\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/removed\n+mark :46\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n+data 8\n+s/11/B/\n+from :45\n+M 100644 :26 file\n+\n+commit refs/heads/removed\n+mark :47\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n+data 8\n+s/12/B/\n+from :46\n+M 100644 :28 file\n+\n+reset refs/heads/removed\n+from :47\n+\n-- \ngitgitgadget\n\n"},{"id":"355183","messageId":"f48b62644bc9cd7a65cbcc522501e37359385089.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 12/21] range-diff: use color for the commit pairs","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:43Z","receivedAt":"2018-08-10T22:14:46Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nArguably the most important part of `git range-diff`'s output is the\nlist of commits in the two branches, together with their relationships.\n\nFor that reason, tbdiff introduced color-coding that is pretty\nintuitive, especially for unchanged patches (all dim yellow, like the\nfirst line in `git show`'s output) vs modified patches (old commit is\nred, new commit is green). Let's imitate that color scheme.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 51 ++++++++++++++++++++++++++++++++++++++-------------\n 1 file changed, 38 insertions(+), 13 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 6d75563f4..b1663da7c 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -258,34 +258,53 @@ static void get_correspondences(struct string_list *a, struct string_list *b,\n \tfree(b2a);\n }\n \n-static void output_pair_header(struct strbuf *buf,\n+static void output_pair_header(struct diff_options *diffopt,\n+\t\t\t       struct strbuf *buf,\n \t\t\t       struct strbuf *dashes,\n \t\t\t       struct patch_util *a_util,\n \t\t\t       struct patch_util *b_util)\n {\n \tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n \tstruct commit *commit;\n+\tchar status;\n+\tconst char *color_reset = diff_get_color_opt(diffopt, DIFF_RESET);\n+\tconst char *color_old = diff_get_color_opt(diffopt, DIFF_FILE_OLD);\n+\tconst char *color_new = diff_get_color_opt(diffopt, DIFF_FILE_NEW);\n+\tconst char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT);\n+\tconst char *color;\n \n \tif (!dashes->len)\n \t\tstrbuf_addchars(dashes, '-',\n \t\t\t\tstrlen(find_unique_abbrev(oid,\n \t\t\t\t\t\t\t  DEFAULT_ABBREV)));\n \n+\tif (!b_util) {\n+\t\tcolor = color_old;\n+\t\tstatus = '<';\n+\t} else if (!a_util) {\n+\t\tcolor = color_new;\n+\t\tstatus = '>';\n+\t} else if (strcmp(a_util->patch, b_util->patch)) {\n+\t\tcolor = color_commit;\n+\t\tstatus = '!';\n+\t} else {\n+\t\tcolor = color_commit;\n+\t\tstatus = '=';\n+\t}\n+\n \tstrbuf_reset(buf);\n+\tstrbuf_addstr(buf, status == '!' ? color_old : color);\n \tif (!a_util)\n \t\tstrbuf_addf(buf, \"-:  %s \", dashes->buf);\n \telse\n \t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n \t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n \n-\tif (!a_util)\n-\t\tstrbuf_addch(buf, '>');\n-\telse if (!b_util)\n-\t\tstrbuf_addch(buf, '<');\n-\telse if (strcmp(a_util->patch, b_util->patch))\n-\t\tstrbuf_addch(buf, '!');\n-\telse\n-\t\tstrbuf_addch(buf, '=');\n+\tif (status == '!')\n+\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color);\n+\tstrbuf_addch(buf, status);\n+\tif (status == '!')\n+\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color_new);\n \n \tif (!b_util)\n \t\tstrbuf_addf(buf, \" -:  %s\", dashes->buf);\n@@ -295,10 +314,13 @@ static void output_pair_header(struct strbuf *buf,\n \n \tcommit = lookup_commit_reference(the_repository, oid);\n \tif (commit) {\n+\t\tif (status == '!')\n+\t\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color);\n+\n \t\tstrbuf_addch(buf, ' ');\n \t\tpp_commit_easy(CMIT_FMT_ONELINE, commit, buf);\n \t}\n-\tstrbuf_addch(buf, '\\n');\n+\tstrbuf_addf(buf, \"%s\\n\", color_reset);\n \n \tfwrite(buf->buf, buf->len, 1, stdout);\n }\n@@ -356,21 +378,24 @@ static void output(struct string_list *a, struct string_list *b,\n \n \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n \t\tif (i < a->nr && a_util->matching < 0) {\n-\t\t\toutput_pair_header(&buf, &dashes, a_util, NULL);\n+\t\t\toutput_pair_header(diffopt,\n+\t\t\t\t\t   &buf, &dashes, a_util, NULL);\n \t\t\ti++;\n \t\t\tcontinue;\n \t\t}\n \n \t\t/* Show unmatched RHS commits. */\n \t\twhile (j < b->nr && b_util->matching < 0) {\n-\t\t\toutput_pair_header(&buf, &dashes, NULL, b_util);\n+\t\t\toutput_pair_header(diffopt,\n+\t\t\t\t\t   &buf, &dashes, NULL, b_util);\n \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n \n \t\t/* Show matching LHS/RHS pair. */\n \t\tif (j < b->nr) {\n \t\t\ta_util = a->items[b_util->matching].util;\n-\t\t\toutput_pair_header(&buf, &dashes, a_util, b_util);\n+\t\t\toutput_pair_header(diffopt,\n+\t\t\t\t\t   &buf, &dashes, a_util, b_util);\n \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n \t\t\t\t\t   b->items[j].string, diffopt);\n-- \ngitgitgadget\n\n"},{"id":"355184","messageId":"1ad74f9396b216d853a3e96206692f9a137aef6a.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 13/21] color: add the meta color GIT_COLOR_REVERSE","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:44Z","receivedAt":"2018-08-10T22:14:47Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis \"color\" simply reverts background and foreground. It will be used\nin the upcoming \"dual color\" mode of `git range-diff`, where we will\nreverse colors for the -/+ markers and the fragment headers of the\n\"outer\" diff.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n color.h | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/color.h b/color.h\nindex 5b744e1bc..33e786342 100644\n--- a/color.h\n+++ b/color.h\n@@ -44,6 +44,7 @@ struct strbuf;\n #define GIT_COLOR_BG_CYAN\t\"\\033[46m\"\n #define GIT_COLOR_FAINT\t\t\"\\033[2m\"\n #define GIT_COLOR_FAINT_ITALIC\t\"\\033[2;3m\"\n+#define GIT_COLOR_REVERSE\t\"\\033[7m\"\n \n /* A special value meaning \"no color selected\" */\n #define GIT_COLOR_NIL \"NIL\"\n-- \ngitgitgadget\n\n"},{"id":"355185","messageId":"39a0ecd289e336a2cb300bde32b032888a745453.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 14/21] diff: add an internal option to dual-color diffs of diffs","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:45Z","receivedAt":"2018-08-10T22:14:49Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen diffing diffs, it can be quite daunting to figure out what the heck\nis going on, as there are nested +/- signs.\n\nLet's make this easier by adding a flag in diff_options that allows\ncolor-coding the outer diff sign with inverted colors, so that the\npreimage and postimage is colored like the diff it is.\n\nOf course, this really only makes sense when the preimage and postimage\n*are* diffs. So let's not expose this flag via a command-line option for\nnow.\n\nThis is a feature that was invented by git-tbdiff, and it will be used\nby `git range-diff` in the next commit, by offering it via a new option:\n`--dual-color`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n diff.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++-----------\n diff.h |  1 +\n 2 files changed, 69 insertions(+), 15 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 9c4bd9fa1..e6c857abf 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -609,14 +609,18 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,\n \tecbdata->blank_at_eof_in_postimage = (at - l2) + 1;\n }\n \n-static void emit_line_0(struct diff_options *o, const char *set, const char *reset,\n+static void emit_line_0(struct diff_options *o,\n+\t\t\tconst char *set, unsigned reverse, const char *reset,\n \t\t\tint first, const char *line, int len)\n {\n \tint has_trailing_newline, has_trailing_carriage_return;\n \tint nofirst;\n \tFILE *file = o->file;\n \n-\tfputs(diff_line_prefix(o), file);\n+\tif (first)\n+\t\tfputs(diff_line_prefix(o), file);\n+\telse if (!len)\n+\t\treturn;\n \n \tif (len == 0) {\n \t\thas_trailing_newline = (first == '\\n');\n@@ -634,8 +638,10 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res\n \t}\n \n \tif (len || !nofirst) {\n+\t\tif (reverse && want_color(o->use_color))\n+\t\t\tfputs(GIT_COLOR_REVERSE, file);\n \t\tfputs(set, file);\n-\t\tif (!nofirst)\n+\t\tif (first && !nofirst)\n \t\t\tfputc(first, file);\n \t\tfwrite(line, len, 1, file);\n \t\tfputs(reset, file);\n@@ -649,7 +655,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res\n static void emit_line(struct diff_options *o, const char *set, const char *reset,\n \t\t      const char *line, int len)\n {\n-\temit_line_0(o, set, reset, line[0], line+1, len-1);\n+\temit_line_0(o, set, 0, reset, line[0], line+1, len-1);\n }\n \n enum diff_symbol {\n@@ -1168,7 +1174,8 @@ static void dim_moved_lines(struct diff_options *o)\n \n static void emit_line_ws_markup(struct diff_options *o,\n \t\t\t\tconst char *set, const char *reset,\n-\t\t\t\tconst char *line, int len, char sign,\n+\t\t\t\tconst char *line, int len,\n+\t\t\t\tconst char *set_sign, char sign,\n \t\t\t\tunsigned ws_rule, int blank_at_eof)\n {\n \tconst char *ws = NULL;\n@@ -1179,14 +1186,20 @@ static void emit_line_ws_markup(struct diff_options *o,\n \t\t\tws = NULL;\n \t}\n \n-\tif (!ws)\n-\t\temit_line_0(o, set, reset, sign, line, len);\n-\telse if (blank_at_eof)\n+\tif (!ws && !set_sign)\n+\t\temit_line_0(o, set, 0, reset, sign, line, len);\n+\telse if (!ws) {\n+\t\t/* Emit just the prefix, then the rest. */\n+\t\temit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n+\t\t\t    sign, \"\", 0);\n+\t\temit_line_0(o, set, 0, reset, 0, line, len);\n+\t} else if (blank_at_eof)\n \t\t/* Blank line at EOF - paint '+' as well */\n-\t\temit_line_0(o, ws, reset, sign, line, len);\n+\t\temit_line_0(o, ws, 0, reset, sign, line, len);\n \telse {\n \t\t/* Emit just the prefix, then the rest. */\n-\t\temit_line_0(o, set, reset, sign, \"\", 0);\n+\t\temit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n+\t\t\t    sign, \"\", 0);\n \t\tws_check_emit(line, len, ws_rule,\n \t\t\t      o->file, set, reset, ws);\n \t}\n@@ -1196,7 +1209,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\t\t\t struct emitted_diff_symbol *eds)\n {\n \tstatic const char *nneof = \" No newline at end of file\\n\";\n-\tconst char *context, *reset, *set, *meta, *fraginfo;\n+\tconst char *context, *reset, *set, *set_sign, *meta, *fraginfo;\n \tstruct strbuf sb = STRBUF_INIT;\n \n \tenum diff_symbol s = eds->s;\n@@ -1209,7 +1222,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\tcontext = diff_get_color_opt(o, DIFF_CONTEXT);\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n \t\tputc('\\n', o->file);\n-\t\temit_line_0(o, context, reset, '\\\\',\n+\t\temit_line_0(o, context, 0, reset, '\\\\',\n \t\t\t    nneof, strlen(nneof));\n \t\tbreak;\n \tcase DIFF_SYMBOL_SUBMODULE_HEADER:\n@@ -1236,7 +1249,18 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \tcase DIFF_SYMBOL_CONTEXT:\n \t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n-\t\temit_line_ws_markup(o, set, reset, line, len, ' ',\n+\t\tset_sign = NULL;\n+\t\tif (o->flags.dual_color_diffed_diffs) {\n+\t\t\tchar c = !len ? 0 : line[0];\n+\n+\t\t\tif (c == '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n+\t\t\telse if (c == '@')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n+\t\t\telse if (c == '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n+\t\t}\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',\n \t\t\t\t    flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);\n \t\tbreak;\n \tcase DIFF_SYMBOL_PLUS:\n@@ -1263,7 +1287,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n \t\t}\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n-\t\temit_line_ws_markup(o, set, reset, line, len, '+',\n+\t\tif (!o->flags.dual_color_diffed_diffs)\n+\t\t\tset_sign = NULL;\n+\t\telse {\n+\t\t\tchar c = !len ? 0 : line[0];\n+\n+\t\t\tset_sign = set;\n+\t\t\tif (c == '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n+\t\t\telse if (c == '@')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n+\t\t\telse if (c != '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t}\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK,\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);\n \t\tbreak;\n@@ -1291,7 +1328,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n \t\t}\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n-\t\temit_line_ws_markup(o, set, reset, line, len, '-',\n+\t\tif (!o->flags.dual_color_diffed_diffs)\n+\t\t\tset_sign = NULL;\n+\t\telse {\n+\t\t\tchar c = !len ? 0 : line[0];\n+\n+\t\t\tset_sign = set;\n+\t\t\tif (c == '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n+\t\t\telse if (c == '@')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n+\t\t\telse if (c != '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t}\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '-',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);\n \t\tbreak;\n \tcase DIFF_SYMBOL_WORDS_PORCELAIN:\n@@ -1482,6 +1532,7 @@ static void emit_hunk_header(struct emit_callback *ecbdata,\n \tconst char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);\n \tconst char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);\n \tconst char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);\n+\tconst char *reverse = ecbdata->color_diff ? GIT_COLOR_REVERSE : \"\";\n \tstatic const char atat[2] = { '@', '@' };\n \tconst char *cp, *ep;\n \tstruct strbuf msgbuf = STRBUF_INIT;\n@@ -1502,6 +1553,8 @@ static void emit_hunk_header(struct emit_callback *ecbdata,\n \tep += 2; /* skip over @@ */\n \n \t/* The hunk header in fraginfo color */\n+\tif (ecbdata->opt->flags.dual_color_diffed_diffs)\n+\t\tstrbuf_addstr(&msgbuf, reverse);\n \tstrbuf_addstr(&msgbuf, frag);\n \tstrbuf_add(&msgbuf, line, ep - line);\n \tstrbuf_addstr(&msgbuf, reset);\ndiff --git a/diff.h b/diff.h\nindex d88ceb357..cca4f9d6c 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -95,6 +95,7 @@ struct diff_flags {\n \tunsigned default_follow_renames:1;\n \tunsigned stat_with_summary:1;\n \tunsigned suppress_diff_headers:1;\n+\tunsigned dual_color_diffed_diffs:1;\n };\n \n static inline void diff_flags_or(struct diff_flags *a,\n-- \ngitgitgadget\n\n"},{"id":"355186","messageId":"c32a24f6aa12950dbed6079160a3aa8b9e62dc4c.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 15/21] range-diff: offer to dual-color the diffs","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:47Z","receivedAt":"2018-08-10T22:14:51Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen showing what changed between old and new commits, we show a diff of\nthe patches. This diff is a diff between diffs, therefore there are\nnested +/- signs, and it can be relatively hard to understand what is\ngoing on.\n\nWith the --dual-color option, the preimage and the postimage are colored\nlike the diffs they are, and the *outer* +/- sign is inverted for\nclarity.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/range-diff.c | 8 ++++++++\n 1 file changed, 8 insertions(+)\n\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex bc7a2fb76..da3ad3eba 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -20,9 +20,12 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n {\n \tint creation_factor = 60;\n \tstruct diff_options diffopt = { NULL };\n+\tint dual_color = 0;\n \tstruct option options[] = {\n \t\tOPT_INTEGER(0, \"creation-factor\", &creation_factor,\n \t\t\t    N_(\"Percentage by which creation is weighted\")),\n+\t\tOPT_BOOL(0, \"dual-color\", &dual_color,\n+\t\t\t    N_(\"color both diff and diff-between-diffs\")),\n \t\tOPT_END()\n \t};\n \tint i, j, res = 0;\n@@ -58,6 +61,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\t\t     options + ARRAY_SIZE(options) - 1, /* OPT_END */\n \t\t\t     builtin_range_diff_usage, 0);\n \n+\tif (dual_color) {\n+\t\tdiffopt.use_color = 1;\n+\t\tdiffopt.flags.dual_color_diffed_diffs = 1;\n+\t}\n+\n \tif (argc == 2) {\n \t\tif (!strstr(argv[0], \"..\"))\n \t\t\tdie(_(\"no .. in range: '%s'\"), argv[0]);\n-- \ngitgitgadget\n\n"},{"id":"355187","messageId":"05947781f94a351b84dd189905b665d41df7b442.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 16/21] range-diff --dual-color: skip white-space warnings","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:49Z","receivedAt":"2018-08-10T22:14:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen displaying a diff of diffs, it is possible that there is an outer\n`+` before a context line. That happens when the context changed between\nold and new commit. When that context line starts with a tab (after the\nspace that marks it as context line), our diff machinery spits out a\nwhite-space error (space before tab), but in this case, that is\nincorrect.\n\nRather than adding a specific whitespace flag that specifically ignores\nthe first space in the output (and might miss other problems with the\nwhite-space warnings), let's just skip handling white-space errors in\ndual color mode to begin with.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n diff.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/diff.c b/diff.c\nindex e6c857abf..ea8ecae04 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -1299,6 +1299,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n \t\t\telse if (c != '+')\n \t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t\tflags &= ~DIFF_SYMBOL_CONTENT_WS_MASK;\n \t\t}\n \t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK,\n-- \ngitgitgadget\n\n"},{"id":"355188","messageId":"3147c4440c8eb3c4905d59f3564ee08375abede0.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 17/21] range-diff: populate the man page","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:50Z","receivedAt":"2018-08-10T22:14:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe bulk of this patch consists of a heavily butchered version of\ntbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from\nhttps://github.com/trast/tbdiff.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-range-diff.txt | 229 +++++++++++++++++++++++++++++++\n 1 file changed, 229 insertions(+)\n\ndiff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\nindex 49f717db8..bebb47d42 100644\n--- a/Documentation/git-range-diff.txt\n+++ b/Documentation/git-range-diff.txt\n@@ -5,6 +5,235 @@ NAME\n ----\n git-range-diff - Compare two commit ranges (e.g. two versions of a branch)\n \n+SYNOPSIS\n+--------\n+[verse]\n+'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]\n+\t[--dual-color] [--creation-factor=<factor>]\n+\t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n+\n+DESCRIPTION\n+-----------\n+\n+This command shows the differences between two versions of a patch\n+series, or more generally, two commit ranges (ignoring merge commits).\n+\n+To that end, it first finds pairs of commits from both commit ranges\n+that correspond with each other. Two commits are said to correspond when\n+the diff between their patches (i.e. the author information, the commit\n+message and the commit diff) is reasonably small compared to the\n+patches' size. See ``Algorithm`` below for details.\n+\n+Finally, the list of matching commits is shown in the order of the\n+second commit range, with unmatched commits being inserted just after\n+all of their ancestors have been shown.\n+\n+\n+OPTIONS\n+-------\n+--dual-color::\n+\tWhen the commit diffs differ, recreate the original diffs'\n+\tcoloring, and add outer -/+ diff markers with the *background*\n+\tbeing red/green to make it easier to see e.g. when there was a\n+\tchange in what exact lines were added.\n+\n+--creation-factor=<percent>::\n+\tSet the creation/deletion cost fudge factor to `<percent>`.\n+\tDefaults to 60. Try a larger value if `git range-diff` erroneously\n+\tconsiders a large change a total rewrite (deletion of one commit\n+\tand addition of another), and a smaller one in the reverse case.\n+\tSee the ``Algorithm`` section below for an explanation why this is\n+\tneeded.\n+\n+<range1> <range2>::\n+\tCompare the commits specified by the two ranges, where\n+\t`<range1>` is considered an older version of `<range2>`.\n+\n+<rev1>...<rev2>::\n+\tEquivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.\n+\n+<base> <rev1> <rev2>::\n+\tEquivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.\n+\tNote that `<base>` does not need to be the exact branch point\n+\tof the branches. Example: after rebasing a branch `my-topic`,\n+\t`git range-diff my-topic@{u} my-topic@{1} my-topic` would\n+\tshow the differences introduced by the rebase.\n+\n+`git range-diff` also accepts the regular diff options (see\n+linkgit:git-diff[1]), most notably the `--color=[<when>]` and\n+`--no-color` options. These options are used when generating the \"diff\n+between patches\", i.e. to compare the author, commit message and diff of\n+corresponding old/new commits. There is currently no means to tweak the\n+diff options passed to `git log` when generating those patches.\n+\n+\n+CONFIGURATION\n+-------------\n+This command uses the `diff.color.*` and `pager.range-diff` settings\n+(the latter is on by default).\n+See linkgit:git-config[1].\n+\n+\n+EXAMPLES\n+--------\n+\n+When a rebase required merge conflicts to be resolved, compare the changes\n+introduced by the rebase directly afterwards using:\n+\n+------------\n+$ git range-diff @{u} @{1} @\n+------------\n+\n+\n+A typical output of `git range-diff` would look like this:\n+\n+------------\n+-:  ------- > 1:  0ddba11 Prepare for the inevitable!\n+1:  c0debee = 2:  cab005e Add a helpful message at the start\n+2:  f00dbal ! 3:  decafe1 Describe a bug\n+    @@ -1,3 +1,3 @@\n+     Author: A U Thor <author@example.com>\n+\n+    -TODO: Describe a bug\n+    +Describe a bug\n+    @@ -324,5 +324,6\n+      This is expected.\n+\n+    -+What is unexpected is that it will also crash.\n+    ++Unexpectedly, it also crashes. This is a bug, and the jury is\n+    ++still out there how to fix it best. See ticket #314 for details.\n+\n+      Contact\n+3:  bedead < -:  ------- TO-UNDO\n+------------\n+\n+In this example, there are 3 old and 3 new commits, where the developer\n+removed the 3rd, added a new one before the first two, and modified the\n+commit message of the 2nd commit as well its diff.\n+\n+When the output goes to a terminal, it is color-coded by default, just\n+like regular `git diff`'s output. In addition, the first line (adding a\n+commit) is green, the last line (deleting a commit) is red, the second\n+line (with a perfect match) is yellow like the commit header of `git\n+show`'s output, and the third line colors the old commit red, the new\n+one green and the rest like `git show`'s commit header.\n+\n+The color-coded diff is actually a bit hard to read, though, as it\n+colors the entire lines red or green. The line that added \"What is\n+unexpected\" in the old commit, for example, is completely red, even if\n+the intent of the old commit was to add something.\n+\n+To help with that, use the `--dual-color` mode. In this mode, the diff\n+of diffs will retain the original diff colors, and prefix the lines with\n+-/+ markers that have their *background* red or green, to make it more\n+obvious that they describe how the diff itself changed.\n+\n+\n+Algorithm\n+---------\n+\n+The general idea is this: we generate a cost matrix between the commits\n+in both commit ranges, then solve the least-cost assignment.\n+\n+The cost matrix is populated thusly: for each pair of commits, both\n+diffs are generated and the \"diff of diffs\" is generated, with 3 context\n+lines, then the number of lines in that diff is used as cost.\n+\n+To avoid false positives (e.g. when a patch has been removed, and an\n+unrelated patch has been added between two iterations of the same patch\n+series), the cost matrix is extended to allow for that, by adding\n+fixed-cost entries for wholesale deletes/adds.\n+\n+Example: Let commits `1--2` be the first iteration of a patch series and\n+`A--C` the second iteration. Let's assume that `A` is a cherry-pick of\n+`2,` and `C` is a cherry-pick of `1` but with a small modification (say,\n+a fixed typo). Visualize the commits as a bipartite graph:\n+\n+------------\n+    1            A\n+\n+    2            B\n+\n+\t\t C\n+------------\n+\n+We are looking for a \"best\" explanation of the new series in terms of\n+the old one. We can represent an \"explanation\" as an edge in the graph:\n+\n+\n+------------\n+    1            A\n+\t       /\n+    2 --------'  B\n+\n+\t\t C\n+------------\n+\n+This explanation comes for \"free\" because there was no change. Similarly\n+`C` could be explained using `1`, but that comes at some cost c>0\n+because of the modification:\n+\n+------------\n+    1 ----.      A\n+\t  |    /\n+    2 ----+---'  B\n+\t  |\n+\t  `----- C\n+\t  c>0\n+------------\n+\n+In mathematical terms, what we are looking for is some sort of a minimum\n+cost bipartite matching; `1` is matched to `C` at some cost, etc. The\n+underlying graph is in fact a complete bipartite graph; the cost we\n+associate with every edge is the size of the diff between the two\n+commits' patches. To explain also new commits, we introduce dummy nodes\n+on both sides:\n+\n+------------\n+    1 ----.      A\n+\t  |    /\n+    2 ----+---'  B\n+\t  |\n+    o     `----- C\n+\t  c>0\n+    o            o\n+\n+    o            o\n+------------\n+\n+The cost of an edge `o--C` is the size of `C`'s diff, modified by a\n+fudge factor that should be smaller than 100%. The cost of an edge\n+`o--o` is free. The fudge factor is necessary because even if `1` and\n+`C` have nothing in common, they may still share a few empty lines and\n+such, possibly making the assignment `1--C`, `o--o` slightly cheaper\n+than `1--o`, `o--C` even if `1` and `C` have nothing in common. With the\n+fudge factor we require a much larger common part to consider patches as\n+corresponding.\n+\n+The overall time needed to compute this algorithm is the time needed to\n+compute n+m commit diffs and then n*m diffs of patches, plus the time\n+needed to compute the least-cost assigment between n and m diffs. Git\n+uses an implementation of the Jonker-Volgenant algorithm to solve the\n+assignment problem, which has cubic runtime complexity. The matching\n+found in this case will look like this:\n+\n+------------\n+    1 ----.      A\n+\t  |    /\n+    2 ----+---'  B\n+       .--+-----'\n+    o -'  `----- C\n+\t  c>0\n+    o ---------- o\n+\n+    o ---------- o\n+------------\n+\n+\n+SEE ALSO\n+--------\n+linkgit:git-log[1]\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\n-- \ngitgitgadget\n\n"},{"id":"355189","messageId":"b08e6d937d9516a6ad8f6325d9e28aa2d2a42eb0.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 18/21] completion: support `git range-diff`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:52Z","receivedAt":"2018-08-10T22:14:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nTab completion of `git range-diff` is very convenient, especially\ngiven that the revision arguments to specify the commit ranges to\ncompare are typically more complex than, say, what is normally passed\nto `git log`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n contrib/completion/git-completion.bash | 14 ++++++++++++++\n 1 file changed, 14 insertions(+)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 94c95516e..3d4ec3432 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1976,6 +1976,20 @@ _git_push ()\n \t__git_complete_remote_or_refspec\n }\n \n+_git_range_diff ()\n+{\n+\tcase \"$cur\" in\n+\t--*)\n+\t\t__gitcomp \"\n+\t\t\t--creation-factor= --dual-color\n+\t\t\t$__git_diff_common_options\n+\t\t\"\n+\t\treturn\n+\t\t;;\n+\tesac\n+\t__git_complete_revlist\n+}\n+\n _git_rebase ()\n {\n \t__git_find_repo_path\n-- \ngitgitgadget\n\n"},{"id":"355190","messageId":"19406283e3f30803b9c6f502a9e9a8820b6ede61.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 19/21] range-diff: left-pad patch numbers","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:53Z","receivedAt":"2018-08-10T22:14:56Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nAs pointed out by Elijah Newren, tbdiff has this neat little alignment\ntrick where it outputs the commit pairs with patch numbers that are\npadded to the maximal patch number's width:\n\n\t  1: cafedead =   1: acefade first patch\n\t[...]\n\t314: beefeada < 314: facecab up to PI!\n\nLet's do the same in range-diff, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 16 +++++++++-------\n 1 file changed, 9 insertions(+), 7 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex b1663da7c..b6b9abac2 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -259,6 +259,7 @@ static void get_correspondences(struct string_list *a, struct string_list *b,\n }\n \n static void output_pair_header(struct diff_options *diffopt,\n+\t\t\t       int patch_no_width,\n \t\t\t       struct strbuf *buf,\n \t\t\t       struct strbuf *dashes,\n \t\t\t       struct patch_util *a_util,\n@@ -295,9 +296,9 @@ static void output_pair_header(struct diff_options *diffopt,\n \tstrbuf_reset(buf);\n \tstrbuf_addstr(buf, status == '!' ? color_old : color);\n \tif (!a_util)\n-\t\tstrbuf_addf(buf, \"-:  %s \", dashes->buf);\n+\t\tstrbuf_addf(buf, \"%*s:  %s \", patch_no_width, \"-\", dashes->buf);\n \telse\n-\t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n+\t\tstrbuf_addf(buf, \"%*d:  %s \", patch_no_width, a_util->i + 1,\n \t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n \n \tif (status == '!')\n@@ -307,9 +308,9 @@ static void output_pair_header(struct diff_options *diffopt,\n \t\tstrbuf_addf(buf, \"%s%s\", color_reset, color_new);\n \n \tif (!b_util)\n-\t\tstrbuf_addf(buf, \" -:  %s\", dashes->buf);\n+\t\tstrbuf_addf(buf, \" %*s:  %s\", patch_no_width, \"-\", dashes->buf);\n \telse\n-\t\tstrbuf_addf(buf, \" %d:  %s\", b_util->i + 1,\n+\t\tstrbuf_addf(buf, \" %*d:  %s\", patch_no_width, b_util->i + 1,\n \t\t\t    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));\n \n \tcommit = lookup_commit_reference(the_repository, oid);\n@@ -357,6 +358,7 @@ static void output(struct string_list *a, struct string_list *b,\n \t\t   struct diff_options *diffopt)\n {\n \tstruct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;\n+\tint patch_no_width = decimal_width(1 + (a->nr > b->nr ? a->nr : b->nr));\n \tint i = 0, j = 0;\n \n \t/*\n@@ -378,7 +380,7 @@ static void output(struct string_list *a, struct string_list *b,\n \n \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n \t\tif (i < a->nr && a_util->matching < 0) {\n-\t\t\toutput_pair_header(diffopt,\n+\t\t\toutput_pair_header(diffopt, patch_no_width,\n \t\t\t\t\t   &buf, &dashes, a_util, NULL);\n \t\t\ti++;\n \t\t\tcontinue;\n@@ -386,7 +388,7 @@ static void output(struct string_list *a, struct string_list *b,\n \n \t\t/* Show unmatched RHS commits. */\n \t\twhile (j < b->nr && b_util->matching < 0) {\n-\t\t\toutput_pair_header(diffopt,\n+\t\t\toutput_pair_header(diffopt, patch_no_width,\n \t\t\t\t\t   &buf, &dashes, NULL, b_util);\n \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n@@ -394,7 +396,7 @@ static void output(struct string_list *a, struct string_list *b,\n \t\t/* Show matching LHS/RHS pair. */\n \t\tif (j < b->nr) {\n \t\t\ta_util = a->items[b_util->matching].util;\n-\t\t\toutput_pair_header(diffopt,\n+\t\t\toutput_pair_header(diffopt, patch_no_width,\n \t\t\t\t\t   &buf, &dashes, a_util, b_util);\n \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n-- \ngitgitgadget\n\n"},{"id":"355191","messageId":"6b3552386fc86dbfe3eeb7fc34155854191e7648.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 20/21] range-diff: make --dual-color the default mode","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:55Z","receivedAt":"2018-08-10T22:14:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nAfter using this command extensively for the last two months, this\ndeveloper came to the conclusion that even if the dual color mode still\nleaves a lot of room for confusion about what was actually changed, the\nnon-dual color mode is substantially worse in that regard.\n\nTherefore, we really want to make the dual color mode the default.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-range-diff.txt       | 32 +++++++++++++++-----------\n builtin/range-diff.c                   | 10 ++++----\n contrib/completion/git-completion.bash |  2 +-\n 3 files changed, 25 insertions(+), 19 deletions(-)\n\ndiff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\nindex bebb47d42..82c71c682 100644\n--- a/Documentation/git-range-diff.txt\n+++ b/Documentation/git-range-diff.txt\n@@ -9,7 +9,7 @@ SYNOPSIS\n --------\n [verse]\n 'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]\n-\t[--dual-color] [--creation-factor=<factor>]\n+\t[--no-dual-color] [--creation-factor=<factor>]\n \t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n \n DESCRIPTION\n@@ -31,11 +31,14 @@ all of their ancestors have been shown.\n \n OPTIONS\n -------\n---dual-color::\n-\tWhen the commit diffs differ, recreate the original diffs'\n-\tcoloring, and add outer -/+ diff markers with the *background*\n-\tbeing red/green to make it easier to see e.g. when there was a\n-\tchange in what exact lines were added.\n+--no-dual-color::\n+\tWhen the commit diffs differ, `git range-diff` recreates the\n+\toriginal diffs' coloring, and adds outer -/+ diff markers with\n+\tthe *background* being red/green to make it easier to see e.g.\n+\twhen there was a change in what exact lines were added. This is\n+\tknown to `range-diff` as \"dual coloring\". Use `--no-dual-color`\n+\tto revert to color all lines according to the outer diff markers\n+\t(and completely ignore the inner diff when it comes to color).\n \n --creation-factor=<percent>::\n \tSet the creation/deletion cost fudge factor to `<percent>`.\n@@ -118,15 +121,16 @@ line (with a perfect match) is yellow like the commit header of `git\n show`'s output, and the third line colors the old commit red, the new\n one green and the rest like `git show`'s commit header.\n \n-The color-coded diff is actually a bit hard to read, though, as it\n-colors the entire lines red or green. The line that added \"What is\n-unexpected\" in the old commit, for example, is completely red, even if\n-the intent of the old commit was to add something.\n+A naive color-coded diff of diffs is actually a bit hard to read,\n+though, as it colors the entire lines red or green. The line that added\n+\"What is unexpected\" in the old commit, for example, is completely red,\n+even if the intent of the old commit was to add something.\n \n-To help with that, use the `--dual-color` mode. In this mode, the diff\n-of diffs will retain the original diff colors, and prefix the lines with\n--/+ markers that have their *background* red or green, to make it more\n-obvious that they describe how the diff itself changed.\n+To help with that, `range` uses the `--dual-color` mode by default. In\n+this mode, the diff of diffs will retain the original diff colors, and\n+prefix the lines with -/+ markers that have their *background* red or\n+green, to make it more obvious that they describe how the diff itself\n+changed.\n \n \n Algorithm\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex da3ad3eba..ef3ba22e2 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -20,11 +20,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n {\n \tint creation_factor = 60;\n \tstruct diff_options diffopt = { NULL };\n-\tint dual_color = 0;\n+\tint simple_color = -1;\n \tstruct option options[] = {\n \t\tOPT_INTEGER(0, \"creation-factor\", &creation_factor,\n \t\t\t    N_(\"Percentage by which creation is weighted\")),\n-\t\tOPT_BOOL(0, \"dual-color\", &dual_color,\n+\t\tOPT_BOOL(0, \"no-dual-color\", &simple_color,\n \t\t\t    N_(\"color both diff and diff-between-diffs\")),\n \t\tOPT_END()\n \t};\n@@ -61,8 +61,10 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\t\t     options + ARRAY_SIZE(options) - 1, /* OPT_END */\n \t\t\t     builtin_range_diff_usage, 0);\n \n-\tif (dual_color) {\n-\t\tdiffopt.use_color = 1;\n+\tif (simple_color < 1) {\n+\t\tif (!simple_color)\n+\t\t\t/* force color when --dual-color was used */\n+\t\t\tdiffopt.use_color = 1;\n \t\tdiffopt.flags.dual_color_diffed_diffs = 1;\n \t}\n \ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 3d4ec3432..d63d2dffd 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1981,7 +1981,7 @@ _git_range_diff ()\n \tcase \"$cur\" in\n \t--*)\n \t\t__gitcomp \"\n-\t\t\t--creation-factor= --dual-color\n+\t\t\t--creation-factor= --no-dual-color\n \t\t\t$__git_diff_common_options\n \t\t\"\n \t\treturn\n-- \ngitgitgadget\n\n"},{"id":"355192","messageId":"ccf8c1bb2459d33c7dc97098c08c47ca7d77ed3e.1533939264.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 21/21] range-diff: use dim/bold cues to improve dual color mode","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-10T22:14:56Z","receivedAt":"2018-08-10T22:15:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nIt *is* a confusing thing to look at a diff of diffs. All too easy is it\nto mix up whether the -/+ markers refer to the \"inner\" or the \"outer\"\ndiff, i.e. whether a `+` indicates that a line was added by either the\nold or the new diff (or both), or whether the new diff does something\ndifferent than the old diff.\n\nTo make things easier to process for normal developers, we introduced\nthe dual color mode which colors the lines according to the commit diff,\ni.e. lines that are added by a commit (whether old, new, or both) are\ncolored in green. In non-dual color mode, the lines would be colored\naccording to the outer diff: if the old commit added a line, it would be\ncolored red (because that line addition is only present in the first\ncommit range that was specified on the command-line, i.e. the \"old\"\ncommit, but not in the second commit range, i.e. the \"new\" commit).\n\nHowever, this dual color mode is still not making things clear enough,\nas we are looking at two levels of diffs, and we still only pick a color\naccording to *one* of them (the outer diff marker is colored\ndifferently, of course, but in particular with deep indentation, it is\neasy to lose track of that outer diff marker's background color).\n\nTherefore, let's add another dimension to the mix. Still use\ngreen/red/normal according to the commit diffs, but now also dim the\nlines that were only in the old commit, and use bold face for the lines\nthat are only in the new commit.\n\nThat way, it is much easier not to lose track of, say, when we are\nlooking at a line that was added in the previous iteration of a patch\nseries but the new iteration adds a slightly different version: the\nobsolete change will be dimmed, the current version of the patch will be\nbold.\n\nAt least this developer has a much easier time reading the range-diffs\nthat way.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/config.txt         |  6 ++++--\n Documentation/git-range-diff.txt | 17 +++++++++++++----\n color.h                          |  6 ++++++\n diff.c                           | 28 ++++++++++++++++++++++------\n diff.h                           |  8 +++++++-\n 5 files changed, 52 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 63365dcf3..90241ed77 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1193,8 +1193,10 @@ color.diff.<slot>::\n \t(highlighting whitespace errors), `oldMoved` (deleted lines),\n \t`newMoved` (added lines), `oldMovedDimmed`, `oldMovedAlternative`,\n \t`oldMovedAlternativeDimmed`, `newMovedDimmed`, `newMovedAlternative`\n-\tand `newMovedAlternativeDimmed` (See the '<mode>'\n-\tsetting of '--color-moved' in linkgit:git-diff[1] for details).\n+\t`newMovedAlternativeDimmed` (See the '<mode>'\n+\tsetting of '--color-moved' in linkgit:git-diff[1] for details),\n+\t`contextDimmed`, `oldDimmed`, `newDimmed`, `contextBold`,\n+\t`oldBold`, and `newBold` (see linkgit:git-range-diff[1] for details).\n \n color.decorate.<slot>::\n \tUse customized color for 'git log --decorate' output.  `<slot>` is one\ndiff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\nindex 82c71c682..f693930fd 100644\n--- a/Documentation/git-range-diff.txt\n+++ b/Documentation/git-range-diff.txt\n@@ -35,10 +35,19 @@ OPTIONS\n \tWhen the commit diffs differ, `git range-diff` recreates the\n \toriginal diffs' coloring, and adds outer -/+ diff markers with\n \tthe *background* being red/green to make it easier to see e.g.\n-\twhen there was a change in what exact lines were added. This is\n-\tknown to `range-diff` as \"dual coloring\". Use `--no-dual-color`\n-\tto revert to color all lines according to the outer diff markers\n-\t(and completely ignore the inner diff when it comes to color).\n+\twhen there was a change in what exact lines were added.\n++\n+Additionally, the commit diff lines that are only present in the first commit\n+range are shown \"dimmed\" (this can be overridden using the `color.diff.<slot>`\n+config setting where `<slot>` is one of `contextDimmed`, `oldDimmed` and\n+`newDimmed`), and the commit diff lines that are only present in the second\n+commit range are shown in bold (which can be overridden using the config\n+settings `color.diff.<slot>` with `<slot>` being one of `contextBold`,\n+`oldBold` or `newBold`).\n++\n+This is known to `range-diff` as \"dual coloring\". Use `--no-dual-color`\n+to revert to color all lines according to the outer diff markers\n+(and completely ignore the inner diff when it comes to color).\n \n --creation-factor=<percent>::\n \tSet the creation/deletion cost fudge factor to `<percent>`.\ndiff --git a/color.h b/color.h\nindex 33e786342..98894d6a1 100644\n--- a/color.h\n+++ b/color.h\n@@ -36,6 +36,12 @@ struct strbuf;\n #define GIT_COLOR_BOLD_BLUE\t\"\\033[1;34m\"\n #define GIT_COLOR_BOLD_MAGENTA\t\"\\033[1;35m\"\n #define GIT_COLOR_BOLD_CYAN\t\"\\033[1;36m\"\n+#define GIT_COLOR_FAINT_RED\t\"\\033[2;31m\"\n+#define GIT_COLOR_FAINT_GREEN\t\"\\033[2;32m\"\n+#define GIT_COLOR_FAINT_YELLOW\t\"\\033[2;33m\"\n+#define GIT_COLOR_FAINT_BLUE\t\"\\033[2;34m\"\n+#define GIT_COLOR_FAINT_MAGENTA\t\"\\033[2;35m\"\n+#define GIT_COLOR_FAINT_CYAN\t\"\\033[2;36m\"\n #define GIT_COLOR_BG_RED\t\"\\033[41m\"\n #define GIT_COLOR_BG_GREEN\t\"\\033[42m\"\n #define GIT_COLOR_BG_YELLOW\t\"\\033[43m\"\ndiff --git a/diff.c b/diff.c\nindex ea8ecae04..ae1314952 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -70,6 +70,12 @@ static char diff_colors[][COLOR_MAXLEN] = {\n \tGIT_COLOR_BOLD_YELLOW,\t/* NEW_MOVED ALTERNATIVE */\n \tGIT_COLOR_FAINT,\t/* NEW_MOVED_DIM */\n \tGIT_COLOR_FAINT_ITALIC,\t/* NEW_MOVED_ALTERNATIVE_DIM */\n+\tGIT_COLOR_FAINT,\t/* CONTEXT_DIM */\n+\tGIT_COLOR_FAINT_RED,\t/* OLD_DIM */\n+\tGIT_COLOR_FAINT_GREEN,\t/* NEW_DIM */\n+\tGIT_COLOR_BOLD,\t\t/* CONTEXT_BOLD */\n+\tGIT_COLOR_BOLD_RED,\t/* OLD_BOLD */\n+\tGIT_COLOR_BOLD_GREEN,\t/* NEW_BOLD */\n };\n \n static const char *color_diff_slots[] = {\n@@ -89,6 +95,12 @@ static const char *color_diff_slots[] = {\n \t[DIFF_FILE_NEW_MOVED_ALT]     = \"newMovedAlternative\",\n \t[DIFF_FILE_NEW_MOVED_DIM]     = \"newMovedDimmed\",\n \t[DIFF_FILE_NEW_MOVED_ALT_DIM] = \"newMovedAlternativeDimmed\",\n+\t[DIFF_CONTEXT_DIM]\t      = \"contextDimmed\",\n+\t[DIFF_FILE_OLD_DIM]\t      = \"oldDimmed\",\n+\t[DIFF_FILE_NEW_DIM]\t      = \"newDimmed\",\n+\t[DIFF_CONTEXT_BOLD]\t      = \"contextBold\",\n+\t[DIFF_FILE_OLD_BOLD]\t      = \"oldBold\",\n+\t[DIFF_FILE_NEW_BOLD]\t      = \"newBold\",\n };\n \n static NORETURN void die_want_option(const char *option_name)\n@@ -1294,11 +1306,13 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \n \t\t\tset_sign = set;\n \t\t\tif (c == '-')\n-\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD_BOLD);\n \t\t\telse if (c == '@')\n \t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n-\t\t\telse if (c != '+')\n-\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t\telse if (c == '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW_BOLD);\n+\t\t\telse\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT_BOLD);\n \t\t\tflags &= ~DIFF_SYMBOL_CONTENT_WS_MASK;\n \t\t}\n \t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n@@ -1336,11 +1350,13 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \n \t\t\tset_sign = set;\n \t\t\tif (c == '+')\n-\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW_DIM);\n \t\t\telse if (c == '@')\n \t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n-\t\t\telse if (c != '-')\n-\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t\telse if (c == '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD_DIM);\n+\t\t\telse\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT_DIM);\n \t\t}\n \t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '-',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);\ndiff --git a/diff.h b/diff.h\nindex cca4f9d6c..e1e54256c 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -248,7 +248,13 @@ enum color_diff {\n \tDIFF_FILE_NEW_MOVED = 13,\n \tDIFF_FILE_NEW_MOVED_ALT = 14,\n \tDIFF_FILE_NEW_MOVED_DIM = 15,\n-\tDIFF_FILE_NEW_MOVED_ALT_DIM = 16\n+\tDIFF_FILE_NEW_MOVED_ALT_DIM = 16,\n+\tDIFF_CONTEXT_DIM = 17,\n+\tDIFF_FILE_OLD_DIM = 18,\n+\tDIFF_FILE_NEW_DIM = 19,\n+\tDIFF_CONTEXT_BOLD = 20,\n+\tDIFF_FILE_OLD_BOLD = 21,\n+\tDIFF_FILE_NEW_BOLD = 22,\n };\n const char *diff_get_color(int diff_use_color, enum color_diff ix);\n #define diff_get_color_opt(o, ix) \\\n-- \ngitgitgadget\n"},{"id":"355301","messageId":"20180812214741.GB13316@hank.intra.tgummerer.com","threadId":"48405","inReplyTo":"9e1e660077d41c479ae46eb07371204c01dff4cd.1533939264.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 05/21] range-diff: also show the diff between patches","fromName":"Thomas Gummerer","fromEmail":"t.gummerer@gmail.com","sentAt":"2018-08-12T21:47:41Z","receivedAt":"2018-08-12T21:47:48Z","isPatch":true,"sender":{"key":"t.gummerer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/191004?v=4"},"body":"Hi Dscho,\n\nOn 08/10, Johannes Schindelin via GitGitGadget wrote:\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n>\n> [..]\n> \n> @@ -13,15 +14,38 @@ NULL\n>  int cmd_range_diff(int argc, const char **argv, const char *prefix)\n>  {\n>  \tint creation_factor = 60;\n> +\tstruct diff_options diffopt = { NULL };\n>  \tstruct option options[] = {\n>  \t\tOPT_INTEGER(0, \"creation-factor\", &creation_factor,\n>  \t\t\t    N_(\"Percentage by which creation is weighted\")),\n>  \t\tOPT_END()\n>  \t};\n> -\tint res = 0;\n> +\tint i, j, res = 0;\n>  \tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n>  \n> +\tgit_config(git_diff_ui_config, NULL);\n> +\n> +\tdiff_setup(&diffopt);\n> +\tdiffopt.output_format = DIFF_FORMAT_PATCH;\n> +\n>  \targc = parse_options(argc, argv, NULL, options,\n> +\t\t\t     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN |\n> +\t\t\t     PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);\n> +\n> +\tfor (i = j = 1; i < argc && strcmp(\"--\", argv[i]); ) {\n> +\t\tint c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);\n> +\n> +\t\tif (!c)\n> +\t\t\targv[j++] = argv[i++];\n> +\t\telse\n> +\t\t\ti += c;\n> +\t}\n\nI don't think this handles \"--\" quite as would be expected.  Trying to\nuse \"git range-diff -- js/range-diff-v4...HEAD\" I get:\n\n    $ ./git range-diff -- js/range-diff-v4...HEAD\n    error: need two commit ranges\n    usage: git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\n       or: git range-diff [<options>] <old-tip>...<new-tip>\n       or: git range-diff [<options>] <base> <old-tip> <new-tip>\n\n        --creation-factor <n>\n                              Percentage by which creation is weighted\n        --no-dual-color       color both diff and diff-between-diffs\n\nwhile what I would have expected is to actually get a range diff.\nThis happens because after we break out of the loop we don't add the\nactual ranges to argv, but just skip them instead.\n\nI think something like the following should be squashed in to this\npatch.\n\n--->8---\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex ef3ba22e29..132574c57a 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -53,6 +53,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n                else\n                        i += c;\n        }\n+       if (i < argc && !strcmp(\"--\", argv[i])) {\n+               i++; j++;\n+               while (i < argc)\n+                       argv[j++] = argv[i++];\n+       }\n        argc = j;\n        diff_setup_done(&diffopt);\n \n--->8---\n\n> +\targc = j;\n> +\tdiff_setup_done(&diffopt);\n> +\n> +\t/* Make sure that there are no unparsed options */\n> +\targc = parse_options(argc, argv, NULL,\n> +\t\t\t     options + ARRAY_SIZE(options) - 1, /* OPT_END */\n>  \t\t\t     builtin_range_diff_usage, 0);\n>  \n>  \tif (argc == 2) {\n> @@ -59,7 +83,8 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n>  \t\tusage_with_options(builtin_range_diff_usage, options);\n>  \t}\n>  \n> -\tres = show_range_diff(range1.buf, range2.buf, creation_factor);\n> +\tres = show_range_diff(range1.buf, range2.buf, creation_factor,\n> +\t\t\t      &diffopt);\n>  \n>  \tstrbuf_release(&range1);\n>  \tstrbuf_release(&range2);\n> diff --git a/range-diff.c b/range-diff.c\n> index 2d94200d3..71883a4b7 100644\n> --- a/range-diff.c\n> +++ b/range-diff.c\n> @@ -6,6 +6,7 @@\n>  #include \"hashmap.h\"\n>  #include \"xdiff-interface.h\"\n>  #include \"linear-assignment.h\"\n> +#include \"diffcore.h\"\n>  \n>  struct patch_util {\n>  \t/* For the search for an exact match */\n> @@ -258,7 +259,31 @@ static const char *short_oid(struct patch_util *util)\n>  \treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n>  }\n>  \n> -static void output(struct string_list *a, struct string_list *b)\n> +static struct diff_filespec *get_filespec(const char *name, const char *p)\n> +{\n> +\tstruct diff_filespec *spec = alloc_filespec(name);\n> +\n> +\tfill_filespec(spec, &null_oid, 0, 0644);\n> +\tspec->data = (char *)p;\n> +\tspec->size = strlen(p);\n> +\tspec->should_munmap = 0;\n> +\tspec->is_stdin = 1;\n> +\n> +\treturn spec;\n> +}\n> +\n> +static void patch_diff(const char *a, const char *b,\n> +\t\t\t      struct diff_options *diffopt)\n> +{\n> +\tdiff_queue(&diff_queued_diff,\n> +\t\t   get_filespec(\"a\", a), get_filespec(\"b\", b));\n> +\n> +\tdiffcore_std(diffopt);\n> +\tdiff_flush(diffopt);\n> +}\n> +\n> +static void output(struct string_list *a, struct string_list *b,\n> +\t\t   struct diff_options *diffopt)\n>  {\n>  \tint i = 0, j = 0;\n>  \n> @@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)\n>  \t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n>  \t\t\t       b_util->matching + 1, short_oid(a_util),\n>  \t\t\t       j + 1, short_oid(b_util));\n> +\t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n> +\t\t\t\tpatch_diff(a->items[b_util->matching].string,\n> +\t\t\t\t\t   b->items[j].string, diffopt);\n>  \t\t\ta_util->shown = 1;\n>  \t\t\tj++;\n>  \t\t}\n> @@ -307,7 +335,7 @@ static void output(struct string_list *a, struct string_list *b)\n>  }\n>  \n>  int show_range_diff(const char *range1, const char *range2,\n> -\t\t    int creation_factor)\n> +\t\t    int creation_factor, struct diff_options *diffopt)\n>  {\n>  \tint res = 0;\n>  \n> @@ -322,7 +350,7 @@ int show_range_diff(const char *range1, const char *range2,\n>  \tif (!res) {\n>  \t\tfind_exact_matches(&branch1, &branch2);\n>  \t\tget_correspondences(&branch1, &branch2, creation_factor);\n> -\t\toutput(&branch1, &branch2);\n> +\t\toutput(&branch1, &branch2, diffopt);\n>  \t}\n>  \n>  \tstring_list_clear(&branch1, 1);\n> diff --git a/range-diff.h b/range-diff.h\n> index 7b6eef303..2407d46a3 100644\n> --- a/range-diff.h\n> +++ b/range-diff.h\n> @@ -1,7 +1,9 @@\n>  #ifndef RANGE_DIFF_H\n>  #define RANGE_DIFF_H\n>  \n> +#include \"diff.h\"\n> +\n>  int show_range_diff(const char *range1, const char *range2,\n> -\t\t    int creation_factor);\n> +\t\t    int creation_factor, struct diff_options *diffopt);\n>  \n>  #endif\n> -- \n> gitgitgadget\n> \n"},{"id":"355318","messageId":"nycvar.QRO.7.76.6.1808131135290.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180812214741.GB13316@hank.intra.tgummerer.com","subject":"Re: [PATCH v5 05/21] range-diff: also show the diff between patches","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-08-13T09:46:18Z","receivedAt":"2018-08-13T09:46:20Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Thomas,\n\nOn Sun, 12 Aug 2018, Thomas Gummerer wrote:\n\n> On 08/10, Johannes Schindelin via GitGitGadget wrote:\n> > From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> >\n> > [..]\n> > \n> > @@ -13,15 +14,38 @@ NULL\n> >  int cmd_range_diff(int argc, const char **argv, const char *prefix)\n> >  {\n> >  \tint creation_factor = 60;\n> > +\tstruct diff_options diffopt = { NULL };\n> >  \tstruct option options[] = {\n> >  \t\tOPT_INTEGER(0, \"creation-factor\", &creation_factor,\n> >  \t\t\t    N_(\"Percentage by which creation is weighted\")),\n> >  \t\tOPT_END()\n> >  \t};\n> > -\tint res = 0;\n> > +\tint i, j, res = 0;\n> >  \tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n> >  \n> > +\tgit_config(git_diff_ui_config, NULL);\n> > +\n> > +\tdiff_setup(&diffopt);\n> > +\tdiffopt.output_format = DIFF_FORMAT_PATCH;\n> > +\n> >  \targc = parse_options(argc, argv, NULL, options,\n> > +\t\t\t     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN |\n> > +\t\t\t     PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);\n> > +\n> > +\tfor (i = j = 1; i < argc && strcmp(\"--\", argv[i]); ) {\n> > +\t\tint c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);\n> > +\n> > +\t\tif (!c)\n> > +\t\t\targv[j++] = argv[i++];\n> > +\t\telse\n> > +\t\t\ti += c;\n> > +\t}\n> \n> I don't think this handles \"--\" quite as would be expected.  Trying to\n> use \"git range-diff -- js/range-diff-v4...HEAD\" I get:\n> \n>     $ ./git range-diff -- js/range-diff-v4...HEAD\n>     error: need two commit ranges\n>     usage: git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\n>        or: git range-diff [<options>] <old-tip>...<new-tip>\n>        or: git range-diff [<options>] <base> <old-tip> <new-tip>\n> \n>         --creation-factor <n>\n>                               Percentage by which creation is weighted\n>         --no-dual-color       color both diff and diff-between-diffs\n> \n> while what I would have expected is to actually get a range diff.\n> This happens because after we break out of the loop we don't add the\n> actual ranges to argv, but just skip them instead.\n\nOuch, good point.\n\n> I think something like the following should be squashed in to this\n> patch.\n> \n> --->8---\n> diff --git a/builtin/range-diff.c b/builtin/range-diff.c\n> index ef3ba22e29..132574c57a 100644\n> --- a/builtin/range-diff.c\n> +++ b/builtin/range-diff.c\n> @@ -53,6 +53,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n>                 else\n>                         i += c;\n>         }\n> +       if (i < argc && !strcmp(\"--\", argv[i])) {\n> +               i++; j++;\n> +               while (i < argc)\n> +                       argv[j++] = argv[i++];\n> +       }\n>         argc = j;\n>         diff_setup_done(&diffopt);\n\nI do not think that is correct. The original idea was for the first\n`parse_options()` call to keep the dashdash, for the second one to keep\nthe dashdash, too, and for the final one to swallow it.\n\nAlso, if `i < argc` at this point, we already know that `argv[i]` refers\nto the dashdash, otherwise the previous loop would not have exited early.\n\nI went with this simple version instead:\n\n\twhile (i < argc)\n\t\targv[j++] = argv[i++];\n\nThanks!\nDscho\n\n> --->8---\n> \n> > +\targc = j;\n> > +\tdiff_setup_done(&diffopt);\n> > +\n> > +\t/* Make sure that there are no unparsed options */\n> > +\targc = parse_options(argc, argv, NULL,\n> > +\t\t\t     options + ARRAY_SIZE(options) - 1, /* OPT_END */\n> >  \t\t\t     builtin_range_diff_usage, 0);\n> >  \n> >  \tif (argc == 2) {\n> > @@ -59,7 +83,8 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n> >  \t\tusage_with_options(builtin_range_diff_usage, options);\n> >  \t}\n> >  \n> > -\tres = show_range_diff(range1.buf, range2.buf, creation_factor);\n> > +\tres = show_range_diff(range1.buf, range2.buf, creation_factor,\n> > +\t\t\t      &diffopt);\n> >  \n> >  \tstrbuf_release(&range1);\n> >  \tstrbuf_release(&range2);\n> > diff --git a/range-diff.c b/range-diff.c\n> > index 2d94200d3..71883a4b7 100644\n> > --- a/range-diff.c\n> > +++ b/range-diff.c\n> > @@ -6,6 +6,7 @@\n> >  #include \"hashmap.h\"\n> >  #include \"xdiff-interface.h\"\n> >  #include \"linear-assignment.h\"\n> > +#include \"diffcore.h\"\n> >  \n> >  struct patch_util {\n> >  \t/* For the search for an exact match */\n> > @@ -258,7 +259,31 @@ static const char *short_oid(struct patch_util *util)\n> >  \treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n> >  }\n> >  \n> > -static void output(struct string_list *a, struct string_list *b)\n> > +static struct diff_filespec *get_filespec(const char *name, const char *p)\n> > +{\n> > +\tstruct diff_filespec *spec = alloc_filespec(name);\n> > +\n> > +\tfill_filespec(spec, &null_oid, 0, 0644);\n> > +\tspec->data = (char *)p;\n> > +\tspec->size = strlen(p);\n> > +\tspec->should_munmap = 0;\n> > +\tspec->is_stdin = 1;\n> > +\n> > +\treturn spec;\n> > +}\n> > +\n> > +static void patch_diff(const char *a, const char *b,\n> > +\t\t\t      struct diff_options *diffopt)\n> > +{\n> > +\tdiff_queue(&diff_queued_diff,\n> > +\t\t   get_filespec(\"a\", a), get_filespec(\"b\", b));\n> > +\n> > +\tdiffcore_std(diffopt);\n> > +\tdiff_flush(diffopt);\n> > +}\n> > +\n> > +static void output(struct string_list *a, struct string_list *b,\n> > +\t\t   struct diff_options *diffopt)\n> >  {\n> >  \tint i = 0, j = 0;\n> >  \n> > @@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)\n> >  \t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n> >  \t\t\t       b_util->matching + 1, short_oid(a_util),\n> >  \t\t\t       j + 1, short_oid(b_util));\n> > +\t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n> > +\t\t\t\tpatch_diff(a->items[b_util->matching].string,\n> > +\t\t\t\t\t   b->items[j].string, diffopt);\n> >  \t\t\ta_util->shown = 1;\n> >  \t\t\tj++;\n> >  \t\t}\n> > @@ -307,7 +335,7 @@ static void output(struct string_list *a, struct string_list *b)\n> >  }\n> >  \n> >  int show_range_diff(const char *range1, const char *range2,\n> > -\t\t    int creation_factor)\n> > +\t\t    int creation_factor, struct diff_options *diffopt)\n> >  {\n> >  \tint res = 0;\n> >  \n> > @@ -322,7 +350,7 @@ int show_range_diff(const char *range1, const char *range2,\n> >  \tif (!res) {\n> >  \t\tfind_exact_matches(&branch1, &branch2);\n> >  \t\tget_correspondences(&branch1, &branch2, creation_factor);\n> > -\t\toutput(&branch1, &branch2);\n> > +\t\toutput(&branch1, &branch2, diffopt);\n> >  \t}\n> >  \n> >  \tstring_list_clear(&branch1, 1);\n> > diff --git a/range-diff.h b/range-diff.h\n> > index 7b6eef303..2407d46a3 100644\n> > --- a/range-diff.h\n> > +++ b/range-diff.h\n> > @@ -1,7 +1,9 @@\n> >  #ifndef RANGE_DIFF_H\n> >  #define RANGE_DIFF_H\n> >  \n> > +#include \"diff.h\"\n> > +\n> >  int show_range_diff(const char *range1, const char *range2,\n> > -\t\t    int creation_factor);\n> > +\t\t    int creation_factor, struct diff_options *diffopt);\n> >  \n> >  #endif\n> > -- \n> > gitgitgadget\n> > \n> \n"},{"id":"355323","messageId":"pull.1.v6.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v6 00/21] Add range-diff, a tbdiff lookalike","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:32:59Z","receivedAt":"2018-08-13T11:33:03Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The incredibly useful git-tbdiff [https://github.com/trast/tbdiff] tool to\ncompare patch series (say, to see what changed between two iterations sent\nto the Git mailing list) is slightly less useful for this developer due to\nthe fact that it requires the hungarian and numpy Python packages which are\nfor some reason really hard to build in MSYS2. So hard that I even had to\ngive up, because it was simply easier to re-implement the whole shebang as a\nbuiltin command.\n\nThe project at https://github.com/trast/tbdiff seems to be dormant, anyway.\nFunny (and true) story: I looked at the open Pull Requests to see how active\nthat project is, only to find to my surprise that I had submitted one in\nAugust 2015, and that it was still unanswered let alone merged.\n\nWhile at it, I forward-ported AEvar's patch to force --decorate=no because \ngit -p tbdiff would fail otherwise.\n\nSide note: I work on implementing range-diff not only to make life easier\nfor reviewers who have to suffer through v2, v3, ... of my patch series, but\nalso to verify my changes before submitting a new iteration. And also, maybe\neven more importantly, I plan to use it to verify my merging-rebases of Git\nfor Windows (for which I previously used to redirect the\npre-rebase/post-rebase diffs vs upstream and then compare them using git\ndiff --no-index). And of course any interested person can see what changes\nwere necessary e.g. in the merging-rebase of Git for Windows onto v2.17.0 by\nrunning a command like:\n\n        base=^{/Start.the.merging-rebase}\n        tag=v2.17.0.windows.1\n        pre=$tag$base^2\n        git range-diff $pre$base..$pre $tag$base..$tag\n\nThe command uses what it calls the \"dual color mode\" (can be disabled via \n--no-dual-color) which helps identifying what actually changed: it prefixes\nlines with a - (and red background) that correspond to the first commit\nrange, and with a + (and green background) that correspond to the second\nrange. The rest of the lines will be colored according to the original\ndiffs.\n\nChanges since v4:\n\n * Fixed a typo in the commit message of \"range-diff: add tests\" that was\n   introduced in v4.\n * White-space fixes.\n * Fixed the length of the first header underline in the man page.\n * Changed the preprocessor guard in linear-assignment.h to reflect the new\n   name (instead of the old name, which was hungarian.h).\n * Likewise, changed the preprocessor guards in range-diff.h to hide the\n   history of the thrice-renamed command.\n * Fixed indentation in the completion.\n * Instead of trying to paper over white-space error handling that does not\n   apply to \"diffs of diffs\", dual color mode now simply disables all\n   white-space warnings.\n * When showing the \"single arg must be symmetric range\" error message, git\n   range-diff now also shows the usage.\n * Adjusted the commit message of \"range-diff: adjust the output of the\n   commit pairs\" to avoid the surprise of the reviewer when onelines are\n   printed all of a sudden, too.\n * \"range-diff: adjust the output of the commit pairs\" is now using a\n   simpler way to print onelines.\n * We are now sandwiching the diff_opt_parse() loop between two \n   parse_options(), to make sure that we caught all options, and that the -- \n   separator is handled.\n * Adjusted the lookup_commit_reference() call to the newest master (it now\n   takes a the_repository parameter).\n\nChanges since v3:\n\n * The cover letter was adjusted to reflect the new reality (the command is\n   called range-diff now, not branch-diff, and --dual-color is the default).\n * The documentation was adjusted a bit more in the patch that makes \n   --dual-color the default.\n * Clarified the calculation of the cost matrix, as per Stefan Beller's\n   request.\n * The man page now spells out that merge commits are ignored in the commit\n   ranges (not merges per se).\n * The code in linear-assignment.c was adjusted to use the SWAP() macro.\n * The commit message of the patch introducing the first rudimentary\n   implementation no longer talks about the \"Hungarian\" algorithm, but about\n   the \"linear assignment algorithm\" instead.\n * A bogus indentation change was backed out from the patch introducing the\n   first rudimentary implementation.\n * Instead of merely warning about missing .. in the 2-parameter invocation,\n   we now exit with the error message.\n * The diff_opt_parse() function is allowed to return a value larger than 1,\n   indicating that more than just one command-line parameter was parsed. We\n   now advance by the indicated value instead of always advancing exactly 1\n   (which is still correct much of the time).\n * A lengthy if...else if...else if...else was simplified (from a logical\n   point of view) by reordering it.\n * The unnecessarily static variable dashes was turned into a local variable\n   of the caller.\n * The commit message talking about the new man page still referred to git\n   branch --diff, which has been fixed.\n * A forgotten t7910 reference was changed to t3206.\n * An unbalanced double-tick was fixed in the man page.\n * Fixed grammar both of the commit message and the description of the \n   --no-dual-color option.\n * To fix the build, a blank man page is now introduced together with the\n   new range-diff command, even if it is populated for real only at a later\n   patch (i.e. at the same time as before).\n * The headaches Junio fears would be incurred by that simple workaround to\n   avoid bogus white-space error reporting are fended off: a more complex\n   patch is now in place that adds (and uses) a new white-space flag. Sadly,\n   as is all too common when Junio \"encourages\" me to replace a simple\n   workaround by something \"proper\", it caused all kinds of headaches to get\n   this right, so I am rather less certain that the \"proper\" fix will cause\n   us less headaches than the simple workaround would have done. But\n   whatever.\n * The dual color mode now also dims the changes that are exclusively in the\n   first specified commit range, and uses bold face on the changes\n   exclusively in the second one. This matches the intuition when using \n   range-diff to compare an older iteration of a patch series to a newer\n   one: the changes from the previous iteration that were replaced by new\n   ones \"fade\", while the changes that replace them are \"shiny new\".\n\nChanges since v2:\n\n * Right-aligned the patch numbers in the commit pairs.\n * Used ALLOC_ARRAY() in hungarian.c instead of xmalloc(sizeof()*size).\n * Changed compute_assignment()s return type from int to void, as it always\n   succeeds.\n * Changed the Hungarian Algorithm to use an integer cost matrix.\n * Changed the --creation-weight option to --creation-factor where is an\n   integer.\n * Retitled 1/19 and 2/19 to better conform with the current conventions, as\n   pointed out (and suggested) by Junio.\n * Shut up Coverity, and at the same time avoided passing the unnecessary i \n   and j parameters to output_pair_header().\n * Removed support for the --no-patches option: we inherit diff_options'\n   support for -s already (and much more).\n * Removed the ugly _INV enum values, and introduced a beautiful\n   GIT_COLOR_REVERSE instead. This way, whatever the user configured as\n   color.diff.new (or .old) will be used in reverse in the dual color mode.\n * Instead of overriding the fragment header color, the dual color mode will\n   now reverse the \"outer\" fragment headers, too.\n * Turned the stand-alone branch-diff command into the --diff option of git\n   branch. Adjusted pretty much all commit messages to account for this.\n   This change should no longer be visible: see below.\n * Pretty much re-wrote the completion, to support the new --diff mode of\n   git-branch. See below: it was reverted for range-diff.\n * Renamed t7910 to t3206, to be closer to the git-branch tests.\n * Ensured that git_diff_ui_config() gets called, and therefore color.diff.*\n   respected.\n * Avoided leaking four_spaces.\n * Fixed a declaration in a for (;;) statement (which Junio had as a fixup!\n   that I almost missed).\n * Renamed branch --diff, which had been renamed from branch-diff (which was\n   picked to avoid re-using tbdiff) to range-diff.\n * Renamed hungarian.c and its header to linear-assignment.c\n * Made --dual-color the default, and changed it to still auto-detect\n   whether color should be used rather than forcing it\n\nJohannes Schindelin (20):\n  linear-assignment: a function to solve least-cost assignment problems\n  Introduce `range-diff` to compare iterations of a topic branch\n  range-diff: first rudimentary implementation\n  range-diff: improve the order of the shown commits\n  range-diff: also show the diff between patches\n  range-diff: right-trim commit messages\n  range-diff: indent the diffs just like tbdiff\n  range-diff: suppress the diff headers\n  range-diff: adjust the output of the commit pairs\n  range-diff: do not show \"function names\" in hunk headers\n  range-diff: use color for the commit pairs\n  color: add the meta color GIT_COLOR_REVERSE\n  diff: add an internal option to dual-color diffs of diffs\n  range-diff: offer to dual-color the diffs\n  range-diff --dual-color: skip white-space warnings\n  range-diff: populate the man page\n  completion: support `git range-diff`\n  range-diff: left-pad patch numbers\n  range-diff: make --dual-color the default mode\n  range-diff: use dim/bold cues to improve dual color mode\n\nThomas Rast (1):\n  range-diff: add tests\n\n .gitignore                             |   1 +\n Documentation/config.txt               |   6 +-\n Documentation/git-range-diff.txt       | 252 +++++++++++\n Makefile                               |   3 +\n builtin.h                              |   1 +\n builtin/range-diff.c                   | 116 +++++\n color.h                                |   7 +\n command-list.txt                       |   1 +\n contrib/completion/git-completion.bash |  14 +\n diff.c                                 | 105 ++++-\n diff.h                                 |  10 +-\n git.c                                  |   1 +\n linear-assignment.c                    | 201 ++++++++\n linear-assignment.h                    |  22 +\n range-diff.c                           | 435 ++++++++++++++++++\n range-diff.h                           |   9 +\n t/.gitattributes                       |   1 +\n t/t3206-range-diff.sh                  | 145 ++++++\n t/t3206/history.export                 | 604 +++++++++++++++++++++++++\n 19 files changed, 1915 insertions(+), 19 deletions(-)\n create mode 100644 Documentation/git-range-diff.txt\n create mode 100644 builtin/range-diff.c\n create mode 100644 linear-assignment.c\n create mode 100644 linear-assignment.h\n create mode 100644 range-diff.c\n create mode 100644 range-diff.h\n create mode 100755 t/t3206-range-diff.sh\n create mode 100644 t/t3206/history.export\n\n\nbase-commit: 1d89318c48d233d52f1db230cf622935ac3c69fa\nPublished-As: https://github.com/gitgitgadget/git/releases/tags/pr-1%2Fdscho%2Fbranch-diff-v6\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1/dscho/branch-diff-v6\nPull-Request: https://github.com/gitgitgadget/git/pull/1\n\nRange-diff vs v5:\n\n  1:  f168da3a3 =  1:  f168da3a3 linear-assignment: a function to solve least-cost assignment problems\n  2:  33758f361 =  2:  33758f361 Introduce `range-diff` to compare iterations of a topic branch\n  3:  08b8c3fc4 =  3:  08b8c3fc4 range-diff: first rudimentary implementation\n  4:  7b9091968 =  4:  7b9091968 range-diff: improve the order of the shown commits\n  5:  9e1e66007 !  5:  8515d2f75 range-diff: also show the diff between patches\n     @@ -80,6 +80,8 @@\n      +\t\telse\n      +\t\t\ti += c;\n      +\t}\n     ++\twhile (i < argc)\n     ++\t\targv[j++] = argv[i++];\n      +\targc = j;\n      +\tdiff_setup_done(&diffopt);\n      +\n  6:  167ca02a3 =  6:  a10ca0163 range-diff: right-trim commit messages\n  7:  ca8de8c75 =  7:  f81cbef2c range-diff: indent the diffs just like tbdiff\n  8:  eb94d1982 =  8:  458090ffd range-diff: suppress the diff headers\n  9:  6330afad9 =  9:  d3be03a44 range-diff: adjust the output of the commit pairs\n 10:  c296675eb = 10:  94b44dfe6 range-diff: do not show \"function names\" in hunk headers\n 11:  85e0ab82f = 11:  1477c58e4 range-diff: add tests\n 12:  f48b62644 = 12:  32492c159 range-diff: use color for the commit pairs\n 13:  1ad74f939 = 13:  969a196f4 color: add the meta color GIT_COLOR_REVERSE\n 14:  39a0ecd28 = 14:  f1c86f606 diff: add an internal option to dual-color diffs of diffs\n 15:  c32a24f6a = 15:  3c7b9f339 range-diff: offer to dual-color the diffs\n 16:  05947781f = 16:  c56c51c8b range-diff --dual-color: skip white-space warnings\n 17:  3147c4440 = 17:  8c5543a06 range-diff: populate the man page\n 18:  b08e6d937 = 18:  16e3cf27b completion: support `git range-diff`\n 19:  19406283e = 19:  d9b09abcf range-diff: left-pad patch numbers\n 20:  6b3552386 = 20:  f6fd3955e range-diff: make --dual-color the default mode\n 21:  ccf8c1bb2 = 21:  699cd712e range-diff: use dim/bold cues to improve dual color mode\n\n-- \ngitgitgadget\n"},{"id":"355324","messageId":"f168da3a3c5d6ce9b7c1ea036ca184c20729153e.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 01/21] linear-assignment: a function to solve least-cost assignment problems","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:00Z","receivedAt":"2018-08-13T11:33:04Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe problem solved by the code introduced in this commit goes like this:\ngiven two sets of items, and a cost matrix which says how much it\n\"costs\" to assign any given item of the first set to any given item of\nthe second, assign all items (except when the sets have different size)\nin the cheapest way.\n\nWe use the Jonker-Volgenant algorithm to solve the assignment problem to\nanswer questions such as: given two different versions of a topic branch\n(or iterations of a patch series), what is the best pairing of\ncommits/patches between the different versions?\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Makefile            |   1 +\n linear-assignment.c | 201 ++++++++++++++++++++++++++++++++++++++++++++\n linear-assignment.h |  22 +++++\n 3 files changed, 224 insertions(+)\n create mode 100644 linear-assignment.c\n create mode 100644 linear-assignment.h\n\ndiff --git a/Makefile b/Makefile\nindex bc4fc8eea..1af719b44 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -870,6 +870,7 @@ LIB_OBJS += gpg-interface.o\n LIB_OBJS += graph.o\n LIB_OBJS += grep.o\n LIB_OBJS += hashmap.o\n+LIB_OBJS += linear-assignment.o\n LIB_OBJS += help.o\n LIB_OBJS += hex.o\n LIB_OBJS += ident.o\ndiff --git a/linear-assignment.c b/linear-assignment.c\nnew file mode 100644\nindex 000000000..9b3e56e28\n--- /dev/null\n+++ b/linear-assignment.c\n@@ -0,0 +1,201 @@\n+/*\n+ * Based on: Jonker, R., & Volgenant, A. (1987). <i>A shortest augmenting path\n+ * algorithm for dense and sparse linear assignment problems</i>. Computing,\n+ * 38(4), 325-340.\n+ */\n+#include \"cache.h\"\n+#include \"linear-assignment.h\"\n+\n+#define COST(column, row) cost[(column) + column_count * (row)]\n+\n+/*\n+ * The parameter `cost` is the cost matrix: the cost to assign column j to row\n+ * i is `cost[j + column_count * i].\n+ */\n+void compute_assignment(int column_count, int row_count, int *cost,\n+\t\t\tint *column2row, int *row2column)\n+{\n+\tint *v, *d;\n+\tint *free_row, free_count = 0, saved_free_count, *pred, *col;\n+\tint i, j, phase;\n+\n+\tmemset(column2row, -1, sizeof(int) * column_count);\n+\tmemset(row2column, -1, sizeof(int) * row_count);\n+\tALLOC_ARRAY(v, column_count);\n+\n+\t/* column reduction */\n+\tfor (j = column_count - 1; j >= 0; j--) {\n+\t\tint i1 = 0;\n+\n+\t\tfor (i = 1; i < row_count; i++)\n+\t\t\tif (COST(j, i1) > COST(j, i))\n+\t\t\t\ti1 = i;\n+\t\tv[j] = COST(j, i1);\n+\t\tif (row2column[i1] == -1) {\n+\t\t\t/* row i1 unassigned */\n+\t\t\trow2column[i1] = j;\n+\t\t\tcolumn2row[j] = i1;\n+\t\t} else {\n+\t\t\tif (row2column[i1] >= 0)\n+\t\t\t\trow2column[i1] = -2 - row2column[i1];\n+\t\t\tcolumn2row[j] = -1;\n+\t\t}\n+\t}\n+\n+\t/* reduction transfer */\n+\tALLOC_ARRAY(free_row, row_count);\n+\tfor (i = 0; i < row_count; i++) {\n+\t\tint j1 = row2column[i];\n+\t\tif (j1 == -1)\n+\t\t\tfree_row[free_count++] = i;\n+\t\telse if (j1 < -1)\n+\t\t\trow2column[i] = -2 - j1;\n+\t\telse {\n+\t\t\tint min = COST(!j1, i) - v[!j1];\n+\t\t\tfor (j = 1; j < column_count; j++)\n+\t\t\t\tif (j != j1 && min > COST(j, i) - v[j])\n+\t\t\t\t\tmin = COST(j, i) - v[j];\n+\t\t\tv[j1] -= min;\n+\t\t}\n+\t}\n+\n+\tif (free_count ==\n+\t    (column_count < row_count ? row_count - column_count : 0)) {\n+\t\tfree(v);\n+\t\tfree(free_row);\n+\t\treturn;\n+\t}\n+\n+\t/* augmenting row reduction */\n+\tfor (phase = 0; phase < 2; phase++) {\n+\t\tint k = 0;\n+\n+\t\tsaved_free_count = free_count;\n+\t\tfree_count = 0;\n+\t\twhile (k < saved_free_count) {\n+\t\t\tint u1, u2;\n+\t\t\tint j1 = 0, j2, i0;\n+\n+\t\t\ti = free_row[k++];\n+\t\t\tu1 = COST(j1, i) - v[j1];\n+\t\t\tj2 = -1;\n+\t\t\tu2 = INT_MAX;\n+\t\t\tfor (j = 1; j < column_count; j++) {\n+\t\t\t\tint c = COST(j, i) - v[j];\n+\t\t\t\tif (u2 > c) {\n+\t\t\t\t\tif (u1 < c) {\n+\t\t\t\t\t\tu2 = c;\n+\t\t\t\t\t\tj2 = j;\n+\t\t\t\t\t} else {\n+\t\t\t\t\t\tu2 = u1;\n+\t\t\t\t\t\tu1 = c;\n+\t\t\t\t\t\tj2 = j1;\n+\t\t\t\t\t\tj1 = j;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tif (j2 < 0) {\n+\t\t\t\tj2 = j1;\n+\t\t\t\tu2 = u1;\n+\t\t\t}\n+\n+\t\t\ti0 = column2row[j1];\n+\t\t\tif (u1 < u2)\n+\t\t\t\tv[j1] -= u2 - u1;\n+\t\t\telse if (i0 >= 0) {\n+\t\t\t\tj1 = j2;\n+\t\t\t\ti0 = column2row[j1];\n+\t\t\t}\n+\n+\t\t\tif (i0 >= 0) {\n+\t\t\t\tif (u1 < u2)\n+\t\t\t\t\tfree_row[--k] = i0;\n+\t\t\t\telse\n+\t\t\t\t\tfree_row[free_count++] = i0;\n+\t\t\t}\n+\t\t\trow2column[i] = j1;\n+\t\t\tcolumn2row[j1] = i;\n+\t\t}\n+\t}\n+\n+\t/* augmentation */\n+\tsaved_free_count = free_count;\n+\tALLOC_ARRAY(d, column_count);\n+\tALLOC_ARRAY(pred, column_count);\n+\tALLOC_ARRAY(col, column_count);\n+\tfor (free_count = 0; free_count < saved_free_count; free_count++) {\n+\t\tint i1 = free_row[free_count], low = 0, up = 0, last, k;\n+\t\tint min, c, u1;\n+\n+\t\tfor (j = 0; j < column_count; j++) {\n+\t\t\td[j] = COST(j, i1) - v[j];\n+\t\t\tpred[j] = i1;\n+\t\t\tcol[j] = j;\n+\t\t}\n+\n+\t\tj = -1;\n+\t\tdo {\n+\t\t\tlast = low;\n+\t\t\tmin = d[col[up++]];\n+\t\t\tfor (k = up; k < column_count; k++) {\n+\t\t\t\tj = col[k];\n+\t\t\t\tc = d[j];\n+\t\t\t\tif (c <= min) {\n+\t\t\t\t\tif (c < min) {\n+\t\t\t\t\t\tup = low;\n+\t\t\t\t\t\tmin = c;\n+\t\t\t\t\t}\n+\t\t\t\t\tcol[k] = col[up];\n+\t\t\t\t\tcol[up++] = j;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tfor (k = low; k < up; k++)\n+\t\t\t\tif (column2row[col[k]] == -1)\n+\t\t\t\t\tgoto update;\n+\n+\t\t\t/* scan a row */\n+\t\t\tdo {\n+\t\t\t\tint j1 = col[low++];\n+\n+\t\t\t\ti = column2row[j1];\n+\t\t\t\tu1 = COST(j1, i) - v[j1] - min;\n+\t\t\t\tfor (k = up; k < column_count; k++) {\n+\t\t\t\t\tj = col[k];\n+\t\t\t\t\tc = COST(j, i) - v[j] - u1;\n+\t\t\t\t\tif (c < d[j]) {\n+\t\t\t\t\t\td[j] = c;\n+\t\t\t\t\t\tpred[j] = i;\n+\t\t\t\t\t\tif (c == min) {\n+\t\t\t\t\t\t\tif (column2row[j] == -1)\n+\t\t\t\t\t\t\t\tgoto update;\n+\t\t\t\t\t\t\tcol[k] = col[up];\n+\t\t\t\t\t\t\tcol[up++] = j;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t} while (low != up);\n+\t\t} while (low == up);\n+\n+update:\n+\t\t/* updating of the column pieces */\n+\t\tfor (k = 0; k < last; k++) {\n+\t\t\tint j1 = col[k];\n+\t\t\tv[j1] += d[j1] - min;\n+\t\t}\n+\n+\t\t/* augmentation */\n+\t\tdo {\n+\t\t\tif (j < 0)\n+\t\t\t\tBUG(\"negative j: %d\", j);\n+\t\t\ti = pred[j];\n+\t\t\tcolumn2row[j] = i;\n+\t\t\tSWAP(j, row2column[i]);\n+\t\t} while (i1 != i);\n+\t}\n+\n+\tfree(col);\n+\tfree(pred);\n+\tfree(d);\n+\tfree(v);\n+\tfree(free_row);\n+}\ndiff --git a/linear-assignment.h b/linear-assignment.h\nnew file mode 100644\nindex 000000000..1dfea7662\n--- /dev/null\n+++ b/linear-assignment.h\n@@ -0,0 +1,22 @@\n+#ifndef LINEAR_ASSIGNMENT_H\n+#define LINEAR_ASSIGNMENT_H\n+\n+/*\n+ * Compute an assignment of columns -> rows (and vice versa) such that every\n+ * column is assigned to at most one row (and vice versa) minimizing the\n+ * overall cost.\n+ *\n+ * The parameter `cost` is the cost matrix: the cost to assign column j to row\n+ * i is `cost[j + column_count * i].\n+ *\n+ * The arrays column2row and row2column will be populated with the respective\n+ * assignments (-1 for unassigned, which can happen only if column_count !=\n+ * row_count).\n+ */\n+void compute_assignment(int column_count, int row_count, int *cost,\n+\t\t\tint *column2row, int *row2column);\n+\n+/* The maximal cost in the cost matrix (to prevent integer overflows). */\n+#define COST_MAX (1<<16)\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"355325","messageId":"33758f361c4cbe47bca50d10d61ff36bf6da2b5a.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 02/21] Introduce `range-diff` to compare iterations of a topic branch","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:02Z","receivedAt":"2018-08-13T11:33:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis command does not do a whole lot so far, apart from showing a usage\nthat is oddly similar to that of `git tbdiff`. And for a good reason:\nthe next commits will turn `range-branch` into a full-blown replacement\nfor `tbdiff`.\n\nAt this point, we ignore tbdiff's color options, as they will all be\nimplemented later using diff_options.\n\nSince f318d739159 (generate-cmds.sh: export all commands to\ncommand-list.h, 2018-05-10), every new command *requires* a man page to\nbuild right away, so let's also add a blank man page, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n .gitignore                       |  1 +\n Documentation/git-range-diff.txt | 10 ++++++++++\n Makefile                         |  1 +\n builtin.h                        |  1 +\n builtin/range-diff.c             | 25 +++++++++++++++++++++++++\n command-list.txt                 |  1 +\n git.c                            |  1 +\n 7 files changed, 40 insertions(+)\n create mode 100644 Documentation/git-range-diff.txt\n create mode 100644 builtin/range-diff.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 3284a1e9b..cc0ad74b4 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -113,6 +113,7 @@\n /git-pull\n /git-push\n /git-quiltimport\n+/git-range-diff\n /git-read-tree\n /git-rebase\n /git-rebase--am\ndiff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\nnew file mode 100644\nindex 000000000..49f717db8\n--- /dev/null\n+++ b/Documentation/git-range-diff.txt\n@@ -0,0 +1,10 @@\n+git-range-diff(1)\n+=================\n+\n+NAME\n+----\n+git-range-diff - Compare two commit ranges (e.g. two versions of a branch)\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex 1af719b44..7ff7eba42 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1063,6 +1063,7 @@ BUILTIN_OBJS += builtin/prune-packed.o\n BUILTIN_OBJS += builtin/prune.o\n BUILTIN_OBJS += builtin/pull.o\n BUILTIN_OBJS += builtin/push.o\n+BUILTIN_OBJS += builtin/range-diff.o\n BUILTIN_OBJS += builtin/read-tree.o\n BUILTIN_OBJS += builtin/rebase--helper.o\n BUILTIN_OBJS += builtin/receive-pack.o\ndiff --git a/builtin.h b/builtin.h\nindex 0362f1ce2..99206df4b 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -201,6 +201,7 @@ extern int cmd_prune(int argc, const char **argv, const char *prefix);\n extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);\n extern int cmd_pull(int argc, const char **argv, const char *prefix);\n extern int cmd_push(int argc, const char **argv, const char *prefix);\n+extern int cmd_range_diff(int argc, const char **argv, const char *prefix);\n extern int cmd_read_tree(int argc, const char **argv, const char *prefix);\n extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix);\n extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nnew file mode 100644\nindex 000000000..36788ea4f\n--- /dev/null\n+++ b/builtin/range-diff.c\n@@ -0,0 +1,25 @@\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"parse-options.h\"\n+\n+static const char * const builtin_range_diff_usage[] = {\n+N_(\"git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n+N_(\"git range-diff [<options>] <old-tip>...<new-tip>\"),\n+N_(\"git range-diff [<options>] <base> <old-tip> <new-tip>\"),\n+NULL\n+};\n+\n+int cmd_range_diff(int argc, const char **argv, const char *prefix)\n+{\n+\tint creation_factor = 60;\n+\tstruct option options[] = {\n+\t\tOPT_INTEGER(0, \"creation-factor\", &creation_factor,\n+\t\t\t    N_(\"Percentage by which creation is weighted\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, options,\n+\t\t\t     builtin_range_diff_usage, 0);\n+\n+\treturn 0;\n+}\ndiff --git a/command-list.txt b/command-list.txt\nindex e1c26c1bb..a9dda3b8a 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -139,6 +139,7 @@ git-prune-packed                        plumbingmanipulators\n git-pull                                mainporcelain           remote\n git-push                                mainporcelain           remote\n git-quiltimport                         foreignscminterface\n+git-range-diff                          mainporcelain\n git-read-tree                           plumbingmanipulators\n git-rebase                              mainporcelain           history\n git-receive-pack                        synchelpers\ndiff --git a/git.c b/git.c\nindex fc7d15d54..5b48cac3a 100644\n--- a/git.c\n+++ b/git.c\n@@ -520,6 +520,7 @@ static struct cmd_struct commands[] = {\n \t{ \"prune-packed\", cmd_prune_packed, RUN_SETUP },\n \t{ \"pull\", cmd_pull, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"push\", cmd_push, RUN_SETUP },\n+\t{ \"range-diff\", cmd_range_diff, RUN_SETUP | USE_PAGER },\n \t{ \"read-tree\", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX},\n \t{ \"rebase--helper\", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"receive-pack\", cmd_receive_pack },\n-- \ngitgitgadget\n\n"},{"id":"355326","messageId":"08b8c3fc45253737ef6ca860e6cbe3ee6211d7a6.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 03/21] range-diff: first rudimentary implementation","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:04Z","receivedAt":"2018-08-13T11:33:08Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nAt this stage, `git range-diff` can determine corresponding commits\nof two related commit ranges. This makes use of the recently introduced\nimplementation of the linear assignment algorithm.\n\nThe core of this patch is a straight port of the ideas of tbdiff, the\napparently dormant project at https://github.com/trast/tbdiff.\n\nThe output does not at all match `tbdiff`'s output yet, as this patch\nreally concentrates on getting the patch matching part right.\n\nNote: due to differences in the diff algorithm (`tbdiff` uses the Python\nmodule `difflib`, Git uses its xdiff fork), the cost matrix calculated\nby `range-diff` is different (but very similar) to the one calculated\nby `tbdiff`. Therefore, it is possible that they find different matching\ncommits in corner cases (e.g. when a patch was split into two patches of\nroughly equal length).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Makefile             |   1 +\n builtin/range-diff.c |  45 ++++++-\n range-diff.c         | 311 +++++++++++++++++++++++++++++++++++++++++++\n range-diff.h         |   7 +\n 4 files changed, 363 insertions(+), 1 deletion(-)\n create mode 100644 range-diff.c\n create mode 100644 range-diff.h\n\ndiff --git a/Makefile b/Makefile\nindex 7ff7eba42..72f16882e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -925,6 +925,7 @@ LIB_OBJS += progress.o\n LIB_OBJS += prompt.o\n LIB_OBJS += protocol.o\n LIB_OBJS += quote.o\n+LIB_OBJS += range-diff.o\n LIB_OBJS += reachable.o\n LIB_OBJS += read-cache.o\n LIB_OBJS += reflog-walk.o\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 36788ea4f..94c1f362c 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -1,6 +1,7 @@\n #include \"cache.h\"\n #include \"builtin.h\"\n #include \"parse-options.h\"\n+#include \"range-diff.h\"\n \n static const char * const builtin_range_diff_usage[] = {\n N_(\"git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n@@ -17,9 +18,51 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"Percentage by which creation is weighted\")),\n \t\tOPT_END()\n \t};\n+\tint res = 0;\n+\tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n \n \targc = parse_options(argc, argv, NULL, options,\n \t\t\t     builtin_range_diff_usage, 0);\n \n-\treturn 0;\n+\tif (argc == 2) {\n+\t\tif (!strstr(argv[0], \"..\"))\n+\t\t\tdie(_(\"no .. in range: '%s'\"), argv[0]);\n+\t\tstrbuf_addstr(&range1, argv[0]);\n+\n+\t\tif (!strstr(argv[1], \"..\"))\n+\t\t\tdie(_(\"no .. in range: '%s'\"), argv[1]);\n+\t\tstrbuf_addstr(&range2, argv[1]);\n+\t} else if (argc == 3) {\n+\t\tstrbuf_addf(&range1, \"%s..%s\", argv[0], argv[1]);\n+\t\tstrbuf_addf(&range2, \"%s..%s\", argv[0], argv[2]);\n+\t} else if (argc == 1) {\n+\t\tconst char *b = strstr(argv[0], \"...\"), *a = argv[0];\n+\t\tint a_len;\n+\n+\t\tif (!b) {\n+\t\t\terror(_(\"single arg format must be symmetric range\"));\n+\t\t\tusage_with_options(builtin_range_diff_usage, options);\n+\t\t}\n+\n+\t\ta_len = (int)(b - a);\n+\t\tif (!a_len) {\n+\t\t\ta = \"HEAD\";\n+\t\t\ta_len = strlen(a);\n+\t\t}\n+\t\tb += 3;\n+\t\tif (!*b)\n+\t\t\tb = \"HEAD\";\n+\t\tstrbuf_addf(&range1, \"%s..%.*s\", b, a_len, a);\n+\t\tstrbuf_addf(&range2, \"%.*s..%s\", a_len, a, b);\n+\t} else {\n+\t\terror(_(\"need two commit ranges\"));\n+\t\tusage_with_options(builtin_range_diff_usage, options);\n+\t}\n+\n+\tres = show_range_diff(range1.buf, range2.buf, creation_factor);\n+\n+\tstrbuf_release(&range1);\n+\tstrbuf_release(&range2);\n+\n+\treturn res;\n }\ndiff --git a/range-diff.c b/range-diff.c\nnew file mode 100644\nindex 000000000..15d418afa\n--- /dev/null\n+++ b/range-diff.c\n@@ -0,0 +1,311 @@\n+#include \"cache.h\"\n+#include \"range-diff.h\"\n+#include \"string-list.h\"\n+#include \"run-command.h\"\n+#include \"argv-array.h\"\n+#include \"hashmap.h\"\n+#include \"xdiff-interface.h\"\n+#include \"linear-assignment.h\"\n+\n+struct patch_util {\n+\t/* For the search for an exact match */\n+\tstruct hashmap_entry e;\n+\tconst char *diff, *patch;\n+\n+\tint i;\n+\tint diffsize;\n+\tsize_t diff_offset;\n+\t/* the index of the matching item in the other branch, or -1 */\n+\tint matching;\n+\tstruct object_id oid;\n+};\n+\n+/*\n+ * Reads the patches into a string list, with the `util` field being populated\n+ * as struct object_id (will need to be free()d).\n+ */\n+static int read_patches(const char *range, struct string_list *list)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tFILE *in;\n+\tstruct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;\n+\tstruct patch_util *util = NULL;\n+\tint in_header = 1;\n+\n+\targv_array_pushl(&cp.args, \"log\", \"--no-color\", \"-p\", \"--no-merges\",\n+\t\t\t\"--reverse\", \"--date-order\", \"--decorate=no\",\n+\t\t\t\"--no-abbrev-commit\", range,\n+\t\t\tNULL);\n+\tcp.out = -1;\n+\tcp.no_stdin = 1;\n+\tcp.git_cmd = 1;\n+\n+\tif (start_command(&cp))\n+\t\treturn error_errno(_(\"could not start `log`\"));\n+\tin = fdopen(cp.out, \"r\");\n+\tif (!in) {\n+\t\terror_errno(_(\"could not read `log` output\"));\n+\t\tfinish_command(&cp);\n+\t\treturn -1;\n+\t}\n+\n+\twhile (strbuf_getline(&line, in) != EOF) {\n+\t\tconst char *p;\n+\n+\t\tif (skip_prefix(line.buf, \"commit \", &p)) {\n+\t\t\tif (util) {\n+\t\t\t\tstring_list_append(list, buf.buf)->util = util;\n+\t\t\t\tstrbuf_reset(&buf);\n+\t\t\t}\n+\t\t\tutil = xcalloc(sizeof(*util), 1);\n+\t\t\tif (get_oid(p, &util->oid)) {\n+\t\t\t\terror(_(\"could not parse commit '%s'\"), p);\n+\t\t\t\tfree(util);\n+\t\t\t\tstring_list_clear(list, 1);\n+\t\t\t\tstrbuf_release(&buf);\n+\t\t\t\tstrbuf_release(&line);\n+\t\t\t\tfclose(in);\n+\t\t\t\tfinish_command(&cp);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t\tutil->matching = -1;\n+\t\t\tin_header = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (starts_with(line.buf, \"diff --git\")) {\n+\t\t\tin_header = 0;\n+\t\t\tstrbuf_addch(&buf, '\\n');\n+\t\t\tif (!util->diff_offset)\n+\t\t\t\tutil->diff_offset = buf.len;\n+\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\t} else if (in_header) {\n+\t\t\tif (starts_with(line.buf, \"Author: \")) {\n+\t\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\t\t\tstrbuf_addstr(&buf, \"\\n\\n\");\n+\t\t\t} else if (starts_with(line.buf, \"    \")) {\n+\t\t\t\tstrbuf_addbuf(&buf, &line);\n+\t\t\t\tstrbuf_addch(&buf, '\\n');\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t} else if (starts_with(line.buf, \"@@ \"))\n+\t\t\tstrbuf_addstr(&buf, \"@@\");\n+\t\telse if (!line.buf[0] || starts_with(line.buf, \"index \"))\n+\t\t\t/*\n+\t\t\t * A completely blank (not ' \\n', which is context)\n+\t\t\t * line is not valid in a diff.  We skip it\n+\t\t\t * silently, because this neatly handles the blank\n+\t\t\t * separator line between commits in git-log\n+\t\t\t * output.\n+\t\t\t *\n+\t\t\t * We also want to ignore the diff's `index` lines\n+\t\t\t * because they contain exact blob hashes in which\n+\t\t\t * we are not interested.\n+\t\t\t */\n+\t\t\tcontinue;\n+\t\telse\n+\t\t\tstrbuf_addbuf(&buf, &line);\n+\n+\t\tstrbuf_addch(&buf, '\\n');\n+\t\tutil->diffsize++;\n+\t}\n+\tfclose(in);\n+\tstrbuf_release(&line);\n+\n+\tif (util)\n+\t\tstring_list_append(list, buf.buf)->util = util;\n+\tstrbuf_release(&buf);\n+\n+\tif (finish_command(&cp))\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+static int patch_util_cmp(const void *dummy, const struct patch_util *a,\n+\t\t     const struct patch_util *b, const char *keydata)\n+{\n+\treturn strcmp(a->diff, keydata ? keydata : b->diff);\n+}\n+\n+static void find_exact_matches(struct string_list *a, struct string_list *b)\n+{\n+\tstruct hashmap map;\n+\tint i;\n+\n+\thashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);\n+\n+\t/* First, add the patches of a to a hash map */\n+\tfor (i = 0; i < a->nr; i++) {\n+\t\tstruct patch_util *util = a->items[i].util;\n+\n+\t\tutil->i = i;\n+\t\tutil->patch = a->items[i].string;\n+\t\tutil->diff = util->patch + util->diff_offset;\n+\t\thashmap_entry_init(util, strhash(util->diff));\n+\t\thashmap_add(&map, util);\n+\t}\n+\n+\t/* Now try to find exact matches in b */\n+\tfor (i = 0; i < b->nr; i++) {\n+\t\tstruct patch_util *util = b->items[i].util, *other;\n+\n+\t\tutil->i = i;\n+\t\tutil->patch = b->items[i].string;\n+\t\tutil->diff = util->patch + util->diff_offset;\n+\t\thashmap_entry_init(util, strhash(util->diff));\n+\t\tother = hashmap_remove(&map, util, NULL);\n+\t\tif (other) {\n+\t\t\tif (other->matching >= 0)\n+\t\t\t\tBUG(\"already assigned!\");\n+\n+\t\t\tother->matching = i;\n+\t\t\tutil->matching = other->i;\n+\t\t}\n+\t}\n+\n+\thashmap_free(&map, 0);\n+}\n+\n+static void diffsize_consume(void *data, char *line, unsigned long len)\n+{\n+\t(*(int *)data)++;\n+}\n+\n+static int diffsize(const char *a, const char *b)\n+{\n+\txpparam_t pp = { 0 };\n+\txdemitconf_t cfg = { 0 };\n+\tmmfile_t mf1, mf2;\n+\tint count = 0;\n+\n+\tmf1.ptr = (char *)a;\n+\tmf1.size = strlen(a);\n+\tmf2.ptr = (char *)b;\n+\tmf2.size = strlen(b);\n+\n+\tcfg.ctxlen = 3;\n+\tif (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))\n+\t\treturn count;\n+\n+\terror(_(\"failed to generate diff\"));\n+\treturn COST_MAX;\n+}\n+\n+static void get_correspondences(struct string_list *a, struct string_list *b,\n+\t\t\t\tint creation_factor)\n+{\n+\tint n = a->nr + b->nr;\n+\tint *cost, c, *a2b, *b2a;\n+\tint i, j;\n+\n+\tALLOC_ARRAY(cost, st_mult(n, n));\n+\tALLOC_ARRAY(a2b, n);\n+\tALLOC_ARRAY(b2a, n);\n+\n+\tfor (i = 0; i < a->nr; i++) {\n+\t\tstruct patch_util *a_util = a->items[i].util;\n+\n+\t\tfor (j = 0; j < b->nr; j++) {\n+\t\t\tstruct patch_util *b_util = b->items[j].util;\n+\n+\t\t\tif (a_util->matching == j)\n+\t\t\t\tc = 0;\n+\t\t\telse if (a_util->matching < 0 && b_util->matching < 0)\n+\t\t\t\tc = diffsize(a_util->diff, b_util->diff);\n+\t\t\telse\n+\t\t\t\tc = COST_MAX;\n+\t\t\tcost[i + n * j] = c;\n+\t\t}\n+\n+\t\tc = a_util->matching < 0 ?\n+\t\t\ta_util->diffsize * creation_factor / 100 : COST_MAX;\n+\t\tfor (j = b->nr; j < n; j++)\n+\t\t\tcost[i + n * j] = c;\n+\t}\n+\n+\tfor (j = 0; j < b->nr; j++) {\n+\t\tstruct patch_util *util = b->items[j].util;\n+\n+\t\tc = util->matching < 0 ?\n+\t\t\tutil->diffsize * creation_factor / 100 : COST_MAX;\n+\t\tfor (i = a->nr; i < n; i++)\n+\t\t\tcost[i + n * j] = c;\n+\t}\n+\n+\tfor (i = a->nr; i < n; i++)\n+\t\tfor (j = b->nr; j < n; j++)\n+\t\t\tcost[i + n * j] = 0;\n+\n+\tcompute_assignment(n, n, cost, a2b, b2a);\n+\n+\tfor (i = 0; i < a->nr; i++)\n+\t\tif (a2b[i] >= 0 && a2b[i] < b->nr) {\n+\t\t\tstruct patch_util *a_util = a->items[i].util;\n+\t\t\tstruct patch_util *b_util = b->items[a2b[i]].util;\n+\n+\t\t\ta_util->matching = a2b[i];\n+\t\t\tb_util->matching = i;\n+\t\t}\n+\n+\tfree(cost);\n+\tfree(a2b);\n+\tfree(b2a);\n+}\n+\n+static const char *short_oid(struct patch_util *util)\n+{\n+\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n+}\n+\n+static void output(struct string_list *a, struct string_list *b)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < b->nr; i++) {\n+\t\tstruct patch_util *util = b->items[i].util, *prev;\n+\n+\t\tif (util->matching < 0)\n+\t\t\tprintf(\"-: -------- > %d: %s\\n\",\n+\t\t\t\t\ti + 1, short_oid(util));\n+\t\telse {\n+\t\t\tprev = a->items[util->matching].util;\n+\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n+\t\t\t       util->matching + 1, short_oid(prev),\n+\t\t\t       i + 1, short_oid(util));\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i < a->nr; i++) {\n+\t\tstruct patch_util *util = a->items[i].util;\n+\n+\t\tif (util->matching < 0)\n+\t\t\tprintf(\"%d: %s < -: --------\\n\",\n+\t\t\t       i + 1, short_oid(util));\n+\t}\n+}\n+\n+int show_range_diff(const char *range1, const char *range2,\n+\t\t    int creation_factor)\n+{\n+\tint res = 0;\n+\n+\tstruct string_list branch1 = STRING_LIST_INIT_DUP;\n+\tstruct string_list branch2 = STRING_LIST_INIT_DUP;\n+\n+\tif (read_patches(range1, &branch1))\n+\t\tres = error(_(\"could not parse log for '%s'\"), range1);\n+\tif (!res && read_patches(range2, &branch2))\n+\t\tres = error(_(\"could not parse log for '%s'\"), range2);\n+\n+\tif (!res) {\n+\t\tfind_exact_matches(&branch1, &branch2);\n+\t\tget_correspondences(&branch1, &branch2, creation_factor);\n+\t\toutput(&branch1, &branch2);\n+\t}\n+\n+\tstring_list_clear(&branch1, 1);\n+\tstring_list_clear(&branch2, 1);\n+\n+\treturn res;\n+}\ndiff --git a/range-diff.h b/range-diff.h\nnew file mode 100644\nindex 000000000..7b6eef303\n--- /dev/null\n+++ b/range-diff.h\n@@ -0,0 +1,7 @@\n+#ifndef RANGE_DIFF_H\n+#define RANGE_DIFF_H\n+\n+int show_range_diff(const char *range1, const char *range2,\n+\t\t    int creation_factor);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"355327","messageId":"7b90919685d1e94b50f5278ec1b57a59cba1f8e5.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 04/21] range-diff: improve the order of the shown commits","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:05Z","receivedAt":"2018-08-13T11:33:08Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis patch lets `git range-diff` use the same order as tbdiff.\n\nThe idea is simple: for left-to-right readers, it is natural to assume\nthat the `git range-diff` is performed between an older vs a newer\nversion of the branch. As such, the user is probably more interested in\nthe question \"where did this come from?\" rather than \"where did that one\ngo?\".\n\nTo that end, we list the commits in the order of the second commit range\n(\"the newer version\"), inserting the unmatched commits of the first\ncommit range as soon as all their predecessors have been shown.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 59 +++++++++++++++++++++++++++++++++++-----------------\n 1 file changed, 40 insertions(+), 19 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 15d418afa..2d94200d3 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -12,7 +12,7 @@ struct patch_util {\n \tstruct hashmap_entry e;\n \tconst char *diff, *patch;\n \n-\tint i;\n+\tint i, shown;\n \tint diffsize;\n \tsize_t diff_offset;\n \t/* the index of the matching item in the other branch, or -1 */\n@@ -260,28 +260,49 @@ static const char *short_oid(struct patch_util *util)\n \n static void output(struct string_list *a, struct string_list *b)\n {\n-\tint i;\n-\n-\tfor (i = 0; i < b->nr; i++) {\n-\t\tstruct patch_util *util = b->items[i].util, *prev;\n+\tint i = 0, j = 0;\n+\n+\t/*\n+\t * We assume the user is really more interested in the second argument\n+\t * (\"newer\" version). To that end, we print the output in the order of\n+\t * the RHS (the `b` parameter). To put the LHS (the `a` parameter)\n+\t * commits that are no longer in the RHS into a good place, we place\n+\t * them once we have shown all of their predecessors in the LHS.\n+\t */\n+\n+\twhile (i < a->nr || j < b->nr) {\n+\t\tstruct patch_util *a_util, *b_util;\n+\t\ta_util = i < a->nr ? a->items[i].util : NULL;\n+\t\tb_util = j < b->nr ? b->items[j].util : NULL;\n+\n+\t\t/* Skip all the already-shown commits from the LHS. */\n+\t\twhile (i < a->nr && a_util->shown)\n+\t\t\ta_util = ++i < a->nr ? a->items[i].util : NULL;\n+\n+\t\t/* Show unmatched LHS commit whose predecessors were shown. */\n+\t\tif (i < a->nr && a_util->matching < 0) {\n+\t\t\tprintf(\"%d: %s < -: --------\\n\",\n+\t\t\t       i + 1, short_oid(a_util));\n+\t\t\ti++;\n+\t\t\tcontinue;\n+\t\t}\n \n-\t\tif (util->matching < 0)\n+\t\t/* Show unmatched RHS commits. */\n+\t\twhile (j < b->nr && b_util->matching < 0) {\n \t\t\tprintf(\"-: -------- > %d: %s\\n\",\n-\t\t\t\t\ti + 1, short_oid(util));\n-\t\telse {\n-\t\t\tprev = a->items[util->matching].util;\n-\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n-\t\t\t       util->matching + 1, short_oid(prev),\n-\t\t\t       i + 1, short_oid(util));\n+\t\t\t       j + 1, short_oid(b_util));\n+\t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n-\t}\n-\n-\tfor (i = 0; i < a->nr; i++) {\n-\t\tstruct patch_util *util = a->items[i].util;\n \n-\t\tif (util->matching < 0)\n-\t\t\tprintf(\"%d: %s < -: --------\\n\",\n-\t\t\t       i + 1, short_oid(util));\n+\t\t/* Show matching LHS/RHS pair. */\n+\t\tif (j < b->nr) {\n+\t\t\ta_util = a->items[b_util->matching].util;\n+\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n+\t\t\t       b_util->matching + 1, short_oid(a_util),\n+\t\t\t       j + 1, short_oid(b_util));\n+\t\t\ta_util->shown = 1;\n+\t\t\tj++;\n+\t\t}\n \t}\n }\n \n-- \ngitgitgadget\n\n"},{"id":"355328","messageId":"8515d2f75c5439bcc38ea1a9f5a57b89cd8945d7.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 05/21] range-diff: also show the diff between patches","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:07Z","receivedAt":"2018-08-13T11:33:10Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nJust like tbdiff, we now show the diff between matching patches. This is\na \"diff of two diffs\", so it can be a bit daunting to read for the\nbeginner.\n\nAn alternative would be to display an interdiff, i.e. the hypothetical\ndiff which is the result of first reverting the old diff and then\napplying the new diff.\n\nEspecially when rebasing frequently, an interdiff is often not feasible,\nthough: if the old diff cannot be applied in reverse (due to a moving\nupstream), an interdiff can simply not be inferred.\n\nThis commit brings `range-diff` closer to feature parity with regard\nto tbdiff.\n\nTo make `git range-diff` respect e.g. color.diff.* settings, we have\nto adjust git_branch_config() accordingly.\n\nNote: while we now parse diff options such as --color, the effect is not\nyet the same as in tbdiff, where also the commit pairs would be colored.\nThis is left for a later commit.\n\nNote also: while tbdiff accepts the `--no-patches` option to suppress\nthese diffs between patches, we prefer the `-s` (or `--no-patch`) option\nthat is automatically supported via our use of diff_opt_parse().\n\nAnd finally note: to support diff options, we have to call\n`parse_options()` such that it keeps unknown options, and then loop over\nthose and let `diff_opt_parse()` handle them. After that loop, we have\nto call `parse_options()` again, to make sure that no unknown options\nare left.\n\nHelped-by: Thomas Gummerer <t.gummerer@gmail.com>\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/range-diff.c | 31 +++++++++++++++++++++++++++++--\n range-diff.c         | 34 +++++++++++++++++++++++++++++++---\n range-diff.h         |  4 +++-\n 3 files changed, 63 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 94c1f362c..3b06ed944 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -2,6 +2,7 @@\n #include \"builtin.h\"\n #include \"parse-options.h\"\n #include \"range-diff.h\"\n+#include \"config.h\"\n \n static const char * const builtin_range_diff_usage[] = {\n N_(\"git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\"),\n@@ -13,15 +14,40 @@ NULL\n int cmd_range_diff(int argc, const char **argv, const char *prefix)\n {\n \tint creation_factor = 60;\n+\tstruct diff_options diffopt = { NULL };\n \tstruct option options[] = {\n \t\tOPT_INTEGER(0, \"creation-factor\", &creation_factor,\n \t\t\t    N_(\"Percentage by which creation is weighted\")),\n \t\tOPT_END()\n \t};\n-\tint res = 0;\n+\tint i, j, res = 0;\n \tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n \n+\tgit_config(git_diff_ui_config, NULL);\n+\n+\tdiff_setup(&diffopt);\n+\tdiffopt.output_format = DIFF_FORMAT_PATCH;\n+\n \targc = parse_options(argc, argv, NULL, options,\n+\t\t\t     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN |\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);\n+\n+\tfor (i = j = 1; i < argc && strcmp(\"--\", argv[i]); ) {\n+\t\tint c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);\n+\n+\t\tif (!c)\n+\t\t\targv[j++] = argv[i++];\n+\t\telse\n+\t\t\ti += c;\n+\t}\n+\twhile (i < argc)\n+\t\targv[j++] = argv[i++];\n+\targc = j;\n+\tdiff_setup_done(&diffopt);\n+\n+\t/* Make sure that there are no unparsed options */\n+\targc = parse_options(argc, argv, NULL,\n+\t\t\t     options + ARRAY_SIZE(options) - 1, /* OPT_END */\n \t\t\t     builtin_range_diff_usage, 0);\n \n \tif (argc == 2) {\n@@ -59,7 +85,8 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(builtin_range_diff_usage, options);\n \t}\n \n-\tres = show_range_diff(range1.buf, range2.buf, creation_factor);\n+\tres = show_range_diff(range1.buf, range2.buf, creation_factor,\n+\t\t\t      &diffopt);\n \n \tstrbuf_release(&range1);\n \tstrbuf_release(&range2);\ndiff --git a/range-diff.c b/range-diff.c\nindex 2d94200d3..71883a4b7 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -6,6 +6,7 @@\n #include \"hashmap.h\"\n #include \"xdiff-interface.h\"\n #include \"linear-assignment.h\"\n+#include \"diffcore.h\"\n \n struct patch_util {\n \t/* For the search for an exact match */\n@@ -258,7 +259,31 @@ static const char *short_oid(struct patch_util *util)\n \treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n }\n \n-static void output(struct string_list *a, struct string_list *b)\n+static struct diff_filespec *get_filespec(const char *name, const char *p)\n+{\n+\tstruct diff_filespec *spec = alloc_filespec(name);\n+\n+\tfill_filespec(spec, &null_oid, 0, 0644);\n+\tspec->data = (char *)p;\n+\tspec->size = strlen(p);\n+\tspec->should_munmap = 0;\n+\tspec->is_stdin = 1;\n+\n+\treturn spec;\n+}\n+\n+static void patch_diff(const char *a, const char *b,\n+\t\t\t      struct diff_options *diffopt)\n+{\n+\tdiff_queue(&diff_queued_diff,\n+\t\t   get_filespec(\"a\", a), get_filespec(\"b\", b));\n+\n+\tdiffcore_std(diffopt);\n+\tdiff_flush(diffopt);\n+}\n+\n+static void output(struct string_list *a, struct string_list *b,\n+\t\t   struct diff_options *diffopt)\n {\n \tint i = 0, j = 0;\n \n@@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)\n \t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n \t\t\t       b_util->matching + 1, short_oid(a_util),\n \t\t\t       j + 1, short_oid(b_util));\n+\t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n+\t\t\t\tpatch_diff(a->items[b_util->matching].string,\n+\t\t\t\t\t   b->items[j].string, diffopt);\n \t\t\ta_util->shown = 1;\n \t\t\tj++;\n \t\t}\n@@ -307,7 +335,7 @@ static void output(struct string_list *a, struct string_list *b)\n }\n \n int show_range_diff(const char *range1, const char *range2,\n-\t\t    int creation_factor)\n+\t\t    int creation_factor, struct diff_options *diffopt)\n {\n \tint res = 0;\n \n@@ -322,7 +350,7 @@ int show_range_diff(const char *range1, const char *range2,\n \tif (!res) {\n \t\tfind_exact_matches(&branch1, &branch2);\n \t\tget_correspondences(&branch1, &branch2, creation_factor);\n-\t\toutput(&branch1, &branch2);\n+\t\toutput(&branch1, &branch2, diffopt);\n \t}\n \n \tstring_list_clear(&branch1, 1);\ndiff --git a/range-diff.h b/range-diff.h\nindex 7b6eef303..2407d46a3 100644\n--- a/range-diff.h\n+++ b/range-diff.h\n@@ -1,7 +1,9 @@\n #ifndef RANGE_DIFF_H\n #define RANGE_DIFF_H\n \n+#include \"diff.h\"\n+\n int show_range_diff(const char *range1, const char *range2,\n-\t\t    int creation_factor);\n+\t\t    int creation_factor, struct diff_options *diffopt);\n \n #endif\n-- \ngitgitgadget\n\n"},{"id":"355329","messageId":"a10ca01633c65bdae66f10af93075fe0400ccb5b.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 06/21] range-diff: right-trim commit messages","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:08Z","receivedAt":"2018-08-13T11:33:11Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen comparing commit messages, we need to keep in mind that they are\nindented by four spaces. That is, empty lines are no longer empty, but\nhave \"trailing whitespace\". When displaying them in color, that results\nin those nagging red lines.\n\nLet's just right-trim the lines in the commit message, it's not like\ntrailing white-space in the commit messages are important enough to care\nabout in `git range-diff`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 71883a4b7..1ecee2c09 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -85,6 +85,7 @@ static int read_patches(const char *range, struct string_list *list)\n \t\t\t\tstrbuf_addbuf(&buf, &line);\n \t\t\t\tstrbuf_addstr(&buf, \"\\n\\n\");\n \t\t\t} else if (starts_with(line.buf, \"    \")) {\n+\t\t\t\tstrbuf_rtrim(&line);\n \t\t\t\tstrbuf_addbuf(&buf, &line);\n \t\t\t\tstrbuf_addch(&buf, '\\n');\n \t\t\t}\n-- \ngitgitgadget\n\n"},{"id":"355332","messageId":"f81cbef2c7946bac5e3f6c08a9b937eb05450cae.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 07/21] range-diff: indent the diffs just like tbdiff","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:10Z","receivedAt":"2018-08-13T11:33:13Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe main information in the `range-diff` view comes from the list of\nmatching and non-matching commits, the diffs are additional information.\nIndenting them helps with the reading flow.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/range-diff.c | 10 ++++++++++\n 1 file changed, 10 insertions(+)\n\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 3b06ed944..f0598005a 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -11,6 +11,11 @@ N_(\"git range-diff [<options>] <base> <old-tip> <new-tip>\"),\n NULL\n };\n \n+static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)\n+{\n+\treturn data;\n+}\n+\n int cmd_range_diff(int argc, const char **argv, const char *prefix)\n {\n \tint creation_factor = 60;\n@@ -21,12 +26,16 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \tint i, j, res = 0;\n+\tstruct strbuf four_spaces = STRBUF_INIT;\n \tstruct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;\n \n \tgit_config(git_diff_ui_config, NULL);\n \n \tdiff_setup(&diffopt);\n \tdiffopt.output_format = DIFF_FORMAT_PATCH;\n+\tdiffopt.output_prefix = output_prefix_cb;\n+\tstrbuf_addstr(&four_spaces, \"    \");\n+\tdiffopt.output_prefix_data = &four_spaces;\n \n \targc = parse_options(argc, argv, NULL, options,\n \t\t\t     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN |\n@@ -90,6 +99,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \n \tstrbuf_release(&range1);\n \tstrbuf_release(&range2);\n+\tstrbuf_release(&four_spaces);\n \n \treturn res;\n }\n-- \ngitgitgadget\n\n"},{"id":"355330","messageId":"458090ffd23115545c999aeef952e2e29ee628f0.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 08/21] range-diff: suppress the diff headers","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:11Z","receivedAt":"2018-08-13T11:33:15Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen showing the diff between corresponding patches of the two branch\nversions, we have to make up a fake filename to run the diff machinery.\n\nThat filename does not carry any meaningful information, hence tbdiff\nsuppresses it. So we should, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/range-diff.c | 1 +\n diff.c               | 5 ++++-\n diff.h               | 1 +\n 3 files changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex f0598005a..76659d0b3 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -33,6 +33,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \n \tdiff_setup(&diffopt);\n \tdiffopt.output_format = DIFF_FORMAT_PATCH;\n+\tdiffopt.flags.suppress_diff_headers = 1;\n \tdiffopt.output_prefix = output_prefix_cb;\n \tstrbuf_addstr(&four_spaces, \"    \");\n \tdiffopt.output_prefix_data = &four_spaces;\ndiff --git a/diff.c b/diff.c\nindex 04d044bbb..9c4bd9fa1 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -3395,13 +3395,16 @@ static void builtin_diff(const char *name_a,\n \t\tmemset(&xpp, 0, sizeof(xpp));\n \t\tmemset(&xecfg, 0, sizeof(xecfg));\n \t\tmemset(&ecbdata, 0, sizeof(ecbdata));\n+\t\tif (o->flags.suppress_diff_headers)\n+\t\t\tlbl[0] = NULL;\n \t\tecbdata.label_path = lbl;\n \t\tecbdata.color_diff = want_color(o->use_color);\n \t\tecbdata.ws_rule = whitespace_rule(name_b);\n \t\tif (ecbdata.ws_rule & WS_BLANK_AT_EOF)\n \t\t\tcheck_blank_at_eof(&mf1, &mf2, &ecbdata);\n \t\tecbdata.opt = o;\n-\t\tecbdata.header = header.len ? &header : NULL;\n+\t\tif (header.len && !o->flags.suppress_diff_headers)\n+\t\t\tecbdata.header = &header;\n \t\txpp.flags = o->xdl_opts;\n \t\txpp.anchors = o->anchors;\n \t\txpp.anchors_nr = o->anchors_nr;\ndiff --git a/diff.h b/diff.h\nindex a14895bb8..d88ceb357 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -94,6 +94,7 @@ struct diff_flags {\n \tunsigned funccontext:1;\n \tunsigned default_follow_renames:1;\n \tunsigned stat_with_summary:1;\n+\tunsigned suppress_diff_headers:1;\n };\n \n static inline void diff_flags_or(struct diff_flags *a,\n-- \ngitgitgadget\n\n"},{"id":"355331","messageId":"d3be03a446a40d2176b6ffdd9e095d97873042c2.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 09/21] range-diff: adjust the output of the commit pairs","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:13Z","receivedAt":"2018-08-13T11:33:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis not only uses \"dashed stand-ins\" for \"pairs\" where one side is\nmissing (i.e. unmatched commits that are present only in one of the two\ncommit ranges), but also adds onelines for the reader's pleasure.\n\nThis change brings `git range-diff` yet another step closer to\nfeature parity with tbdiff: it now shows the oneline, too, and indicates\nwith `=` when the commits have identical diffs.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 59 ++++++++++++++++++++++++++++++++++++++++++++--------\n 1 file changed, 50 insertions(+), 9 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 1ecee2c09..23aa61af5 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -7,6 +7,8 @@\n #include \"xdiff-interface.h\"\n #include \"linear-assignment.h\"\n #include \"diffcore.h\"\n+#include \"commit.h\"\n+#include \"pretty.h\"\n \n struct patch_util {\n \t/* For the search for an exact match */\n@@ -255,9 +257,49 @@ static void get_correspondences(struct string_list *a, struct string_list *b,\n \tfree(b2a);\n }\n \n-static const char *short_oid(struct patch_util *util)\n+static void output_pair_header(struct strbuf *buf,\n+\t\t\t       struct strbuf *dashes,\n+\t\t\t       struct patch_util *a_util,\n+\t\t\t       struct patch_util *b_util)\n {\n-\treturn find_unique_abbrev(&util->oid, DEFAULT_ABBREV);\n+\tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n+\tstruct commit *commit;\n+\n+\tif (!dashes->len)\n+\t\tstrbuf_addchars(dashes, '-',\n+\t\t\t\tstrlen(find_unique_abbrev(oid,\n+\t\t\t\t\t\t\t  DEFAULT_ABBREV)));\n+\n+\tstrbuf_reset(buf);\n+\tif (!a_util)\n+\t\tstrbuf_addf(buf, \"-:  %s \", dashes->buf);\n+\telse\n+\t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n+\t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n+\n+\tif (!a_util)\n+\t\tstrbuf_addch(buf, '>');\n+\telse if (!b_util)\n+\t\tstrbuf_addch(buf, '<');\n+\telse if (strcmp(a_util->patch, b_util->patch))\n+\t\tstrbuf_addch(buf, '!');\n+\telse\n+\t\tstrbuf_addch(buf, '=');\n+\n+\tif (!b_util)\n+\t\tstrbuf_addf(buf, \" -:  %s\", dashes->buf);\n+\telse\n+\t\tstrbuf_addf(buf, \" %d:  %s\", b_util->i + 1,\n+\t\t\t    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));\n+\n+\tcommit = lookup_commit_reference(the_repository, oid);\n+\tif (commit) {\n+\t\tstrbuf_addch(buf, ' ');\n+\t\tpp_commit_easy(CMIT_FMT_ONELINE, commit, buf);\n+\t}\n+\tstrbuf_addch(buf, '\\n');\n+\n+\tfwrite(buf->buf, buf->len, 1, stdout);\n }\n \n static struct diff_filespec *get_filespec(const char *name, const char *p)\n@@ -286,6 +328,7 @@ static void patch_diff(const char *a, const char *b,\n static void output(struct string_list *a, struct string_list *b,\n \t\t   struct diff_options *diffopt)\n {\n+\tstruct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;\n \tint i = 0, j = 0;\n \n \t/*\n@@ -307,25 +350,21 @@ static void output(struct string_list *a, struct string_list *b,\n \n \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n \t\tif (i < a->nr && a_util->matching < 0) {\n-\t\t\tprintf(\"%d: %s < -: --------\\n\",\n-\t\t\t       i + 1, short_oid(a_util));\n+\t\t\toutput_pair_header(&buf, &dashes, a_util, NULL);\n \t\t\ti++;\n \t\t\tcontinue;\n \t\t}\n \n \t\t/* Show unmatched RHS commits. */\n \t\twhile (j < b->nr && b_util->matching < 0) {\n-\t\t\tprintf(\"-: -------- > %d: %s\\n\",\n-\t\t\t       j + 1, short_oid(b_util));\n+\t\t\toutput_pair_header(&buf, &dashes, NULL, b_util);\n \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n \n \t\t/* Show matching LHS/RHS pair. */\n \t\tif (j < b->nr) {\n \t\t\ta_util = a->items[b_util->matching].util;\n-\t\t\tprintf(\"%d: %s ! %d: %s\\n\",\n-\t\t\t       b_util->matching + 1, short_oid(a_util),\n-\t\t\t       j + 1, short_oid(b_util));\n+\t\t\toutput_pair_header(&buf, &dashes, a_util, b_util);\n \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n \t\t\t\t\t   b->items[j].string, diffopt);\n@@ -333,6 +372,8 @@ static void output(struct string_list *a, struct string_list *b,\n \t\t\tj++;\n \t\t}\n \t}\n+\tstrbuf_release(&buf);\n+\tstrbuf_release(&dashes);\n }\n \n int show_range_diff(const char *range1, const char *range2,\n-- \ngitgitgadget\n\n"},{"id":"355333","messageId":"94b44dfe6859fd79803d682885a6a52ad800eedd.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 10/21] range-diff: do not show \"function names\" in hunk headers","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:14Z","receivedAt":"2018-08-13T11:33:18Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWe are comparing complete, formatted commit messages with patches. There\nare no function names here, so stop looking for them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 23aa61af5..6d75563f4 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -9,6 +9,7 @@\n #include \"diffcore.h\"\n #include \"commit.h\"\n #include \"pretty.h\"\n+#include \"userdiff.h\"\n \n struct patch_util {\n \t/* For the search for an exact match */\n@@ -302,6 +303,10 @@ static void output_pair_header(struct strbuf *buf,\n \tfwrite(buf->buf, buf->len, 1, stdout);\n }\n \n+static struct userdiff_driver no_func_name = {\n+\t.funcname = { \"$^\", 0 }\n+};\n+\n static struct diff_filespec *get_filespec(const char *name, const char *p)\n {\n \tstruct diff_filespec *spec = alloc_filespec(name);\n@@ -311,6 +316,7 @@ static struct diff_filespec *get_filespec(const char *name, const char *p)\n \tspec->size = strlen(p);\n \tspec->should_munmap = 0;\n \tspec->is_stdin = 1;\n+\tspec->driver = &no_func_name;\n \n \treturn spec;\n }\n-- \ngitgitgadget\n\n"},{"id":"355334","messageId":"1477c58e4c0c9e1d5836ffcb9bcb7360b1a5df0d.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 11/21] range-diff: add tests","fromName":"Thomas Rast via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:16Z","receivedAt":"2018-08-13T11:33:20Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"From: Thomas Rast <tr@thomasrast.ch>\n\nThese are essentially lifted from https://github.com/trast/tbdiff, with\nlight touch-ups to account for the command now being named `git\nrange-diff`.\n\nApart from renaming `tbdiff` to `range-diff`, only one test case needed\nto be adjusted: 11 - 'changed message'.\n\nThe underlying reason it had to be adjusted is that diff generation is\nsometimes ambiguous. In this case, a comment line and an empty line are\nadded, but it is ambiguous whether they were added after the existing\nempty line, or whether an empty line and the comment line are added\n*before* the existing empty line. And apparently xdiff picks a different\noption here than Python's difflib.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/.gitattributes       |   1 +\n t/t3206-range-diff.sh  | 145 ++++++++++\n t/t3206/history.export | 604 +++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 750 insertions(+)\n create mode 100755 t/t3206-range-diff.sh\n create mode 100644 t/t3206/history.export\n\ndiff --git a/t/.gitattributes b/t/.gitattributes\nindex 3bd959ae5..b17bf71b8 100644\n--- a/t/.gitattributes\n+++ b/t/.gitattributes\n@@ -1,6 +1,7 @@\n t[0-9][0-9][0-9][0-9]/* -whitespace\n /diff-lib/* eol=lf\n /t0110/url-* binary\n+/t3206/* eol=lf\n /t3900/*.txt eol=lf\n /t3901/*.txt eol=lf\n /t4034/*/* eol=lf\ndiff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh\nnew file mode 100755\nindex 000000000..2237c7f4a\n--- /dev/null\n+++ b/t/t3206-range-diff.sh\n@@ -0,0 +1,145 @@\n+#!/bin/sh\n+\n+test_description='range-diff tests'\n+\n+. ./test-lib.sh\n+\n+# Note that because of the range-diff's heuristics, test_commit does more\n+# harm than good.  We need some real history.\n+\n+test_expect_success 'setup' '\n+\tgit fast-import < \"$TEST_DIRECTORY\"/t3206/history.export\n+'\n+\n+test_expect_success 'simple A..B A..C (unmodified)' '\n+\tgit range-diff --no-color master..topic master..unmodified \\\n+\t\t>actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  35b9b25 s/5/A/\n+\t2:  fccce22 = 2:  de345ab s/4/A/\n+\t3:  147e64e = 3:  9af6654 s/11/B/\n+\t4:  a63e992 = 4:  2901f77 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'simple B...C (unmodified)' '\n+\tgit range-diff --no-color topic...unmodified >actual &&\n+\t# same \"expected\" as above\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'simple A B C (unmodified)' '\n+\tgit range-diff --no-color master topic unmodified >actual &&\n+\t# same \"expected\" as above\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'trivial reordering' '\n+\tgit range-diff --no-color master topic reordered >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  aca177a s/5/A/\n+\t3:  147e64e = 2:  14ad629 s/11/B/\n+\t4:  a63e992 = 3:  ee58208 s/12/B/\n+\t2:  fccce22 = 4:  307b27a s/4/A/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'removed a commit' '\n+\tgit range-diff --no-color master topic removed >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  7657159 s/5/A/\n+\t2:  fccce22 < -:  ------- s/4/A/\n+\t3:  147e64e = 2:  43d84d3 s/11/B/\n+\t4:  a63e992 = 3:  a740396 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'added a commit' '\n+\tgit range-diff --no-color master topic added >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  2716022 s/5/A/\n+\t2:  fccce22 = 2:  b62accd s/4/A/\n+\t-:  ------- > 3:  df46cfa s/6/A/\n+\t3:  147e64e = 4:  3e64548 s/11/B/\n+\t4:  a63e992 = 5:  12b4063 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'new base, A B C' '\n+\tgit range-diff --no-color master topic rebased >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  cc9c443 s/5/A/\n+\t2:  fccce22 = 2:  c5d9641 s/4/A/\n+\t3:  147e64e = 3:  28cc2b6 s/11/B/\n+\t4:  a63e992 = 4:  5628ab7 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'new base, B...C' '\n+\t# this syntax includes the commits from master!\n+\tgit range-diff --no-color topic...rebased >actual &&\n+\tcat >expected <<-EOF &&\n+\t-:  ------- > 1:  a31b12e unrelated\n+\t1:  4de457d = 2:  cc9c443 s/5/A/\n+\t2:  fccce22 = 3:  c5d9641 s/4/A/\n+\t3:  147e64e = 4:  28cc2b6 s/11/B/\n+\t4:  a63e992 = 5:  5628ab7 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'changed commit' '\n+\tgit range-diff --no-color topic...changed >actual &&\n+\tcat >expected <<-EOF &&\n+\t1:  4de457d = 1:  a4b3333 s/5/A/\n+\t2:  fccce22 = 2:  f51d370 s/4/A/\n+\t3:  147e64e ! 3:  0559556 s/11/B/\n+\t    @@ -10,7 +10,7 @@\n+\t      9\n+\t      10\n+\t     -11\n+\t    -+B\n+\t    ++BB\n+\t      12\n+\t      13\n+\t      14\n+\t4:  a63e992 ! 4:  d966c5c s/12/B/\n+\t    @@ -8,7 +8,7 @@\n+\t     @@\n+\t      9\n+\t      10\n+\t    - B\n+\t    + BB\n+\t     -12\n+\t     +B\n+\t      13\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'changed message' '\n+\tgit range-diff --no-color topic...changed-message >actual &&\n+\tsed s/Z/\\ /g >expected <<-EOF &&\n+\t1:  4de457d = 1:  f686024 s/5/A/\n+\t2:  fccce22 ! 2:  4ab067d s/4/A/\n+\t    @@ -2,6 +2,8 @@\n+\t    Z\n+\t    Z    s/4/A/\n+\t    Z\n+\t    +    Also a silly comment here!\n+\t    +\n+\t    Zdiff --git a/file b/file\n+\t    Z--- a/file\n+\t    Z+++ b/file\n+\t3:  147e64e = 3:  b9cb956 s/11/B/\n+\t4:  a63e992 = 4:  8add5f1 s/12/B/\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_done\ndiff --git a/t/t3206/history.export b/t/t3206/history.export\nnew file mode 100644\nindex 000000000..b8ffff094\n--- /dev/null\n+++ b/t/t3206/history.export\n@@ -0,0 +1,604 @@\n+blob\n+mark :1\n+data 51\n+1\n+2\n+3\n+4\n+5\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+reset refs/heads/removed\n+commit refs/heads/removed\n+mark :2\n+author Thomas Rast <trast@inf.ethz.ch> 1374424921 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374484724 +0200\n+data 8\n+initial\n+M 100644 :1 file\n+\n+blob\n+mark :3\n+data 51\n+1\n+2\n+3\n+4\n+A\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :4\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+blob\n+mark :5\n+data 51\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :6\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+data 7\n+s/4/A/\n+from :4\n+M 100644 :5 file\n+\n+blob\n+mark :7\n+data 50\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :8\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+data 8\n+s/11/B/\n+from :6\n+M 100644 :7 file\n+\n+blob\n+mark :9\n+data 49\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/topic\n+mark :10\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+data 8\n+s/12/B/\n+from :8\n+M 100644 :9 file\n+\n+blob\n+mark :11\n+data 10\n+unrelated\n+\n+commit refs/heads/master\n+mark :12\n+author Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200\n+data 10\n+unrelated\n+from :2\n+M 100644 :11 otherfile\n+\n+commit refs/heads/rebased\n+mark :13\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485137 +0200\n+data 7\n+s/5/A/\n+from :12\n+M 100644 :3 file\n+\n+commit refs/heads/rebased\n+mark :14\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n+data 7\n+s/4/A/\n+from :13\n+M 100644 :5 file\n+\n+commit refs/heads/rebased\n+mark :15\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n+data 8\n+s/11/B/\n+from :14\n+M 100644 :7 file\n+\n+commit refs/heads/rebased\n+mark :16\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n+data 8\n+s/12/B/\n+from :15\n+M 100644 :9 file\n+\n+commit refs/heads/added\n+mark :17\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/added\n+mark :18\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 7\n+s/4/A/\n+from :17\n+M 100644 :5 file\n+\n+blob\n+mark :19\n+data 51\n+1\n+2\n+3\n+A\n+A\n+A\n+7\n+8\n+9\n+10\n+11\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/added\n+mark :20\n+author Thomas Rast <trast@inf.ethz.ch> 1374485186 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 7\n+s/6/A/\n+from :18\n+M 100644 :19 file\n+\n+blob\n+mark :21\n+data 50\n+1\n+2\n+3\n+A\n+A\n+A\n+7\n+8\n+9\n+10\n+B\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/added\n+mark :22\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 8\n+s/11/B/\n+from :20\n+M 100644 :21 file\n+\n+blob\n+mark :23\n+data 49\n+1\n+2\n+3\n+A\n+A\n+A\n+7\n+8\n+9\n+10\n+B\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/added\n+mark :24\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n+data 8\n+s/12/B/\n+from :22\n+M 100644 :23 file\n+\n+commit refs/heads/reordered\n+mark :25\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+blob\n+mark :26\n+data 50\n+1\n+2\n+3\n+4\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/reordered\n+mark :27\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 8\n+s/11/B/\n+from :25\n+M 100644 :26 file\n+\n+blob\n+mark :28\n+data 49\n+1\n+2\n+3\n+4\n+A\n+6\n+7\n+8\n+9\n+10\n+B\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/reordered\n+mark :29\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 8\n+s/12/B/\n+from :27\n+M 100644 :28 file\n+\n+commit refs/heads/reordered\n+mark :30\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n+data 7\n+s/4/A/\n+from :29\n+M 100644 :9 file\n+\n+commit refs/heads/changed\n+mark :31\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/changed\n+mark :32\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 7\n+s/4/A/\n+from :31\n+M 100644 :5 file\n+\n+blob\n+mark :33\n+data 51\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+BB\n+12\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/changed\n+mark :34\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 8\n+s/11/B/\n+from :32\n+M 100644 :33 file\n+\n+blob\n+mark :35\n+data 50\n+1\n+2\n+3\n+A\n+A\n+6\n+7\n+8\n+9\n+10\n+BB\n+B\n+13\n+14\n+15\n+16\n+17\n+18\n+19\n+20\n+\n+commit refs/heads/changed\n+mark :36\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n+data 8\n+s/12/B/\n+from :34\n+M 100644 :35 file\n+\n+commit refs/heads/changed-message\n+mark :37\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/changed-message\n+mark :38\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200\n+data 35\n+s/4/A/\n+\n+Also a silly comment here!\n+from :37\n+M 100644 :5 file\n+\n+commit refs/heads/changed-message\n+mark :39\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200\n+data 8\n+s/11/B/\n+from :38\n+M 100644 :7 file\n+\n+commit refs/heads/changed-message\n+mark :40\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200\n+data 8\n+s/12/B/\n+from :39\n+M 100644 :9 file\n+\n+commit refs/heads/unmodified\n+mark :41\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/unmodified\n+mark :42\n+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200\n+data 7\n+s/4/A/\n+from :41\n+M 100644 :5 file\n+\n+commit refs/heads/unmodified\n+mark :43\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200\n+data 8\n+s/11/B/\n+from :42\n+M 100644 :7 file\n+\n+commit refs/heads/unmodified\n+mark :44\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200\n+data 8\n+s/12/B/\n+from :43\n+M 100644 :9 file\n+\n+commit refs/heads/removed\n+mark :45\n+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n+data 7\n+s/5/A/\n+from :2\n+M 100644 :3 file\n+\n+commit refs/heads/removed\n+mark :46\n+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n+data 8\n+s/11/B/\n+from :45\n+M 100644 :26 file\n+\n+commit refs/heads/removed\n+mark :47\n+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n+data 8\n+s/12/B/\n+from :46\n+M 100644 :28 file\n+\n+reset refs/heads/removed\n+from :47\n+\n-- \ngitgitgadget\n\n"},{"id":"355335","messageId":"32492c159b4fc02d690a3034d1a5deb21fcdd2ed.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 12/21] range-diff: use color for the commit pairs","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:18Z","receivedAt":"2018-08-13T11:33:21Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nArguably the most important part of `git range-diff`'s output is the\nlist of commits in the two branches, together with their relationships.\n\nFor that reason, tbdiff introduced color-coding that is pretty\nintuitive, especially for unchanged patches (all dim yellow, like the\nfirst line in `git show`'s output) vs modified patches (old commit is\nred, new commit is green). Let's imitate that color scheme.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 51 ++++++++++++++++++++++++++++++++++++++-------------\n 1 file changed, 38 insertions(+), 13 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 6d75563f4..b1663da7c 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -258,34 +258,53 @@ static void get_correspondences(struct string_list *a, struct string_list *b,\n \tfree(b2a);\n }\n \n-static void output_pair_header(struct strbuf *buf,\n+static void output_pair_header(struct diff_options *diffopt,\n+\t\t\t       struct strbuf *buf,\n \t\t\t       struct strbuf *dashes,\n \t\t\t       struct patch_util *a_util,\n \t\t\t       struct patch_util *b_util)\n {\n \tstruct object_id *oid = a_util ? &a_util->oid : &b_util->oid;\n \tstruct commit *commit;\n+\tchar status;\n+\tconst char *color_reset = diff_get_color_opt(diffopt, DIFF_RESET);\n+\tconst char *color_old = diff_get_color_opt(diffopt, DIFF_FILE_OLD);\n+\tconst char *color_new = diff_get_color_opt(diffopt, DIFF_FILE_NEW);\n+\tconst char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT);\n+\tconst char *color;\n \n \tif (!dashes->len)\n \t\tstrbuf_addchars(dashes, '-',\n \t\t\t\tstrlen(find_unique_abbrev(oid,\n \t\t\t\t\t\t\t  DEFAULT_ABBREV)));\n \n+\tif (!b_util) {\n+\t\tcolor = color_old;\n+\t\tstatus = '<';\n+\t} else if (!a_util) {\n+\t\tcolor = color_new;\n+\t\tstatus = '>';\n+\t} else if (strcmp(a_util->patch, b_util->patch)) {\n+\t\tcolor = color_commit;\n+\t\tstatus = '!';\n+\t} else {\n+\t\tcolor = color_commit;\n+\t\tstatus = '=';\n+\t}\n+\n \tstrbuf_reset(buf);\n+\tstrbuf_addstr(buf, status == '!' ? color_old : color);\n \tif (!a_util)\n \t\tstrbuf_addf(buf, \"-:  %s \", dashes->buf);\n \telse\n \t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n \t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n \n-\tif (!a_util)\n-\t\tstrbuf_addch(buf, '>');\n-\telse if (!b_util)\n-\t\tstrbuf_addch(buf, '<');\n-\telse if (strcmp(a_util->patch, b_util->patch))\n-\t\tstrbuf_addch(buf, '!');\n-\telse\n-\t\tstrbuf_addch(buf, '=');\n+\tif (status == '!')\n+\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color);\n+\tstrbuf_addch(buf, status);\n+\tif (status == '!')\n+\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color_new);\n \n \tif (!b_util)\n \t\tstrbuf_addf(buf, \" -:  %s\", dashes->buf);\n@@ -295,10 +314,13 @@ static void output_pair_header(struct strbuf *buf,\n \n \tcommit = lookup_commit_reference(the_repository, oid);\n \tif (commit) {\n+\t\tif (status == '!')\n+\t\t\tstrbuf_addf(buf, \"%s%s\", color_reset, color);\n+\n \t\tstrbuf_addch(buf, ' ');\n \t\tpp_commit_easy(CMIT_FMT_ONELINE, commit, buf);\n \t}\n-\tstrbuf_addch(buf, '\\n');\n+\tstrbuf_addf(buf, \"%s\\n\", color_reset);\n \n \tfwrite(buf->buf, buf->len, 1, stdout);\n }\n@@ -356,21 +378,24 @@ static void output(struct string_list *a, struct string_list *b,\n \n \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n \t\tif (i < a->nr && a_util->matching < 0) {\n-\t\t\toutput_pair_header(&buf, &dashes, a_util, NULL);\n+\t\t\toutput_pair_header(diffopt,\n+\t\t\t\t\t   &buf, &dashes, a_util, NULL);\n \t\t\ti++;\n \t\t\tcontinue;\n \t\t}\n \n \t\t/* Show unmatched RHS commits. */\n \t\twhile (j < b->nr && b_util->matching < 0) {\n-\t\t\toutput_pair_header(&buf, &dashes, NULL, b_util);\n+\t\t\toutput_pair_header(diffopt,\n+\t\t\t\t\t   &buf, &dashes, NULL, b_util);\n \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n \n \t\t/* Show matching LHS/RHS pair. */\n \t\tif (j < b->nr) {\n \t\t\ta_util = a->items[b_util->matching].util;\n-\t\t\toutput_pair_header(&buf, &dashes, a_util, b_util);\n+\t\t\toutput_pair_header(diffopt,\n+\t\t\t\t\t   &buf, &dashes, a_util, b_util);\n \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n \t\t\t\t\t   b->items[j].string, diffopt);\n-- \ngitgitgadget\n\n"},{"id":"355336","messageId":"969a196f48be31e72803d673981a1fa8ad1e3f4b.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 13/21] color: add the meta color GIT_COLOR_REVERSE","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:19Z","receivedAt":"2018-08-13T11:33:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis \"color\" simply reverts background and foreground. It will be used\nin the upcoming \"dual color\" mode of `git range-diff`, where we will\nreverse colors for the -/+ markers and the fragment headers of the\n\"outer\" diff.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n color.h | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/color.h b/color.h\nindex 5b744e1bc..33e786342 100644\n--- a/color.h\n+++ b/color.h\n@@ -44,6 +44,7 @@ struct strbuf;\n #define GIT_COLOR_BG_CYAN\t\"\\033[46m\"\n #define GIT_COLOR_FAINT\t\t\"\\033[2m\"\n #define GIT_COLOR_FAINT_ITALIC\t\"\\033[2;3m\"\n+#define GIT_COLOR_REVERSE\t\"\\033[7m\"\n \n /* A special value meaning \"no color selected\" */\n #define GIT_COLOR_NIL \"NIL\"\n-- \ngitgitgadget\n\n"},{"id":"355337","messageId":"3c7b9f339258fee12a1ea1184dc078ca5eca1cba.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 15/21] range-diff: offer to dual-color the diffs","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:22Z","receivedAt":"2018-08-13T11:33:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen showing what changed between old and new commits, we show a diff of\nthe patches. This diff is a diff between diffs, therefore there are\nnested +/- signs, and it can be relatively hard to understand what is\ngoing on.\n\nWith the --dual-color option, the preimage and the postimage are colored\nlike the diffs they are, and the *outer* +/- sign is inverted for\nclarity.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/range-diff.c | 8 ++++++++\n 1 file changed, 8 insertions(+)\n\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 76659d0b3..5a9ad82fb 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -20,9 +20,12 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n {\n \tint creation_factor = 60;\n \tstruct diff_options diffopt = { NULL };\n+\tint dual_color = 0;\n \tstruct option options[] = {\n \t\tOPT_INTEGER(0, \"creation-factor\", &creation_factor,\n \t\t\t    N_(\"Percentage by which creation is weighted\")),\n+\t\tOPT_BOOL(0, \"dual-color\", &dual_color,\n+\t\t\t    N_(\"color both diff and diff-between-diffs\")),\n \t\tOPT_END()\n \t};\n \tint i, j, res = 0;\n@@ -60,6 +63,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\t\t     options + ARRAY_SIZE(options) - 1, /* OPT_END */\n \t\t\t     builtin_range_diff_usage, 0);\n \n+\tif (dual_color) {\n+\t\tdiffopt.use_color = 1;\n+\t\tdiffopt.flags.dual_color_diffed_diffs = 1;\n+\t}\n+\n \tif (argc == 2) {\n \t\tif (!strstr(argv[0], \"..\"))\n \t\t\tdie(_(\"no .. in range: '%s'\"), argv[0]);\n-- \ngitgitgadget\n\n"},{"id":"355338","messageId":"c56c51c8bb470ec9a498e984854f5154acf86ef2.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 16/21] range-diff --dual-color: skip white-space warnings","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:24Z","receivedAt":"2018-08-13T11:33:28Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen displaying a diff of diffs, it is possible that there is an outer\n`+` before a context line. That happens when the context changed between\nold and new commit. When that context line starts with a tab (after the\nspace that marks it as context line), our diff machinery spits out a\nwhite-space error (space before tab), but in this case, that is\nincorrect.\n\nRather than adding a specific whitespace flag that specifically ignores\nthe first space in the output (and might miss other problems with the\nwhite-space warnings), let's just skip handling white-space errors in\ndual color mode to begin with.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n diff.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/diff.c b/diff.c\nindex e6c857abf..ea8ecae04 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -1299,6 +1299,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n \t\t\telse if (c != '+')\n \t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t\tflags &= ~DIFF_SYMBOL_CONTENT_WS_MASK;\n \t\t}\n \t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK,\n-- \ngitgitgadget\n\n"},{"id":"355339","messageId":"8c5543a0667fffe0cb0684427f726fdfb75b28d0.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 17/21] range-diff: populate the man page","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:25Z","receivedAt":"2018-08-13T11:33:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe bulk of this patch consists of a heavily butchered version of\ntbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from\nhttps://github.com/trast/tbdiff.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-range-diff.txt | 229 +++++++++++++++++++++++++++++++\n 1 file changed, 229 insertions(+)\n\ndiff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\nindex 49f717db8..bebb47d42 100644\n--- a/Documentation/git-range-diff.txt\n+++ b/Documentation/git-range-diff.txt\n@@ -5,6 +5,235 @@ NAME\n ----\n git-range-diff - Compare two commit ranges (e.g. two versions of a branch)\n \n+SYNOPSIS\n+--------\n+[verse]\n+'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]\n+\t[--dual-color] [--creation-factor=<factor>]\n+\t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n+\n+DESCRIPTION\n+-----------\n+\n+This command shows the differences between two versions of a patch\n+series, or more generally, two commit ranges (ignoring merge commits).\n+\n+To that end, it first finds pairs of commits from both commit ranges\n+that correspond with each other. Two commits are said to correspond when\n+the diff between their patches (i.e. the author information, the commit\n+message and the commit diff) is reasonably small compared to the\n+patches' size. See ``Algorithm`` below for details.\n+\n+Finally, the list of matching commits is shown in the order of the\n+second commit range, with unmatched commits being inserted just after\n+all of their ancestors have been shown.\n+\n+\n+OPTIONS\n+-------\n+--dual-color::\n+\tWhen the commit diffs differ, recreate the original diffs'\n+\tcoloring, and add outer -/+ diff markers with the *background*\n+\tbeing red/green to make it easier to see e.g. when there was a\n+\tchange in what exact lines were added.\n+\n+--creation-factor=<percent>::\n+\tSet the creation/deletion cost fudge factor to `<percent>`.\n+\tDefaults to 60. Try a larger value if `git range-diff` erroneously\n+\tconsiders a large change a total rewrite (deletion of one commit\n+\tand addition of another), and a smaller one in the reverse case.\n+\tSee the ``Algorithm`` section below for an explanation why this is\n+\tneeded.\n+\n+<range1> <range2>::\n+\tCompare the commits specified by the two ranges, where\n+\t`<range1>` is considered an older version of `<range2>`.\n+\n+<rev1>...<rev2>::\n+\tEquivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.\n+\n+<base> <rev1> <rev2>::\n+\tEquivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.\n+\tNote that `<base>` does not need to be the exact branch point\n+\tof the branches. Example: after rebasing a branch `my-topic`,\n+\t`git range-diff my-topic@{u} my-topic@{1} my-topic` would\n+\tshow the differences introduced by the rebase.\n+\n+`git range-diff` also accepts the regular diff options (see\n+linkgit:git-diff[1]), most notably the `--color=[<when>]` and\n+`--no-color` options. These options are used when generating the \"diff\n+between patches\", i.e. to compare the author, commit message and diff of\n+corresponding old/new commits. There is currently no means to tweak the\n+diff options passed to `git log` when generating those patches.\n+\n+\n+CONFIGURATION\n+-------------\n+This command uses the `diff.color.*` and `pager.range-diff` settings\n+(the latter is on by default).\n+See linkgit:git-config[1].\n+\n+\n+EXAMPLES\n+--------\n+\n+When a rebase required merge conflicts to be resolved, compare the changes\n+introduced by the rebase directly afterwards using:\n+\n+------------\n+$ git range-diff @{u} @{1} @\n+------------\n+\n+\n+A typical output of `git range-diff` would look like this:\n+\n+------------\n+-:  ------- > 1:  0ddba11 Prepare for the inevitable!\n+1:  c0debee = 2:  cab005e Add a helpful message at the start\n+2:  f00dbal ! 3:  decafe1 Describe a bug\n+    @@ -1,3 +1,3 @@\n+     Author: A U Thor <author@example.com>\n+\n+    -TODO: Describe a bug\n+    +Describe a bug\n+    @@ -324,5 +324,6\n+      This is expected.\n+\n+    -+What is unexpected is that it will also crash.\n+    ++Unexpectedly, it also crashes. This is a bug, and the jury is\n+    ++still out there how to fix it best. See ticket #314 for details.\n+\n+      Contact\n+3:  bedead < -:  ------- TO-UNDO\n+------------\n+\n+In this example, there are 3 old and 3 new commits, where the developer\n+removed the 3rd, added a new one before the first two, and modified the\n+commit message of the 2nd commit as well its diff.\n+\n+When the output goes to a terminal, it is color-coded by default, just\n+like regular `git diff`'s output. In addition, the first line (adding a\n+commit) is green, the last line (deleting a commit) is red, the second\n+line (with a perfect match) is yellow like the commit header of `git\n+show`'s output, and the third line colors the old commit red, the new\n+one green and the rest like `git show`'s commit header.\n+\n+The color-coded diff is actually a bit hard to read, though, as it\n+colors the entire lines red or green. The line that added \"What is\n+unexpected\" in the old commit, for example, is completely red, even if\n+the intent of the old commit was to add something.\n+\n+To help with that, use the `--dual-color` mode. In this mode, the diff\n+of diffs will retain the original diff colors, and prefix the lines with\n+-/+ markers that have their *background* red or green, to make it more\n+obvious that they describe how the diff itself changed.\n+\n+\n+Algorithm\n+---------\n+\n+The general idea is this: we generate a cost matrix between the commits\n+in both commit ranges, then solve the least-cost assignment.\n+\n+The cost matrix is populated thusly: for each pair of commits, both\n+diffs are generated and the \"diff of diffs\" is generated, with 3 context\n+lines, then the number of lines in that diff is used as cost.\n+\n+To avoid false positives (e.g. when a patch has been removed, and an\n+unrelated patch has been added between two iterations of the same patch\n+series), the cost matrix is extended to allow for that, by adding\n+fixed-cost entries for wholesale deletes/adds.\n+\n+Example: Let commits `1--2` be the first iteration of a patch series and\n+`A--C` the second iteration. Let's assume that `A` is a cherry-pick of\n+`2,` and `C` is a cherry-pick of `1` but with a small modification (say,\n+a fixed typo). Visualize the commits as a bipartite graph:\n+\n+------------\n+    1            A\n+\n+    2            B\n+\n+\t\t C\n+------------\n+\n+We are looking for a \"best\" explanation of the new series in terms of\n+the old one. We can represent an \"explanation\" as an edge in the graph:\n+\n+\n+------------\n+    1            A\n+\t       /\n+    2 --------'  B\n+\n+\t\t C\n+------------\n+\n+This explanation comes for \"free\" because there was no change. Similarly\n+`C` could be explained using `1`, but that comes at some cost c>0\n+because of the modification:\n+\n+------------\n+    1 ----.      A\n+\t  |    /\n+    2 ----+---'  B\n+\t  |\n+\t  `----- C\n+\t  c>0\n+------------\n+\n+In mathematical terms, what we are looking for is some sort of a minimum\n+cost bipartite matching; `1` is matched to `C` at some cost, etc. The\n+underlying graph is in fact a complete bipartite graph; the cost we\n+associate with every edge is the size of the diff between the two\n+commits' patches. To explain also new commits, we introduce dummy nodes\n+on both sides:\n+\n+------------\n+    1 ----.      A\n+\t  |    /\n+    2 ----+---'  B\n+\t  |\n+    o     `----- C\n+\t  c>0\n+    o            o\n+\n+    o            o\n+------------\n+\n+The cost of an edge `o--C` is the size of `C`'s diff, modified by a\n+fudge factor that should be smaller than 100%. The cost of an edge\n+`o--o` is free. The fudge factor is necessary because even if `1` and\n+`C` have nothing in common, they may still share a few empty lines and\n+such, possibly making the assignment `1--C`, `o--o` slightly cheaper\n+than `1--o`, `o--C` even if `1` and `C` have nothing in common. With the\n+fudge factor we require a much larger common part to consider patches as\n+corresponding.\n+\n+The overall time needed to compute this algorithm is the time needed to\n+compute n+m commit diffs and then n*m diffs of patches, plus the time\n+needed to compute the least-cost assigment between n and m diffs. Git\n+uses an implementation of the Jonker-Volgenant algorithm to solve the\n+assignment problem, which has cubic runtime complexity. The matching\n+found in this case will look like this:\n+\n+------------\n+    1 ----.      A\n+\t  |    /\n+    2 ----+---'  B\n+       .--+-----'\n+    o -'  `----- C\n+\t  c>0\n+    o ---------- o\n+\n+    o ---------- o\n+------------\n+\n+\n+SEE ALSO\n+--------\n+linkgit:git-log[1]\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\n-- \ngitgitgadget\n\n"},{"id":"355340","messageId":"16e3cf27b515ba94412c48b0f58059f1f30cc08d.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 18/21] completion: support `git range-diff`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:27Z","receivedAt":"2018-08-13T11:33:32Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nTab completion of `git range-diff` is very convenient, especially\ngiven that the revision arguments to specify the commit ranges to\ncompare are typically more complex than, say, what is normally passed\nto `git log`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n contrib/completion/git-completion.bash | 14 ++++++++++++++\n 1 file changed, 14 insertions(+)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 94c95516e..3d4ec3432 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1976,6 +1976,20 @@ _git_push ()\n \t__git_complete_remote_or_refspec\n }\n \n+_git_range_diff ()\n+{\n+\tcase \"$cur\" in\n+\t--*)\n+\t\t__gitcomp \"\n+\t\t\t--creation-factor= --dual-color\n+\t\t\t$__git_diff_common_options\n+\t\t\"\n+\t\treturn\n+\t\t;;\n+\tesac\n+\t__git_complete_revlist\n+}\n+\n _git_rebase ()\n {\n \t__git_find_repo_path\n-- \ngitgitgadget\n\n"},{"id":"355341","messageId":"d9b09abcfd0b8ca51ad1f5bbc473a02139d48890.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 19/21] range-diff: left-pad patch numbers","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:28Z","receivedAt":"2018-08-13T11:33:32Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nAs pointed out by Elijah Newren, tbdiff has this neat little alignment\ntrick where it outputs the commit pairs with patch numbers that are\npadded to the maximal patch number's width:\n\n\t  1: cafedead =   1: acefade first patch\n\t[...]\n\t314: beefeada < 314: facecab up to PI!\n\nLet's do the same in range-diff, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n range-diff.c | 16 +++++++++-------\n 1 file changed, 9 insertions(+), 7 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex b1663da7c..b6b9abac2 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -259,6 +259,7 @@ static void get_correspondences(struct string_list *a, struct string_list *b,\n }\n \n static void output_pair_header(struct diff_options *diffopt,\n+\t\t\t       int patch_no_width,\n \t\t\t       struct strbuf *buf,\n \t\t\t       struct strbuf *dashes,\n \t\t\t       struct patch_util *a_util,\n@@ -295,9 +296,9 @@ static void output_pair_header(struct diff_options *diffopt,\n \tstrbuf_reset(buf);\n \tstrbuf_addstr(buf, status == '!' ? color_old : color);\n \tif (!a_util)\n-\t\tstrbuf_addf(buf, \"-:  %s \", dashes->buf);\n+\t\tstrbuf_addf(buf, \"%*s:  %s \", patch_no_width, \"-\", dashes->buf);\n \telse\n-\t\tstrbuf_addf(buf, \"%d:  %s \", a_util->i + 1,\n+\t\tstrbuf_addf(buf, \"%*d:  %s \", patch_no_width, a_util->i + 1,\n \t\t\t    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));\n \n \tif (status == '!')\n@@ -307,9 +308,9 @@ static void output_pair_header(struct diff_options *diffopt,\n \t\tstrbuf_addf(buf, \"%s%s\", color_reset, color_new);\n \n \tif (!b_util)\n-\t\tstrbuf_addf(buf, \" -:  %s\", dashes->buf);\n+\t\tstrbuf_addf(buf, \" %*s:  %s\", patch_no_width, \"-\", dashes->buf);\n \telse\n-\t\tstrbuf_addf(buf, \" %d:  %s\", b_util->i + 1,\n+\t\tstrbuf_addf(buf, \" %*d:  %s\", patch_no_width, b_util->i + 1,\n \t\t\t    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));\n \n \tcommit = lookup_commit_reference(the_repository, oid);\n@@ -357,6 +358,7 @@ static void output(struct string_list *a, struct string_list *b,\n \t\t   struct diff_options *diffopt)\n {\n \tstruct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;\n+\tint patch_no_width = decimal_width(1 + (a->nr > b->nr ? a->nr : b->nr));\n \tint i = 0, j = 0;\n \n \t/*\n@@ -378,7 +380,7 @@ static void output(struct string_list *a, struct string_list *b,\n \n \t\t/* Show unmatched LHS commit whose predecessors were shown. */\n \t\tif (i < a->nr && a_util->matching < 0) {\n-\t\t\toutput_pair_header(diffopt,\n+\t\t\toutput_pair_header(diffopt, patch_no_width,\n \t\t\t\t\t   &buf, &dashes, a_util, NULL);\n \t\t\ti++;\n \t\t\tcontinue;\n@@ -386,7 +388,7 @@ static void output(struct string_list *a, struct string_list *b,\n \n \t\t/* Show unmatched RHS commits. */\n \t\twhile (j < b->nr && b_util->matching < 0) {\n-\t\t\toutput_pair_header(diffopt,\n+\t\t\toutput_pair_header(diffopt, patch_no_width,\n \t\t\t\t\t   &buf, &dashes, NULL, b_util);\n \t\t\tb_util = ++j < b->nr ? b->items[j].util : NULL;\n \t\t}\n@@ -394,7 +396,7 @@ static void output(struct string_list *a, struct string_list *b,\n \t\t/* Show matching LHS/RHS pair. */\n \t\tif (j < b->nr) {\n \t\t\ta_util = a->items[b_util->matching].util;\n-\t\t\toutput_pair_header(diffopt,\n+\t\t\toutput_pair_header(diffopt, patch_no_width,\n \t\t\t\t\t   &buf, &dashes, a_util, b_util);\n \t\t\tif (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))\n \t\t\t\tpatch_diff(a->items[b_util->matching].string,\n-- \ngitgitgadget\n\n"},{"id":"355342","messageId":"f6fd3955eba9cf0f647c53bead92a810b4dd70fa.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 20/21] range-diff: make --dual-color the default mode","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:30Z","receivedAt":"2018-08-13T11:33:34Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nAfter using this command extensively for the last two months, this\ndeveloper came to the conclusion that even if the dual color mode still\nleaves a lot of room for confusion about what was actually changed, the\nnon-dual color mode is substantially worse in that regard.\n\nTherefore, we really want to make the dual color mode the default.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-range-diff.txt       | 32 +++++++++++++++-----------\n builtin/range-diff.c                   | 10 ++++----\n contrib/completion/git-completion.bash |  2 +-\n 3 files changed, 25 insertions(+), 19 deletions(-)\n\ndiff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\nindex bebb47d42..82c71c682 100644\n--- a/Documentation/git-range-diff.txt\n+++ b/Documentation/git-range-diff.txt\n@@ -9,7 +9,7 @@ SYNOPSIS\n --------\n [verse]\n 'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]\n-\t[--dual-color] [--creation-factor=<factor>]\n+\t[--no-dual-color] [--creation-factor=<factor>]\n \t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n \n DESCRIPTION\n@@ -31,11 +31,14 @@ all of their ancestors have been shown.\n \n OPTIONS\n -------\n---dual-color::\n-\tWhen the commit diffs differ, recreate the original diffs'\n-\tcoloring, and add outer -/+ diff markers with the *background*\n-\tbeing red/green to make it easier to see e.g. when there was a\n-\tchange in what exact lines were added.\n+--no-dual-color::\n+\tWhen the commit diffs differ, `git range-diff` recreates the\n+\toriginal diffs' coloring, and adds outer -/+ diff markers with\n+\tthe *background* being red/green to make it easier to see e.g.\n+\twhen there was a change in what exact lines were added. This is\n+\tknown to `range-diff` as \"dual coloring\". Use `--no-dual-color`\n+\tto revert to color all lines according to the outer diff markers\n+\t(and completely ignore the inner diff when it comes to color).\n \n --creation-factor=<percent>::\n \tSet the creation/deletion cost fudge factor to `<percent>`.\n@@ -118,15 +121,16 @@ line (with a perfect match) is yellow like the commit header of `git\n show`'s output, and the third line colors the old commit red, the new\n one green and the rest like `git show`'s commit header.\n \n-The color-coded diff is actually a bit hard to read, though, as it\n-colors the entire lines red or green. The line that added \"What is\n-unexpected\" in the old commit, for example, is completely red, even if\n-the intent of the old commit was to add something.\n+A naive color-coded diff of diffs is actually a bit hard to read,\n+though, as it colors the entire lines red or green. The line that added\n+\"What is unexpected\" in the old commit, for example, is completely red,\n+even if the intent of the old commit was to add something.\n \n-To help with that, use the `--dual-color` mode. In this mode, the diff\n-of diffs will retain the original diff colors, and prefix the lines with\n--/+ markers that have their *background* red or green, to make it more\n-obvious that they describe how the diff itself changed.\n+To help with that, `range` uses the `--dual-color` mode by default. In\n+this mode, the diff of diffs will retain the original diff colors, and\n+prefix the lines with -/+ markers that have their *background* red or\n+green, to make it more obvious that they describe how the diff itself\n+changed.\n \n \n Algorithm\ndiff --git a/builtin/range-diff.c b/builtin/range-diff.c\nindex 5a9ad82fb..f52d45d9d 100644\n--- a/builtin/range-diff.c\n+++ b/builtin/range-diff.c\n@@ -20,11 +20,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n {\n \tint creation_factor = 60;\n \tstruct diff_options diffopt = { NULL };\n-\tint dual_color = 0;\n+\tint simple_color = -1;\n \tstruct option options[] = {\n \t\tOPT_INTEGER(0, \"creation-factor\", &creation_factor,\n \t\t\t    N_(\"Percentage by which creation is weighted\")),\n-\t\tOPT_BOOL(0, \"dual-color\", &dual_color,\n+\t\tOPT_BOOL(0, \"no-dual-color\", &simple_color,\n \t\t\t    N_(\"color both diff and diff-between-diffs\")),\n \t\tOPT_END()\n \t};\n@@ -63,8 +63,10 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n \t\t\t     options + ARRAY_SIZE(options) - 1, /* OPT_END */\n \t\t\t     builtin_range_diff_usage, 0);\n \n-\tif (dual_color) {\n-\t\tdiffopt.use_color = 1;\n+\tif (simple_color < 1) {\n+\t\tif (!simple_color)\n+\t\t\t/* force color when --dual-color was used */\n+\t\t\tdiffopt.use_color = 1;\n \t\tdiffopt.flags.dual_color_diffed_diffs = 1;\n \t}\n \ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 3d4ec3432..d63d2dffd 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1981,7 +1981,7 @@ _git_range_diff ()\n \tcase \"$cur\" in\n \t--*)\n \t\t__gitcomp \"\n-\t\t\t--creation-factor= --dual-color\n+\t\t\t--creation-factor= --no-dual-color\n \t\t\t$__git_diff_common_options\n \t\t\"\n \t\treturn\n-- \ngitgitgadget\n\n"},{"id":"355343","messageId":"699cd712e2aa56ad7bb1356b72caf8d4738a18e5.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 21/21] range-diff: use dim/bold cues to improve dual color mode","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:32Z","receivedAt":"2018-08-13T11:33:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nIt *is* a confusing thing to look at a diff of diffs. All too easy is it\nto mix up whether the -/+ markers refer to the \"inner\" or the \"outer\"\ndiff, i.e. whether a `+` indicates that a line was added by either the\nold or the new diff (or both), or whether the new diff does something\ndifferent than the old diff.\n\nTo make things easier to process for normal developers, we introduced\nthe dual color mode which colors the lines according to the commit diff,\ni.e. lines that are added by a commit (whether old, new, or both) are\ncolored in green. In non-dual color mode, the lines would be colored\naccording to the outer diff: if the old commit added a line, it would be\ncolored red (because that line addition is only present in the first\ncommit range that was specified on the command-line, i.e. the \"old\"\ncommit, but not in the second commit range, i.e. the \"new\" commit).\n\nHowever, this dual color mode is still not making things clear enough,\nas we are looking at two levels of diffs, and we still only pick a color\naccording to *one* of them (the outer diff marker is colored\ndifferently, of course, but in particular with deep indentation, it is\neasy to lose track of that outer diff marker's background color).\n\nTherefore, let's add another dimension to the mix. Still use\ngreen/red/normal according to the commit diffs, but now also dim the\nlines that were only in the old commit, and use bold face for the lines\nthat are only in the new commit.\n\nThat way, it is much easier not to lose track of, say, when we are\nlooking at a line that was added in the previous iteration of a patch\nseries but the new iteration adds a slightly different version: the\nobsolete change will be dimmed, the current version of the patch will be\nbold.\n\nAt least this developer has a much easier time reading the range-diffs\nthat way.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/config.txt         |  6 ++++--\n Documentation/git-range-diff.txt | 17 +++++++++++++----\n color.h                          |  6 ++++++\n diff.c                           | 28 ++++++++++++++++++++++------\n diff.h                           |  8 +++++++-\n 5 files changed, 52 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 63365dcf3..90241ed77 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1193,8 +1193,10 @@ color.diff.<slot>::\n \t(highlighting whitespace errors), `oldMoved` (deleted lines),\n \t`newMoved` (added lines), `oldMovedDimmed`, `oldMovedAlternative`,\n \t`oldMovedAlternativeDimmed`, `newMovedDimmed`, `newMovedAlternative`\n-\tand `newMovedAlternativeDimmed` (See the '<mode>'\n-\tsetting of '--color-moved' in linkgit:git-diff[1] for details).\n+\t`newMovedAlternativeDimmed` (See the '<mode>'\n+\tsetting of '--color-moved' in linkgit:git-diff[1] for details),\n+\t`contextDimmed`, `oldDimmed`, `newDimmed`, `contextBold`,\n+\t`oldBold`, and `newBold` (see linkgit:git-range-diff[1] for details).\n \n color.decorate.<slot>::\n \tUse customized color for 'git log --decorate' output.  `<slot>` is one\ndiff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt\nindex 82c71c682..f693930fd 100644\n--- a/Documentation/git-range-diff.txt\n+++ b/Documentation/git-range-diff.txt\n@@ -35,10 +35,19 @@ OPTIONS\n \tWhen the commit diffs differ, `git range-diff` recreates the\n \toriginal diffs' coloring, and adds outer -/+ diff markers with\n \tthe *background* being red/green to make it easier to see e.g.\n-\twhen there was a change in what exact lines were added. This is\n-\tknown to `range-diff` as \"dual coloring\". Use `--no-dual-color`\n-\tto revert to color all lines according to the outer diff markers\n-\t(and completely ignore the inner diff when it comes to color).\n+\twhen there was a change in what exact lines were added.\n++\n+Additionally, the commit diff lines that are only present in the first commit\n+range are shown \"dimmed\" (this can be overridden using the `color.diff.<slot>`\n+config setting where `<slot>` is one of `contextDimmed`, `oldDimmed` and\n+`newDimmed`), and the commit diff lines that are only present in the second\n+commit range are shown in bold (which can be overridden using the config\n+settings `color.diff.<slot>` with `<slot>` being one of `contextBold`,\n+`oldBold` or `newBold`).\n++\n+This is known to `range-diff` as \"dual coloring\". Use `--no-dual-color`\n+to revert to color all lines according to the outer diff markers\n+(and completely ignore the inner diff when it comes to color).\n \n --creation-factor=<percent>::\n \tSet the creation/deletion cost fudge factor to `<percent>`.\ndiff --git a/color.h b/color.h\nindex 33e786342..98894d6a1 100644\n--- a/color.h\n+++ b/color.h\n@@ -36,6 +36,12 @@ struct strbuf;\n #define GIT_COLOR_BOLD_BLUE\t\"\\033[1;34m\"\n #define GIT_COLOR_BOLD_MAGENTA\t\"\\033[1;35m\"\n #define GIT_COLOR_BOLD_CYAN\t\"\\033[1;36m\"\n+#define GIT_COLOR_FAINT_RED\t\"\\033[2;31m\"\n+#define GIT_COLOR_FAINT_GREEN\t\"\\033[2;32m\"\n+#define GIT_COLOR_FAINT_YELLOW\t\"\\033[2;33m\"\n+#define GIT_COLOR_FAINT_BLUE\t\"\\033[2;34m\"\n+#define GIT_COLOR_FAINT_MAGENTA\t\"\\033[2;35m\"\n+#define GIT_COLOR_FAINT_CYAN\t\"\\033[2;36m\"\n #define GIT_COLOR_BG_RED\t\"\\033[41m\"\n #define GIT_COLOR_BG_GREEN\t\"\\033[42m\"\n #define GIT_COLOR_BG_YELLOW\t\"\\033[43m\"\ndiff --git a/diff.c b/diff.c\nindex ea8ecae04..ae1314952 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -70,6 +70,12 @@ static char diff_colors[][COLOR_MAXLEN] = {\n \tGIT_COLOR_BOLD_YELLOW,\t/* NEW_MOVED ALTERNATIVE */\n \tGIT_COLOR_FAINT,\t/* NEW_MOVED_DIM */\n \tGIT_COLOR_FAINT_ITALIC,\t/* NEW_MOVED_ALTERNATIVE_DIM */\n+\tGIT_COLOR_FAINT,\t/* CONTEXT_DIM */\n+\tGIT_COLOR_FAINT_RED,\t/* OLD_DIM */\n+\tGIT_COLOR_FAINT_GREEN,\t/* NEW_DIM */\n+\tGIT_COLOR_BOLD,\t\t/* CONTEXT_BOLD */\n+\tGIT_COLOR_BOLD_RED,\t/* OLD_BOLD */\n+\tGIT_COLOR_BOLD_GREEN,\t/* NEW_BOLD */\n };\n \n static const char *color_diff_slots[] = {\n@@ -89,6 +95,12 @@ static const char *color_diff_slots[] = {\n \t[DIFF_FILE_NEW_MOVED_ALT]     = \"newMovedAlternative\",\n \t[DIFF_FILE_NEW_MOVED_DIM]     = \"newMovedDimmed\",\n \t[DIFF_FILE_NEW_MOVED_ALT_DIM] = \"newMovedAlternativeDimmed\",\n+\t[DIFF_CONTEXT_DIM]\t      = \"contextDimmed\",\n+\t[DIFF_FILE_OLD_DIM]\t      = \"oldDimmed\",\n+\t[DIFF_FILE_NEW_DIM]\t      = \"newDimmed\",\n+\t[DIFF_CONTEXT_BOLD]\t      = \"contextBold\",\n+\t[DIFF_FILE_OLD_BOLD]\t      = \"oldBold\",\n+\t[DIFF_FILE_NEW_BOLD]\t      = \"newBold\",\n };\n \n static NORETURN void die_want_option(const char *option_name)\n@@ -1294,11 +1306,13 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \n \t\t\tset_sign = set;\n \t\t\tif (c == '-')\n-\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD_BOLD);\n \t\t\telse if (c == '@')\n \t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n-\t\t\telse if (c != '+')\n-\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t\telse if (c == '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW_BOLD);\n+\t\t\telse\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT_BOLD);\n \t\t\tflags &= ~DIFF_SYMBOL_CONTENT_WS_MASK;\n \t\t}\n \t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n@@ -1336,11 +1350,13 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \n \t\t\tset_sign = set;\n \t\t\tif (c == '+')\n-\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW_DIM);\n \t\t\telse if (c == '@')\n \t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n-\t\t\telse if (c != '-')\n-\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t\telse if (c == '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD_DIM);\n+\t\t\telse\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT_DIM);\n \t\t}\n \t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '-',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);\ndiff --git a/diff.h b/diff.h\nindex cca4f9d6c..e1e54256c 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -248,7 +248,13 @@ enum color_diff {\n \tDIFF_FILE_NEW_MOVED = 13,\n \tDIFF_FILE_NEW_MOVED_ALT = 14,\n \tDIFF_FILE_NEW_MOVED_DIM = 15,\n-\tDIFF_FILE_NEW_MOVED_ALT_DIM = 16\n+\tDIFF_FILE_NEW_MOVED_ALT_DIM = 16,\n+\tDIFF_CONTEXT_DIM = 17,\n+\tDIFF_FILE_OLD_DIM = 18,\n+\tDIFF_FILE_NEW_DIM = 19,\n+\tDIFF_CONTEXT_BOLD = 20,\n+\tDIFF_FILE_OLD_BOLD = 21,\n+\tDIFF_FILE_NEW_BOLD = 22,\n };\n const char *diff_get_color(int diff_use_color, enum color_diff ix);\n #define diff_get_color_opt(o, ix) \\\n-- \ngitgitgadget\n"},{"id":"355344","messageId":"f1c86f606e018c34230dc016ffdc25a3c70bf840.1534159977.git.gitgitgadget@gmail.com","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 14/21] diff: add an internal option to dual-color diffs of diffs","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2018-08-13T11:33:20Z","receivedAt":"2018-08-13T11:33:39Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen diffing diffs, it can be quite daunting to figure out what the heck\nis going on, as there are nested +/- signs.\n\nLet's make this easier by adding a flag in diff_options that allows\ncolor-coding the outer diff sign with inverted colors, so that the\npreimage and postimage is colored like the diff it is.\n\nOf course, this really only makes sense when the preimage and postimage\n*are* diffs. So let's not expose this flag via a command-line option for\nnow.\n\nThis is a feature that was invented by git-tbdiff, and it will be used\nby `git range-diff` in the next commit, by offering it via a new option:\n`--dual-color`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n diff.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++-----------\n diff.h |  1 +\n 2 files changed, 69 insertions(+), 15 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 9c4bd9fa1..e6c857abf 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -609,14 +609,18 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,\n \tecbdata->blank_at_eof_in_postimage = (at - l2) + 1;\n }\n \n-static void emit_line_0(struct diff_options *o, const char *set, const char *reset,\n+static void emit_line_0(struct diff_options *o,\n+\t\t\tconst char *set, unsigned reverse, const char *reset,\n \t\t\tint first, const char *line, int len)\n {\n \tint has_trailing_newline, has_trailing_carriage_return;\n \tint nofirst;\n \tFILE *file = o->file;\n \n-\tfputs(diff_line_prefix(o), file);\n+\tif (first)\n+\t\tfputs(diff_line_prefix(o), file);\n+\telse if (!len)\n+\t\treturn;\n \n \tif (len == 0) {\n \t\thas_trailing_newline = (first == '\\n');\n@@ -634,8 +638,10 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res\n \t}\n \n \tif (len || !nofirst) {\n+\t\tif (reverse && want_color(o->use_color))\n+\t\t\tfputs(GIT_COLOR_REVERSE, file);\n \t\tfputs(set, file);\n-\t\tif (!nofirst)\n+\t\tif (first && !nofirst)\n \t\t\tfputc(first, file);\n \t\tfwrite(line, len, 1, file);\n \t\tfputs(reset, file);\n@@ -649,7 +655,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res\n static void emit_line(struct diff_options *o, const char *set, const char *reset,\n \t\t      const char *line, int len)\n {\n-\temit_line_0(o, set, reset, line[0], line+1, len-1);\n+\temit_line_0(o, set, 0, reset, line[0], line+1, len-1);\n }\n \n enum diff_symbol {\n@@ -1168,7 +1174,8 @@ static void dim_moved_lines(struct diff_options *o)\n \n static void emit_line_ws_markup(struct diff_options *o,\n \t\t\t\tconst char *set, const char *reset,\n-\t\t\t\tconst char *line, int len, char sign,\n+\t\t\t\tconst char *line, int len,\n+\t\t\t\tconst char *set_sign, char sign,\n \t\t\t\tunsigned ws_rule, int blank_at_eof)\n {\n \tconst char *ws = NULL;\n@@ -1179,14 +1186,20 @@ static void emit_line_ws_markup(struct diff_options *o,\n \t\t\tws = NULL;\n \t}\n \n-\tif (!ws)\n-\t\temit_line_0(o, set, reset, sign, line, len);\n-\telse if (blank_at_eof)\n+\tif (!ws && !set_sign)\n+\t\temit_line_0(o, set, 0, reset, sign, line, len);\n+\telse if (!ws) {\n+\t\t/* Emit just the prefix, then the rest. */\n+\t\temit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n+\t\t\t    sign, \"\", 0);\n+\t\temit_line_0(o, set, 0, reset, 0, line, len);\n+\t} else if (blank_at_eof)\n \t\t/* Blank line at EOF - paint '+' as well */\n-\t\temit_line_0(o, ws, reset, sign, line, len);\n+\t\temit_line_0(o, ws, 0, reset, sign, line, len);\n \telse {\n \t\t/* Emit just the prefix, then the rest. */\n-\t\temit_line_0(o, set, reset, sign, \"\", 0);\n+\t\temit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,\n+\t\t\t    sign, \"\", 0);\n \t\tws_check_emit(line, len, ws_rule,\n \t\t\t      o->file, set, reset, ws);\n \t}\n@@ -1196,7 +1209,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\t\t\t struct emitted_diff_symbol *eds)\n {\n \tstatic const char *nneof = \" No newline at end of file\\n\";\n-\tconst char *context, *reset, *set, *meta, *fraginfo;\n+\tconst char *context, *reset, *set, *set_sign, *meta, *fraginfo;\n \tstruct strbuf sb = STRBUF_INIT;\n \n \tenum diff_symbol s = eds->s;\n@@ -1209,7 +1222,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\tcontext = diff_get_color_opt(o, DIFF_CONTEXT);\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n \t\tputc('\\n', o->file);\n-\t\temit_line_0(o, context, reset, '\\\\',\n+\t\temit_line_0(o, context, 0, reset, '\\\\',\n \t\t\t    nneof, strlen(nneof));\n \t\tbreak;\n \tcase DIFF_SYMBOL_SUBMODULE_HEADER:\n@@ -1236,7 +1249,18 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \tcase DIFF_SYMBOL_CONTEXT:\n \t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n-\t\temit_line_ws_markup(o, set, reset, line, len, ' ',\n+\t\tset_sign = NULL;\n+\t\tif (o->flags.dual_color_diffed_diffs) {\n+\t\t\tchar c = !len ? 0 : line[0];\n+\n+\t\t\tif (c == '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n+\t\t\telse if (c == '@')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n+\t\t\telse if (c == '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n+\t\t}\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',\n \t\t\t\t    flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);\n \t\tbreak;\n \tcase DIFF_SYMBOL_PLUS:\n@@ -1263,7 +1287,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n \t\t}\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n-\t\temit_line_ws_markup(o, set, reset, line, len, '+',\n+\t\tif (!o->flags.dual_color_diffed_diffs)\n+\t\t\tset_sign = NULL;\n+\t\telse {\n+\t\t\tchar c = !len ? 0 : line[0];\n+\n+\t\t\tset_sign = set;\n+\t\t\tif (c == '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n+\t\t\telse if (c == '@')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n+\t\t\telse if (c != '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t}\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '+',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK,\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);\n \t\tbreak;\n@@ -1291,7 +1328,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,\n \t\t\tset = diff_get_color_opt(o, DIFF_FILE_OLD);\n \t\t}\n \t\treset = diff_get_color_opt(o, DIFF_RESET);\n-\t\temit_line_ws_markup(o, set, reset, line, len, '-',\n+\t\tif (!o->flags.dual_color_diffed_diffs)\n+\t\t\tset_sign = NULL;\n+\t\telse {\n+\t\t\tchar c = !len ? 0 : line[0];\n+\n+\t\t\tset_sign = set;\n+\t\t\tif (c == '+')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FILE_NEW);\n+\t\t\telse if (c == '@')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_FRAGINFO);\n+\t\t\telse if (c != '-')\n+\t\t\t\tset = diff_get_color_opt(o, DIFF_CONTEXT);\n+\t\t}\n+\t\temit_line_ws_markup(o, set, reset, line, len, set_sign, '-',\n \t\t\t\t    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);\n \t\tbreak;\n \tcase DIFF_SYMBOL_WORDS_PORCELAIN:\n@@ -1482,6 +1532,7 @@ static void emit_hunk_header(struct emit_callback *ecbdata,\n \tconst char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);\n \tconst char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);\n \tconst char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);\n+\tconst char *reverse = ecbdata->color_diff ? GIT_COLOR_REVERSE : \"\";\n \tstatic const char atat[2] = { '@', '@' };\n \tconst char *cp, *ep;\n \tstruct strbuf msgbuf = STRBUF_INIT;\n@@ -1502,6 +1553,8 @@ static void emit_hunk_header(struct emit_callback *ecbdata,\n \tep += 2; /* skip over @@ */\n \n \t/* The hunk header in fraginfo color */\n+\tif (ecbdata->opt->flags.dual_color_diffed_diffs)\n+\t\tstrbuf_addstr(&msgbuf, reverse);\n \tstrbuf_addstr(&msgbuf, frag);\n \tstrbuf_add(&msgbuf, line, ep - line);\n \tstrbuf_addstr(&msgbuf, reset);\ndiff --git a/diff.h b/diff.h\nindex d88ceb357..cca4f9d6c 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -95,6 +95,7 @@ struct diff_flags {\n \tunsigned default_follow_renames:1;\n \tunsigned stat_with_summary:1;\n \tunsigned suppress_diff_headers:1;\n+\tunsigned dual_color_diffed_diffs:1;\n };\n \n static inline void diff_flags_or(struct diff_flags *a,\n-- \ngitgitgadget\n\n"},{"id":"355345","messageId":"nycvar.QRO.7.76.6.1808131337360.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"pull.1.v6.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 00/21] Add range-diff, a tbdiff lookalike","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-08-13T11:38:29Z","receivedAt":"2018-08-13T11:38:34Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Mon, 13 Aug 2018, Johannes Schindelin via GitGitGadget wrote:\n\n> The incredibly useful git-tbdiff [https://github.com/trast/tbdiff] tool to\n> compare patch series (say, to see what changed between two iterations sent\n> to the Git mailing list) is slightly less useful for this developer due to\n> the fact that it requires the hungarian and numpy Python packages which are\n> for some reason really hard to build in MSYS2. So hard that I even had to\n> give up, because it was simply easier to re-implement the whole shebang as a\n> builtin command.\n> \n> The project at https://github.com/trast/tbdiff seems to be dormant, anyway.\n> Funny (and true) story: I looked at the open Pull Requests to see how active\n> that project is, only to find to my surprise that I had submitted one in\n> August 2015, and that it was still unanswered let alone merged.\n> \n> While at it, I forward-ported AEvar's patch to force --decorate=no because \n> git -p tbdiff would fail otherwise.\n> \n> Side note: I work on implementing range-diff not only to make life easier\n> for reviewers who have to suffer through v2, v3, ... of my patch series, but\n> also to verify my changes before submitting a new iteration. And also, maybe\n> even more importantly, I plan to use it to verify my merging-rebases of Git\n> for Windows (for which I previously used to redirect the\n> pre-rebase/post-rebase diffs vs upstream and then compare them using git\n> diff --no-index). And of course any interested person can see what changes\n> were necessary e.g. in the merging-rebase of Git for Windows onto v2.17.0 by\n> running a command like:\n> \n>         base=^{/Start.the.merging-rebase}\n>         tag=v2.17.0.windows.1\n>         pre=$tag$base^2\n>         git range-diff $pre$base..$pre $tag$base..$tag\n> \n> The command uses what it calls the \"dual color mode\" (can be disabled via \n> --no-dual-color) which helps identifying what actually changed: it prefixes\n> lines with a - (and red background) that correspond to the first commit\n> range, and with a + (and green background) that correspond to the second\n> range. The rest of the lines will be colored according to the original\n> diffs.\n\nChanges since v5:\n\n- Fixed the bug (introduced in v5) where a dashdash would not be handled\n  appropriately.\n\n> Changes since v4:\n> \n>  * Fixed a typo in the commit message of \"range-diff: add tests\" that was\n>    introduced in v4.\n>  * White-space fixes.\n>  * Fixed the length of the first header underline in the man page.\n>  * Changed the preprocessor guard in linear-assignment.h to reflect the new\n>    name (instead of the old name, which was hungarian.h).\n>  * Likewise, changed the preprocessor guards in range-diff.h to hide the\n>    history of the thrice-renamed command.\n>  * Fixed indentation in the completion.\n>  * Instead of trying to paper over white-space error handling that does not\n>    apply to \"diffs of diffs\", dual color mode now simply disables all\n>    white-space warnings.\n>  * When showing the \"single arg must be symmetric range\" error message, git\n>    range-diff now also shows the usage.\n>  * Adjusted the commit message of \"range-diff: adjust the output of the\n>    commit pairs\" to avoid the surprise of the reviewer when onelines are\n>    printed all of a sudden, too.\n>  * \"range-diff: adjust the output of the commit pairs\" is now using a\n>    simpler way to print onelines.\n>  * We are now sandwiching the diff_opt_parse() loop between two \n>    parse_options(), to make sure that we caught all options, and that the -- \n>    separator is handled.\n>  * Adjusted the lookup_commit_reference() call to the newest master (it now\n>    takes a the_repository parameter).\n> \n> Changes since v3:\n> \n>  * The cover letter was adjusted to reflect the new reality (the command is\n>    called range-diff now, not branch-diff, and --dual-color is the default).\n>  * The documentation was adjusted a bit more in the patch that makes \n>    --dual-color the default.\n>  * Clarified the calculation of the cost matrix, as per Stefan Beller's\n>    request.\n>  * The man page now spells out that merge commits are ignored in the commit\n>    ranges (not merges per se).\n>  * The code in linear-assignment.c was adjusted to use the SWAP() macro.\n>  * The commit message of the patch introducing the first rudimentary\n>    implementation no longer talks about the \"Hungarian\" algorithm, but about\n>    the \"linear assignment algorithm\" instead.\n>  * A bogus indentation change was backed out from the patch introducing the\n>    first rudimentary implementation.\n>  * Instead of merely warning about missing .. in the 2-parameter invocation,\n>    we now exit with the error message.\n>  * The diff_opt_parse() function is allowed to return a value larger than 1,\n>    indicating that more than just one command-line parameter was parsed. We\n>    now advance by the indicated value instead of always advancing exactly 1\n>    (which is still correct much of the time).\n>  * A lengthy if...else if...else if...else was simplified (from a logical\n>    point of view) by reordering it.\n>  * The unnecessarily static variable dashes was turned into a local variable\n>    of the caller.\n>  * The commit message talking about the new man page still referred to git\n>    branch --diff, which has been fixed.\n>  * A forgotten t7910 reference was changed to t3206.\n>  * An unbalanced double-tick was fixed in the man page.\n>  * Fixed grammar both of the commit message and the description of the \n>    --no-dual-color option.\n>  * To fix the build, a blank man page is now introduced together with the\n>    new range-diff command, even if it is populated for real only at a later\n>    patch (i.e. at the same time as before).\n>  * The headaches Junio fears would be incurred by that simple workaround to\n>    avoid bogus white-space error reporting are fended off: a more complex\n>    patch is now in place that adds (and uses) a new white-space flag. Sadly,\n>    as is all too common when Junio \"encourages\" me to replace a simple\n>    workaround by something \"proper\", it caused all kinds of headaches to get\n>    this right, so I am rather less certain that the \"proper\" fix will cause\n>    us less headaches than the simple workaround would have done. But\n>    whatever.\n>  * The dual color mode now also dims the changes that are exclusively in the\n>    first specified commit range, and uses bold face on the changes\n>    exclusively in the second one. This matches the intuition when using \n>    range-diff to compare an older iteration of a patch series to a newer\n>    one: the changes from the previous iteration that were replaced by new\n>    ones \"fade\", while the changes that replace them are \"shiny new\".\n> \n> Changes since v2:\n> \n>  * Right-aligned the patch numbers in the commit pairs.\n>  * Used ALLOC_ARRAY() in hungarian.c instead of xmalloc(sizeof()*size).\n>  * Changed compute_assignment()s return type from int to void, as it always\n>    succeeds.\n>  * Changed the Hungarian Algorithm to use an integer cost matrix.\n>  * Changed the --creation-weight option to --creation-factor where is an\n>    integer.\n>  * Retitled 1/19 and 2/19 to better conform with the current conventions, as\n>    pointed out (and suggested) by Junio.\n>  * Shut up Coverity, and at the same time avoided passing the unnecessary i \n>    and j parameters to output_pair_header().\n>  * Removed support for the --no-patches option: we inherit diff_options'\n>    support for -s already (and much more).\n>  * Removed the ugly _INV enum values, and introduced a beautiful\n>    GIT_COLOR_REVERSE instead. This way, whatever the user configured as\n>    color.diff.new (or .old) will be used in reverse in the dual color mode.\n>  * Instead of overriding the fragment header color, the dual color mode will\n>    now reverse the \"outer\" fragment headers, too.\n>  * Turned the stand-alone branch-diff command into the --diff option of git\n>    branch. Adjusted pretty much all commit messages to account for this.\n>    This change should no longer be visible: see below.\n>  * Pretty much re-wrote the completion, to support the new --diff mode of\n>    git-branch. See below: it was reverted for range-diff.\n>  * Renamed t7910 to t3206, to be closer to the git-branch tests.\n>  * Ensured that git_diff_ui_config() gets called, and therefore color.diff.*\n>    respected.\n>  * Avoided leaking four_spaces.\n>  * Fixed a declaration in a for (;;) statement (which Junio had as a fixup!\n>    that I almost missed).\n>  * Renamed branch --diff, which had been renamed from branch-diff (which was\n>    picked to avoid re-using tbdiff) to range-diff.\n>  * Renamed hungarian.c and its header to linear-assignment.c\n>  * Made --dual-color the default, and changed it to still auto-detect\n>    whether color should be used rather than forcing it\n> \n> Johannes Schindelin (20):\n>   linear-assignment: a function to solve least-cost assignment problems\n>   Introduce `range-diff` to compare iterations of a topic branch\n>   range-diff: first rudimentary implementation\n>   range-diff: improve the order of the shown commits\n>   range-diff: also show the diff between patches\n>   range-diff: right-trim commit messages\n>   range-diff: indent the diffs just like tbdiff\n>   range-diff: suppress the diff headers\n>   range-diff: adjust the output of the commit pairs\n>   range-diff: do not show \"function names\" in hunk headers\n>   range-diff: use color for the commit pairs\n>   color: add the meta color GIT_COLOR_REVERSE\n>   diff: add an internal option to dual-color diffs of diffs\n>   range-diff: offer to dual-color the diffs\n>   range-diff --dual-color: skip white-space warnings\n>   range-diff: populate the man page\n>   completion: support `git range-diff`\n>   range-diff: left-pad patch numbers\n>   range-diff: make --dual-color the default mode\n>   range-diff: use dim/bold cues to improve dual color mode\n> \n> Thomas Rast (1):\n>   range-diff: add tests\n> \n>  .gitignore                             |   1 +\n>  Documentation/config.txt               |   6 +-\n>  Documentation/git-range-diff.txt       | 252 +++++++++++\n>  Makefile                               |   3 +\n>  builtin.h                              |   1 +\n>  builtin/range-diff.c                   | 116 +++++\n>  color.h                                |   7 +\n>  command-list.txt                       |   1 +\n>  contrib/completion/git-completion.bash |  14 +\n>  diff.c                                 | 105 ++++-\n>  diff.h                                 |  10 +-\n>  git.c                                  |   1 +\n>  linear-assignment.c                    | 201 ++++++++\n>  linear-assignment.h                    |  22 +\n>  range-diff.c                           | 435 ++++++++++++++++++\n>  range-diff.h                           |   9 +\n>  t/.gitattributes                       |   1 +\n>  t/t3206-range-diff.sh                  | 145 ++++++\n>  t/t3206/history.export                 | 604 +++++++++++++++++++++++++\n>  19 files changed, 1915 insertions(+), 19 deletions(-)\n>  create mode 100644 Documentation/git-range-diff.txt\n>  create mode 100644 builtin/range-diff.c\n>  create mode 100644 linear-assignment.c\n>  create mode 100644 linear-assignment.h\n>  create mode 100644 range-diff.c\n>  create mode 100644 range-diff.h\n>  create mode 100755 t/t3206-range-diff.sh\n>  create mode 100644 t/t3206/history.export\n> \n> \n> base-commit: 1d89318c48d233d52f1db230cf622935ac3c69fa\n> Published-As: https://github.com/gitgitgadget/git/releases/tags/pr-1%2Fdscho%2Fbranch-diff-v6\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1/dscho/branch-diff-v6\n> Pull-Request: https://github.com/gitgitgadget/git/pull/1\n> \n> Range-diff vs v5:\n> \n>   1:  f168da3a3 =  1:  f168da3a3 linear-assignment: a function to solve least-cost assignment problems\n>   2:  33758f361 =  2:  33758f361 Introduce `range-diff` to compare iterations of a topic branch\n>   3:  08b8c3fc4 =  3:  08b8c3fc4 range-diff: first rudimentary implementation\n>   4:  7b9091968 =  4:  7b9091968 range-diff: improve the order of the shown commits\n>   5:  9e1e66007 !  5:  8515d2f75 range-diff: also show the diff between patches\n>      @@ -80,6 +80,8 @@\n>       +\t\telse\n>       +\t\t\ti += c;\n>       +\t}\n>      ++\twhile (i < argc)\n>      ++\t\targv[j++] = argv[i++];\n>       +\targc = j;\n>       +\tdiff_setup_done(&diffopt);\n>       +\n>   6:  167ca02a3 =  6:  a10ca0163 range-diff: right-trim commit messages\n>   7:  ca8de8c75 =  7:  f81cbef2c range-diff: indent the diffs just like tbdiff\n>   8:  eb94d1982 =  8:  458090ffd range-diff: suppress the diff headers\n>   9:  6330afad9 =  9:  d3be03a44 range-diff: adjust the output of the commit pairs\n>  10:  c296675eb = 10:  94b44dfe6 range-diff: do not show \"function names\" in hunk headers\n>  11:  85e0ab82f = 11:  1477c58e4 range-diff: add tests\n>  12:  f48b62644 = 12:  32492c159 range-diff: use color for the commit pairs\n>  13:  1ad74f939 = 13:  969a196f4 color: add the meta color GIT_COLOR_REVERSE\n>  14:  39a0ecd28 = 14:  f1c86f606 diff: add an internal option to dual-color diffs of diffs\n>  15:  c32a24f6a = 15:  3c7b9f339 range-diff: offer to dual-color the diffs\n>  16:  05947781f = 16:  c56c51c8b range-diff --dual-color: skip white-space warnings\n>  17:  3147c4440 = 17:  8c5543a06 range-diff: populate the man page\n>  18:  b08e6d937 = 18:  16e3cf27b completion: support `git range-diff`\n>  19:  19406283e = 19:  d9b09abcf range-diff: left-pad patch numbers\n>  20:  6b3552386 = 20:  f6fd3955e range-diff: make --dual-color the default mode\n>  21:  ccf8c1bb2 = 21:  699cd712e range-diff: use dim/bold cues to improve dual color mode\n> \n> -- \n> gitgitgadget\n> \n> \n"},{"id":"355420","messageId":"xmqqzhxq5fa5.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"c56c51c8bb470ec9a498e984854f5154acf86ef2.1534159977.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 16/21] range-diff --dual-color: skip white-space warnings","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-08-13T17:48:18Z","receivedAt":"2018-08-13T17:48:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n>\n> When displaying a diff of diffs, it is possible that there is an outer\n> `+` before a context line. That happens when the context changed between\n> old and new commit. When that context line starts with a tab (after the\n> space that marks it as context line), our diff machinery spits out a\n> white-space error (space before tab), but in this case, that is\n> incorrect.\n\n    Also it is possible that there is an outer `+`, ` `, or `-`\n    before an added line (i.e. the second column is `+`), and that\n    line introduces a leading whitespace error (e.g. the whole line\n    begins with two plusses, SP and HT, meaning that the new\n    iteration of the patch introduces a space-before-tab whitespace\n    error), but feeding that to our normal diff machinery would of\n    course not catch it as introducing a new whitespace error.\n\nI think it is a good design decision to give up showing whitespace\nerrors correctly in this round, because the problem is not limited\nto the first space on an outer context line.  The next paragraph\nwould want to lose the mention of the \"Rather than\".  We do want to\nlist the problems we know about for future reference, but that is\nwhat we already did in the above paragraph, so all we need to say\nafter this point in the next paragraph would be \"let's punt and\nleave whitespace error highlighting for future enhancements\" or\nsomething like that.\n\nWith the dim/bold cues step applied, the resulting dual-color mode\nshows the differences between the previous few rounds and this one\npretty nicely.  After seeing the huccup between v5 and v6, I am a\nbit hesitant to merge this immediately to 'next' but hopefully we\ncan have it there in a few days (I definitely will merge the topic\nto my previate edition that I use for my everyday work during this\nintegration cycle).\n\nThanks.\n"},{"id":"355421","messageId":"20180813180150.GC13316@hank.intra.tgummerer.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1808131135290.71@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v5 05/21] range-diff: also show the diff between patches","fromName":"Thomas Gummerer","fromEmail":"t.gummerer@gmail.com","sentAt":"2018-08-13T18:01:50Z","receivedAt":"2018-08-13T18:01:59Z","isPatch":true,"sender":{"key":"t.gummerer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/191004?v=4"},"body":"On 08/13, Johannes Schindelin wrote:\n> Hi Thomas,\n> \n> On Sun, 12 Aug 2018, Thomas Gummerer wrote:\n> \n> > On 08/10, Johannes Schindelin via GitGitGadget wrote:\n> > > From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> >\n> > [...]\n> > \n> > I don't think this handles \"--\" quite as would be expected.  Trying to\n> > use \"git range-diff -- js/range-diff-v4...HEAD\" I get:\n> > \n> >     $ ./git range-diff -- js/range-diff-v4...HEAD\n> >     error: need two commit ranges\n> >     usage: git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>\n> >        or: git range-diff [<options>] <old-tip>...<new-tip>\n> >        or: git range-diff [<options>] <base> <old-tip> <new-tip>\n> > \n> >         --creation-factor <n>\n> >                               Percentage by which creation is weighted\n> >         --no-dual-color       color both diff and diff-between-diffs\n> > \n> > while what I would have expected is to actually get a range diff.\n> > This happens because after we break out of the loop we don't add the\n> > actual ranges to argv, but just skip them instead.\n> \n> Ouch, good point.\n> \n> > I think something like the following should be squashed in to this\n> > patch.\n> > \n> > --->8---\n> > diff --git a/builtin/range-diff.c b/builtin/range-diff.c\n> > index ef3ba22e29..132574c57a 100644\n> > --- a/builtin/range-diff.c\n> > +++ b/builtin/range-diff.c\n> > @@ -53,6 +53,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)\n> >                 else\n> >                         i += c;\n> >         }\n> > +       if (i < argc && !strcmp(\"--\", argv[i])) {\n> > +               i++; j++;\n> > +               while (i < argc)\n> > +                       argv[j++] = argv[i++];\n> > +       }\n> >         argc = j;\n> >         diff_setup_done(&diffopt);\n> \n> I do not think that is correct. The original idea was for the first\n> `parse_options()` call to keep the dashdash, for the second one to keep\n> the dashdash, too, and for the final one to swallow it.\n> \n> Also, if `i < argc` at this point, we already know that `argv[i]` refers\n> to the dashdash, otherwise the previous loop would not have exited early.\n> \n> I went with this simple version instead:\n> \n> \twhile (i < argc)\n> \t\targv[j++] = argv[i++];\n\nRight, that's much better, thanks!\n\n> Thanks!\n> Dscho\n\n"},{"id":"355437","messageId":"20180813183504.GD13316@hank.intra.tgummerer.com","threadId":"48405","inReplyTo":"1477c58e4c0c9e1d5836ffcb9bcb7360b1a5df0d.1534159977.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 11/21] range-diff: add tests","fromName":"Thomas Gummerer","fromEmail":"t.gummerer@gmail.com","sentAt":"2018-08-13T18:35:04Z","receivedAt":"2018-08-13T18:35:10Z","isPatch":true,"sender":{"key":"t.gummerer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/191004?v=4"},"body":"On 08/13, Thomas Rast via GitGitGadget wrote:\n> From: Thomas Rast <tr@thomasrast.ch>\n> \n> These are essentially lifted from https://github.com/trast/tbdiff, with\n> light touch-ups to account for the command now being named `git\n> range-diff`.\n> \n> Apart from renaming `tbdiff` to `range-diff`, only one test case needed\n> to be adjusted: 11 - 'changed message'.\n> \n> The underlying reason it had to be adjusted is that diff generation is\n> sometimes ambiguous. In this case, a comment line and an empty line are\n> added, but it is ambiguous whether they were added after the existing\n> empty line, or whether an empty line and the comment line are added\n> *before* the existing empty line. And apparently xdiff picks a different\n> option here than Python's difflib.\n>\n\nJust noticed while reading the whole series again (hopefully for the\nlast time :)), do we need Thomas Rast's Sign-off here, as he is\ncredited as the author here? \n\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  t/.gitattributes       |   1 +\n>  t/t3206-range-diff.sh  | 145 ++++++++++\n>  t/t3206/history.export | 604 +++++++++++++++++++++++++++++++++++++++++\n>  3 files changed, 750 insertions(+)\n>  create mode 100755 t/t3206-range-diff.sh\n>  create mode 100644 t/t3206/history.export\n> \n> diff --git a/t/.gitattributes b/t/.gitattributes\n> index 3bd959ae5..b17bf71b8 100644\n> --- a/t/.gitattributes\n> +++ b/t/.gitattributes\n> @@ -1,6 +1,7 @@\n>  t[0-9][0-9][0-9][0-9]/* -whitespace\n>  /diff-lib/* eol=lf\n>  /t0110/url-* binary\n> +/t3206/* eol=lf\n>  /t3900/*.txt eol=lf\n>  /t3901/*.txt eol=lf\n>  /t4034/*/* eol=lf\n> diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh\n> new file mode 100755\n> index 000000000..2237c7f4a\n> --- /dev/null\n> +++ b/t/t3206-range-diff.sh\n> @@ -0,0 +1,145 @@\n> +#!/bin/sh\n> +\n> +test_description='range-diff tests'\n> +\n> +. ./test-lib.sh\n> +\n> +# Note that because of the range-diff's heuristics, test_commit does more\n> +# harm than good.  We need some real history.\n> +\n> +test_expect_success 'setup' '\n> +\tgit fast-import < \"$TEST_DIRECTORY\"/t3206/history.export\n> +'\n> +\n> +test_expect_success 'simple A..B A..C (unmodified)' '\n> +\tgit range-diff --no-color master..topic master..unmodified \\\n> +\t\t>actual &&\n> +\tcat >expected <<-EOF &&\n> +\t1:  4de457d = 1:  35b9b25 s/5/A/\n> +\t2:  fccce22 = 2:  de345ab s/4/A/\n> +\t3:  147e64e = 3:  9af6654 s/11/B/\n> +\t4:  a63e992 = 4:  2901f77 s/12/B/\n> +\tEOF\n> +\ttest_cmp expected actual\n> +'\n> +\n> +test_expect_success 'simple B...C (unmodified)' '\n> +\tgit range-diff --no-color topic...unmodified >actual &&\n> +\t# same \"expected\" as above\n> +\ttest_cmp expected actual\n> +'\n> +\n> +test_expect_success 'simple A B C (unmodified)' '\n> +\tgit range-diff --no-color master topic unmodified >actual &&\n> +\t# same \"expected\" as above\n> +\ttest_cmp expected actual\n> +'\n> +\n> +test_expect_success 'trivial reordering' '\n> +\tgit range-diff --no-color master topic reordered >actual &&\n> +\tcat >expected <<-EOF &&\n> +\t1:  4de457d = 1:  aca177a s/5/A/\n> +\t3:  147e64e = 2:  14ad629 s/11/B/\n> +\t4:  a63e992 = 3:  ee58208 s/12/B/\n> +\t2:  fccce22 = 4:  307b27a s/4/A/\n> +\tEOF\n> +\ttest_cmp expected actual\n> +'\n> +\n> +test_expect_success 'removed a commit' '\n> +\tgit range-diff --no-color master topic removed >actual &&\n> +\tcat >expected <<-EOF &&\n> +\t1:  4de457d = 1:  7657159 s/5/A/\n> +\t2:  fccce22 < -:  ------- s/4/A/\n> +\t3:  147e64e = 2:  43d84d3 s/11/B/\n> +\t4:  a63e992 = 3:  a740396 s/12/B/\n> +\tEOF\n> +\ttest_cmp expected actual\n> +'\n> +\n> +test_expect_success 'added a commit' '\n> +\tgit range-diff --no-color master topic added >actual &&\n> +\tcat >expected <<-EOF &&\n> +\t1:  4de457d = 1:  2716022 s/5/A/\n> +\t2:  fccce22 = 2:  b62accd s/4/A/\n> +\t-:  ------- > 3:  df46cfa s/6/A/\n> +\t3:  147e64e = 4:  3e64548 s/11/B/\n> +\t4:  a63e992 = 5:  12b4063 s/12/B/\n> +\tEOF\n> +\ttest_cmp expected actual\n> +'\n> +\n> +test_expect_success 'new base, A B C' '\n> +\tgit range-diff --no-color master topic rebased >actual &&\n> +\tcat >expected <<-EOF &&\n> +\t1:  4de457d = 1:  cc9c443 s/5/A/\n> +\t2:  fccce22 = 2:  c5d9641 s/4/A/\n> +\t3:  147e64e = 3:  28cc2b6 s/11/B/\n> +\t4:  a63e992 = 4:  5628ab7 s/12/B/\n> +\tEOF\n> +\ttest_cmp expected actual\n> +'\n> +\n> +test_expect_success 'new base, B...C' '\n> +\t# this syntax includes the commits from master!\n> +\tgit range-diff --no-color topic...rebased >actual &&\n> +\tcat >expected <<-EOF &&\n> +\t-:  ------- > 1:  a31b12e unrelated\n> +\t1:  4de457d = 2:  cc9c443 s/5/A/\n> +\t2:  fccce22 = 3:  c5d9641 s/4/A/\n> +\t3:  147e64e = 4:  28cc2b6 s/11/B/\n> +\t4:  a63e992 = 5:  5628ab7 s/12/B/\n> +\tEOF\n> +\ttest_cmp expected actual\n> +'\n> +\n> +test_expect_success 'changed commit' '\n> +\tgit range-diff --no-color topic...changed >actual &&\n> +\tcat >expected <<-EOF &&\n> +\t1:  4de457d = 1:  a4b3333 s/5/A/\n> +\t2:  fccce22 = 2:  f51d370 s/4/A/\n> +\t3:  147e64e ! 3:  0559556 s/11/B/\n> +\t    @@ -10,7 +10,7 @@\n> +\t      9\n> +\t      10\n> +\t     -11\n> +\t    -+B\n> +\t    ++BB\n> +\t      12\n> +\t      13\n> +\t      14\n> +\t4:  a63e992 ! 4:  d966c5c s/12/B/\n> +\t    @@ -8,7 +8,7 @@\n> +\t     @@\n> +\t      9\n> +\t      10\n> +\t    - B\n> +\t    + BB\n> +\t     -12\n> +\t     +B\n> +\t      13\n> +\tEOF\n> +\ttest_cmp expected actual\n> +'\n> +\n> +test_expect_success 'changed message' '\n> +\tgit range-diff --no-color topic...changed-message >actual &&\n> +\tsed s/Z/\\ /g >expected <<-EOF &&\n> +\t1:  4de457d = 1:  f686024 s/5/A/\n> +\t2:  fccce22 ! 2:  4ab067d s/4/A/\n> +\t    @@ -2,6 +2,8 @@\n> +\t    Z\n> +\t    Z    s/4/A/\n> +\t    Z\n> +\t    +    Also a silly comment here!\n> +\t    +\n> +\t    Zdiff --git a/file b/file\n> +\t    Z--- a/file\n> +\t    Z+++ b/file\n> +\t3:  147e64e = 3:  b9cb956 s/11/B/\n> +\t4:  a63e992 = 4:  8add5f1 s/12/B/\n> +\tEOF\n> +\ttest_cmp expected actual\n> +'\n> +\n> +test_done\n> diff --git a/t/t3206/history.export b/t/t3206/history.export\n> new file mode 100644\n> index 000000000..b8ffff094\n> --- /dev/null\n> +++ b/t/t3206/history.export\n> @@ -0,0 +1,604 @@\n> +blob\n> +mark :1\n> +data 51\n> +1\n> +2\n> +3\n> +4\n> +5\n> +6\n> +7\n> +8\n> +9\n> +10\n> +11\n> +12\n> +13\n> +14\n> +15\n> +16\n> +17\n> +18\n> +19\n> +20\n> +\n> +reset refs/heads/removed\n> +commit refs/heads/removed\n> +mark :2\n> +author Thomas Rast <trast@inf.ethz.ch> 1374424921 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374484724 +0200\n> +data 8\n> +initial\n> +M 100644 :1 file\n> +\n> +blob\n> +mark :3\n> +data 51\n> +1\n> +2\n> +3\n> +4\n> +A\n> +6\n> +7\n> +8\n> +9\n> +10\n> +11\n> +12\n> +13\n> +14\n> +15\n> +16\n> +17\n> +18\n> +19\n> +20\n> +\n> +commit refs/heads/topic\n> +mark :4\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n> +data 7\n> +s/5/A/\n> +from :2\n> +M 100644 :3 file\n> +\n> +blob\n> +mark :5\n> +data 51\n> +1\n> +2\n> +3\n> +A\n> +A\n> +6\n> +7\n> +8\n> +9\n> +10\n> +11\n> +12\n> +13\n> +14\n> +15\n> +16\n> +17\n> +18\n> +19\n> +20\n> +\n> +commit refs/heads/topic\n> +mark :6\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n> +data 7\n> +s/4/A/\n> +from :4\n> +M 100644 :5 file\n> +\n> +blob\n> +mark :7\n> +data 50\n> +1\n> +2\n> +3\n> +A\n> +A\n> +6\n> +7\n> +8\n> +9\n> +10\n> +B\n> +12\n> +13\n> +14\n> +15\n> +16\n> +17\n> +18\n> +19\n> +20\n> +\n> +commit refs/heads/topic\n> +mark :8\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n> +data 8\n> +s/11/B/\n> +from :6\n> +M 100644 :7 file\n> +\n> +blob\n> +mark :9\n> +data 49\n> +1\n> +2\n> +3\n> +A\n> +A\n> +6\n> +7\n> +8\n> +9\n> +10\n> +B\n> +B\n> +13\n> +14\n> +15\n> +16\n> +17\n> +18\n> +19\n> +20\n> +\n> +commit refs/heads/topic\n> +mark :10\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n> +data 8\n> +s/12/B/\n> +from :8\n> +M 100644 :9 file\n> +\n> +blob\n> +mark :11\n> +data 10\n> +unrelated\n> +\n> +commit refs/heads/master\n> +mark :12\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200\n> +data 10\n> +unrelated\n> +from :2\n> +M 100644 :11 otherfile\n> +\n> +commit refs/heads/rebased\n> +mark :13\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485137 +0200\n> +data 7\n> +s/5/A/\n> +from :12\n> +M 100644 :3 file\n> +\n> +commit refs/heads/rebased\n> +mark :14\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n> +data 7\n> +s/4/A/\n> +from :13\n> +M 100644 :5 file\n> +\n> +commit refs/heads/rebased\n> +mark :15\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n> +data 8\n> +s/11/B/\n> +from :14\n> +M 100644 :7 file\n> +\n> +commit refs/heads/rebased\n> +mark :16\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n> +data 8\n> +s/12/B/\n> +from :15\n> +M 100644 :9 file\n> +\n> +commit refs/heads/added\n> +mark :17\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n> +data 7\n> +s/5/A/\n> +from :2\n> +M 100644 :3 file\n> +\n> +commit refs/heads/added\n> +mark :18\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n> +data 7\n> +s/4/A/\n> +from :17\n> +M 100644 :5 file\n> +\n> +blob\n> +mark :19\n> +data 51\n> +1\n> +2\n> +3\n> +A\n> +A\n> +A\n> +7\n> +8\n> +9\n> +10\n> +11\n> +12\n> +13\n> +14\n> +15\n> +16\n> +17\n> +18\n> +19\n> +20\n> +\n> +commit refs/heads/added\n> +mark :20\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485186 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n> +data 7\n> +s/6/A/\n> +from :18\n> +M 100644 :19 file\n> +\n> +blob\n> +mark :21\n> +data 50\n> +1\n> +2\n> +3\n> +A\n> +A\n> +A\n> +7\n> +8\n> +9\n> +10\n> +B\n> +12\n> +13\n> +14\n> +15\n> +16\n> +17\n> +18\n> +19\n> +20\n> +\n> +commit refs/heads/added\n> +mark :22\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n> +data 8\n> +s/11/B/\n> +from :20\n> +M 100644 :21 file\n> +\n> +blob\n> +mark :23\n> +data 49\n> +1\n> +2\n> +3\n> +A\n> +A\n> +A\n> +7\n> +8\n> +9\n> +10\n> +B\n> +B\n> +13\n> +14\n> +15\n> +16\n> +17\n> +18\n> +19\n> +20\n> +\n> +commit refs/heads/added\n> +mark :24\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n> +data 8\n> +s/12/B/\n> +from :22\n> +M 100644 :23 file\n> +\n> +commit refs/heads/reordered\n> +mark :25\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n> +data 7\n> +s/5/A/\n> +from :2\n> +M 100644 :3 file\n> +\n> +blob\n> +mark :26\n> +data 50\n> +1\n> +2\n> +3\n> +4\n> +A\n> +6\n> +7\n> +8\n> +9\n> +10\n> +B\n> +12\n> +13\n> +14\n> +15\n> +16\n> +17\n> +18\n> +19\n> +20\n> +\n> +commit refs/heads/reordered\n> +mark :27\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n> +data 8\n> +s/11/B/\n> +from :25\n> +M 100644 :26 file\n> +\n> +blob\n> +mark :28\n> +data 49\n> +1\n> +2\n> +3\n> +4\n> +A\n> +6\n> +7\n> +8\n> +9\n> +10\n> +B\n> +B\n> +13\n> +14\n> +15\n> +16\n> +17\n> +18\n> +19\n> +20\n> +\n> +commit refs/heads/reordered\n> +mark :29\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n> +data 8\n> +s/12/B/\n> +from :27\n> +M 100644 :28 file\n> +\n> +commit refs/heads/reordered\n> +mark :30\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n> +data 7\n> +s/4/A/\n> +from :29\n> +M 100644 :9 file\n> +\n> +commit refs/heads/changed\n> +mark :31\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n> +data 7\n> +s/5/A/\n> +from :2\n> +M 100644 :3 file\n> +\n> +commit refs/heads/changed\n> +mark :32\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n> +data 7\n> +s/4/A/\n> +from :31\n> +M 100644 :5 file\n> +\n> +blob\n> +mark :33\n> +data 51\n> +1\n> +2\n> +3\n> +A\n> +A\n> +6\n> +7\n> +8\n> +9\n> +10\n> +BB\n> +12\n> +13\n> +14\n> +15\n> +16\n> +17\n> +18\n> +19\n> +20\n> +\n> +commit refs/heads/changed\n> +mark :34\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n> +data 8\n> +s/11/B/\n> +from :32\n> +M 100644 :33 file\n> +\n> +blob\n> +mark :35\n> +data 50\n> +1\n> +2\n> +3\n> +A\n> +A\n> +6\n> +7\n> +8\n> +9\n> +10\n> +BB\n> +B\n> +13\n> +14\n> +15\n> +16\n> +17\n> +18\n> +19\n> +20\n> +\n> +commit refs/heads/changed\n> +mark :36\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n> +data 8\n> +s/12/B/\n> +from :34\n> +M 100644 :35 file\n> +\n> +commit refs/heads/changed-message\n> +mark :37\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200\n> +data 7\n> +s/5/A/\n> +from :2\n> +M 100644 :3 file\n> +\n> +commit refs/heads/changed-message\n> +mark :38\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200\n> +data 35\n> +s/4/A/\n> +\n> +Also a silly comment here!\n> +from :37\n> +M 100644 :5 file\n> +\n> +commit refs/heads/changed-message\n> +mark :39\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200\n> +data 8\n> +s/11/B/\n> +from :38\n> +M 100644 :7 file\n> +\n> +commit refs/heads/changed-message\n> +mark :40\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200\n> +data 8\n> +s/12/B/\n> +from :39\n> +M 100644 :9 file\n> +\n> +commit refs/heads/unmodified\n> +mark :41\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200\n> +data 7\n> +s/5/A/\n> +from :2\n> +M 100644 :3 file\n> +\n> +commit refs/heads/unmodified\n> +mark :42\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200\n> +data 7\n> +s/4/A/\n> +from :41\n> +M 100644 :5 file\n> +\n> +commit refs/heads/unmodified\n> +mark :43\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200\n> +data 8\n> +s/11/B/\n> +from :42\n> +M 100644 :7 file\n> +\n> +commit refs/heads/unmodified\n> +mark :44\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200\n> +data 8\n> +s/12/B/\n> +from :43\n> +M 100644 :9 file\n> +\n> +commit refs/heads/removed\n> +mark :45\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n> +data 7\n> +s/5/A/\n> +from :2\n> +M 100644 :3 file\n> +\n> +commit refs/heads/removed\n> +mark :46\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n> +data 8\n> +s/11/B/\n> +from :45\n> +M 100644 :26 file\n> +\n> +commit refs/heads/removed\n> +mark :47\n> +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n> +committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n> +data 8\n> +s/12/B/\n> +from :46\n> +M 100644 :28 file\n> +\n> +reset refs/heads/removed\n> +from :47\n> +\n> -- \n> gitgitgadget\n> \n"},{"id":"355477","messageId":"20180813204714.GI2734@hank.intra.tgummerer.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1808131337360.71@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v6 00/21] Add range-diff, a tbdiff lookalike","fromName":"Thomas Gummerer","fromEmail":"t.gummerer@gmail.com","sentAt":"2018-08-13T20:47:14Z","receivedAt":"2018-08-13T20:47:19Z","isPatch":true,"sender":{"key":"t.gummerer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/191004?v=4"},"body":"On 08/13, Johannes Schindelin wrote:\n> Hi,\n> \n> On Mon, 13 Aug 2018, Johannes Schindelin via GitGitGadget wrote:\n> \n> > The incredibly useful git-tbdiff [https://github.com/trast/tbdiff] tool to\n> > compare patch series (say, to see what changed between two iterations sent\n> > to the Git mailing list) is slightly less useful for this developer due to\n> > the fact that it requires the hungarian and numpy Python packages which are\n> > for some reason really hard to build in MSYS2. So hard that I even had to\n> > give up, because it was simply easier to re-implement the whole shebang as a\n> > builtin command.\n> > \n> > The project at https://github.com/trast/tbdiff seems to be dormant, anyway.\n> > Funny (and true) story: I looked at the open Pull Requests to see how active\n> > that project is, only to find to my surprise that I had submitted one in\n> > August 2015, and that it was still unanswered let alone merged.\n> > \n> > While at it, I forward-ported AEvar's patch to force --decorate=no because \n> > git -p tbdiff would fail otherwise.\n> > \n> > Side note: I work on implementing range-diff not only to make life easier\n> > for reviewers who have to suffer through v2, v3, ... of my patch series, but\n> > also to verify my changes before submitting a new iteration. And also, maybe\n> > even more importantly, I plan to use it to verify my merging-rebases of Git\n> > for Windows (for which I previously used to redirect the\n> > pre-rebase/post-rebase diffs vs upstream and then compare them using git\n> > diff --no-index). And of course any interested person can see what changes\n> > were necessary e.g. in the merging-rebase of Git for Windows onto v2.17.0 by\n> > running a command like:\n> > \n> >         base=^{/Start.the.merging-rebase}\n> >         tag=v2.17.0.windows.1\n> >         pre=$tag$base^2\n> >         git range-diff $pre$base..$pre $tag$base..$tag\n> > \n> > The command uses what it calls the \"dual color mode\" (can be disabled via \n> > --no-dual-color) which helps identifying what actually changed: it prefixes\n> > lines with a - (and red background) that correspond to the first commit\n> > range, and with a + (and green background) that correspond to the second\n> > range. The rest of the lines will be colored according to the original\n> > diffs.\n> \n> Changes since v5:\n> \n> - Fixed the bug (introduced in v5) where a dashdash would not be handled\n>   appropriately.\n\nThanks!  I've read through all the patches (and the range-diff :))\nagain and played around a bit with the newest version, and I think\nthis is ready for 'next'.\n\nWhile playing around with it I did find one error message that reads\nslightly odd, but it's still understandable, so I'm not sure it's\nworth worrying about now (we can always improve it on top):\n\n     $ ./git range-diff -- js/range-diff-v4...HEADt\n    fatal: ambiguous argument 'HEADt..js/range-diff-v4': unknown revision or path not in the working tree.\n    Use '--' to separate paths from revisions, like this:\n    'git <command> [<revision>...] -- [<file>...]'\n    error: could not parse log for 'HEADt..js/range-diff-v4'\n\n\n> [...]\n"},{"id":"355548","messageId":"nycvar.QRO.7.76.6.1808141652460.71@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180813183504.GD13316@hank.intra.tgummerer.com","subject":"Re: [PATCH v6 11/21] range-diff: add tests","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-08-14T14:53:51Z","receivedAt":"2018-08-14T14:53:53Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Thomas,\n\nOn Mon, 13 Aug 2018, Thomas Gummerer wrote:\n\n> On 08/13, Thomas Rast via GitGitGadget wrote:\n> > From: Thomas Rast <tr@thomasrast.ch>\n> > \n> > These are essentially lifted from https://github.com/trast/tbdiff, with\n> > light touch-ups to account for the command now being named `git\n> > range-diff`.\n> > \n> > Apart from renaming `tbdiff` to `range-diff`, only one test case needed\n> > to be adjusted: 11 - 'changed message'.\n> > \n> > The underlying reason it had to be adjusted is that diff generation is\n> > sometimes ambiguous. In this case, a comment line and an empty line are\n> > added, but it is ambiguous whether they were added after the existing\n> > empty line, or whether an empty line and the comment line are added\n> > *before* the existing empty line. And apparently xdiff picks a different\n> > option here than Python's difflib.\n> >\n> \n> Just noticed while reading the whole series again (hopefully for the\n> last time :)), do we need Thomas Rast's Sign-off here, as he is\n> credited as the author here? \n\nHmm. I hoped that my commit message was enough to indicate that while he\nis the author, I assembled this. Maybe I should move him to the footer, as\nan Original-Authored-By:?\n\nJunio?\n\nCiao,\nDscho\n> \n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> >  t/.gitattributes       |   1 +\n> >  t/t3206-range-diff.sh  | 145 ++++++++++\n> >  t/t3206/history.export | 604 +++++++++++++++++++++++++++++++++++++++++\n> >  3 files changed, 750 insertions(+)\n> >  create mode 100755 t/t3206-range-diff.sh\n> >  create mode 100644 t/t3206/history.export\n> > \n> > diff --git a/t/.gitattributes b/t/.gitattributes\n> > index 3bd959ae5..b17bf71b8 100644\n> > --- a/t/.gitattributes\n> > +++ b/t/.gitattributes\n> > @@ -1,6 +1,7 @@\n> >  t[0-9][0-9][0-9][0-9]/* -whitespace\n> >  /diff-lib/* eol=lf\n> >  /t0110/url-* binary\n> > +/t3206/* eol=lf\n> >  /t3900/*.txt eol=lf\n> >  /t3901/*.txt eol=lf\n> >  /t4034/*/* eol=lf\n> > diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh\n> > new file mode 100755\n> > index 000000000..2237c7f4a\n> > --- /dev/null\n> > +++ b/t/t3206-range-diff.sh\n> > @@ -0,0 +1,145 @@\n> > +#!/bin/sh\n> > +\n> > +test_description='range-diff tests'\n> > +\n> > +. ./test-lib.sh\n> > +\n> > +# Note that because of the range-diff's heuristics, test_commit does more\n> > +# harm than good.  We need some real history.\n> > +\n> > +test_expect_success 'setup' '\n> > +\tgit fast-import < \"$TEST_DIRECTORY\"/t3206/history.export\n> > +'\n> > +\n> > +test_expect_success 'simple A..B A..C (unmodified)' '\n> > +\tgit range-diff --no-color master..topic master..unmodified \\\n> > +\t\t>actual &&\n> > +\tcat >expected <<-EOF &&\n> > +\t1:  4de457d = 1:  35b9b25 s/5/A/\n> > +\t2:  fccce22 = 2:  de345ab s/4/A/\n> > +\t3:  147e64e = 3:  9af6654 s/11/B/\n> > +\t4:  a63e992 = 4:  2901f77 s/12/B/\n> > +\tEOF\n> > +\ttest_cmp expected actual\n> > +'\n> > +\n> > +test_expect_success 'simple B...C (unmodified)' '\n> > +\tgit range-diff --no-color topic...unmodified >actual &&\n> > +\t# same \"expected\" as above\n> > +\ttest_cmp expected actual\n> > +'\n> > +\n> > +test_expect_success 'simple A B C (unmodified)' '\n> > +\tgit range-diff --no-color master topic unmodified >actual &&\n> > +\t# same \"expected\" as above\n> > +\ttest_cmp expected actual\n> > +'\n> > +\n> > +test_expect_success 'trivial reordering' '\n> > +\tgit range-diff --no-color master topic reordered >actual &&\n> > +\tcat >expected <<-EOF &&\n> > +\t1:  4de457d = 1:  aca177a s/5/A/\n> > +\t3:  147e64e = 2:  14ad629 s/11/B/\n> > +\t4:  a63e992 = 3:  ee58208 s/12/B/\n> > +\t2:  fccce22 = 4:  307b27a s/4/A/\n> > +\tEOF\n> > +\ttest_cmp expected actual\n> > +'\n> > +\n> > +test_expect_success 'removed a commit' '\n> > +\tgit range-diff --no-color master topic removed >actual &&\n> > +\tcat >expected <<-EOF &&\n> > +\t1:  4de457d = 1:  7657159 s/5/A/\n> > +\t2:  fccce22 < -:  ------- s/4/A/\n> > +\t3:  147e64e = 2:  43d84d3 s/11/B/\n> > +\t4:  a63e992 = 3:  a740396 s/12/B/\n> > +\tEOF\n> > +\ttest_cmp expected actual\n> > +'\n> > +\n> > +test_expect_success 'added a commit' '\n> > +\tgit range-diff --no-color master topic added >actual &&\n> > +\tcat >expected <<-EOF &&\n> > +\t1:  4de457d = 1:  2716022 s/5/A/\n> > +\t2:  fccce22 = 2:  b62accd s/4/A/\n> > +\t-:  ------- > 3:  df46cfa s/6/A/\n> > +\t3:  147e64e = 4:  3e64548 s/11/B/\n> > +\t4:  a63e992 = 5:  12b4063 s/12/B/\n> > +\tEOF\n> > +\ttest_cmp expected actual\n> > +'\n> > +\n> > +test_expect_success 'new base, A B C' '\n> > +\tgit range-diff --no-color master topic rebased >actual &&\n> > +\tcat >expected <<-EOF &&\n> > +\t1:  4de457d = 1:  cc9c443 s/5/A/\n> > +\t2:  fccce22 = 2:  c5d9641 s/4/A/\n> > +\t3:  147e64e = 3:  28cc2b6 s/11/B/\n> > +\t4:  a63e992 = 4:  5628ab7 s/12/B/\n> > +\tEOF\n> > +\ttest_cmp expected actual\n> > +'\n> > +\n> > +test_expect_success 'new base, B...C' '\n> > +\t# this syntax includes the commits from master!\n> > +\tgit range-diff --no-color topic...rebased >actual &&\n> > +\tcat >expected <<-EOF &&\n> > +\t-:  ------- > 1:  a31b12e unrelated\n> > +\t1:  4de457d = 2:  cc9c443 s/5/A/\n> > +\t2:  fccce22 = 3:  c5d9641 s/4/A/\n> > +\t3:  147e64e = 4:  28cc2b6 s/11/B/\n> > +\t4:  a63e992 = 5:  5628ab7 s/12/B/\n> > +\tEOF\n> > +\ttest_cmp expected actual\n> > +'\n> > +\n> > +test_expect_success 'changed commit' '\n> > +\tgit range-diff --no-color topic...changed >actual &&\n> > +\tcat >expected <<-EOF &&\n> > +\t1:  4de457d = 1:  a4b3333 s/5/A/\n> > +\t2:  fccce22 = 2:  f51d370 s/4/A/\n> > +\t3:  147e64e ! 3:  0559556 s/11/B/\n> > +\t    @@ -10,7 +10,7 @@\n> > +\t      9\n> > +\t      10\n> > +\t     -11\n> > +\t    -+B\n> > +\t    ++BB\n> > +\t      12\n> > +\t      13\n> > +\t      14\n> > +\t4:  a63e992 ! 4:  d966c5c s/12/B/\n> > +\t    @@ -8,7 +8,7 @@\n> > +\t     @@\n> > +\t      9\n> > +\t      10\n> > +\t    - B\n> > +\t    + BB\n> > +\t     -12\n> > +\t     +B\n> > +\t      13\n> > +\tEOF\n> > +\ttest_cmp expected actual\n> > +'\n> > +\n> > +test_expect_success 'changed message' '\n> > +\tgit range-diff --no-color topic...changed-message >actual &&\n> > +\tsed s/Z/\\ /g >expected <<-EOF &&\n> > +\t1:  4de457d = 1:  f686024 s/5/A/\n> > +\t2:  fccce22 ! 2:  4ab067d s/4/A/\n> > +\t    @@ -2,6 +2,8 @@\n> > +\t    Z\n> > +\t    Z    s/4/A/\n> > +\t    Z\n> > +\t    +    Also a silly comment here!\n> > +\t    +\n> > +\t    Zdiff --git a/file b/file\n> > +\t    Z--- a/file\n> > +\t    Z+++ b/file\n> > +\t3:  147e64e = 3:  b9cb956 s/11/B/\n> > +\t4:  a63e992 = 4:  8add5f1 s/12/B/\n> > +\tEOF\n> > +\ttest_cmp expected actual\n> > +'\n> > +\n> > +test_done\n> > diff --git a/t/t3206/history.export b/t/t3206/history.export\n> > new file mode 100644\n> > index 000000000..b8ffff094\n> > --- /dev/null\n> > +++ b/t/t3206/history.export\n> > @@ -0,0 +1,604 @@\n> > +blob\n> > +mark :1\n> > +data 51\n> > +1\n> > +2\n> > +3\n> > +4\n> > +5\n> > +6\n> > +7\n> > +8\n> > +9\n> > +10\n> > +11\n> > +12\n> > +13\n> > +14\n> > +15\n> > +16\n> > +17\n> > +18\n> > +19\n> > +20\n> > +\n> > +reset refs/heads/removed\n> > +commit refs/heads/removed\n> > +mark :2\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374424921 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374484724 +0200\n> > +data 8\n> > +initial\n> > +M 100644 :1 file\n> > +\n> > +blob\n> > +mark :3\n> > +data 51\n> > +1\n> > +2\n> > +3\n> > +4\n> > +A\n> > +6\n> > +7\n> > +8\n> > +9\n> > +10\n> > +11\n> > +12\n> > +13\n> > +14\n> > +15\n> > +16\n> > +17\n> > +18\n> > +19\n> > +20\n> > +\n> > +commit refs/heads/topic\n> > +mark :4\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n> > +data 7\n> > +s/5/A/\n> > +from :2\n> > +M 100644 :3 file\n> > +\n> > +blob\n> > +mark :5\n> > +data 51\n> > +1\n> > +2\n> > +3\n> > +A\n> > +A\n> > +6\n> > +7\n> > +8\n> > +9\n> > +10\n> > +11\n> > +12\n> > +13\n> > +14\n> > +15\n> > +16\n> > +17\n> > +18\n> > +19\n> > +20\n> > +\n> > +commit refs/heads/topic\n> > +mark :6\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n> > +data 7\n> > +s/4/A/\n> > +from :4\n> > +M 100644 :5 file\n> > +\n> > +blob\n> > +mark :7\n> > +data 50\n> > +1\n> > +2\n> > +3\n> > +A\n> > +A\n> > +6\n> > +7\n> > +8\n> > +9\n> > +10\n> > +B\n> > +12\n> > +13\n> > +14\n> > +15\n> > +16\n> > +17\n> > +18\n> > +19\n> > +20\n> > +\n> > +commit refs/heads/topic\n> > +mark :8\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n> > +data 8\n> > +s/11/B/\n> > +from :6\n> > +M 100644 :7 file\n> > +\n> > +blob\n> > +mark :9\n> > +data 49\n> > +1\n> > +2\n> > +3\n> > +A\n> > +A\n> > +6\n> > +7\n> > +8\n> > +9\n> > +10\n> > +B\n> > +B\n> > +13\n> > +14\n> > +15\n> > +16\n> > +17\n> > +18\n> > +19\n> > +20\n> > +\n> > +commit refs/heads/topic\n> > +mark :10\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n> > +data 8\n> > +s/12/B/\n> > +from :8\n> > +M 100644 :9 file\n> > +\n> > +blob\n> > +mark :11\n> > +data 10\n> > +unrelated\n> > +\n> > +commit refs/heads/master\n> > +mark :12\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200\n> > +data 10\n> > +unrelated\n> > +from :2\n> > +M 100644 :11 otherfile\n> > +\n> > +commit refs/heads/rebased\n> > +mark :13\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485137 +0200\n> > +data 7\n> > +s/5/A/\n> > +from :12\n> > +M 100644 :3 file\n> > +\n> > +commit refs/heads/rebased\n> > +mark :14\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n> > +data 7\n> > +s/4/A/\n> > +from :13\n> > +M 100644 :5 file\n> > +\n> > +commit refs/heads/rebased\n> > +mark :15\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n> > +data 8\n> > +s/11/B/\n> > +from :14\n> > +M 100644 :7 file\n> > +\n> > +commit refs/heads/rebased\n> > +mark :16\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200\n> > +data 8\n> > +s/12/B/\n> > +from :15\n> > +M 100644 :9 file\n> > +\n> > +commit refs/heads/added\n> > +mark :17\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n> > +data 7\n> > +s/5/A/\n> > +from :2\n> > +M 100644 :3 file\n> > +\n> > +commit refs/heads/added\n> > +mark :18\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n> > +data 7\n> > +s/4/A/\n> > +from :17\n> > +M 100644 :5 file\n> > +\n> > +blob\n> > +mark :19\n> > +data 51\n> > +1\n> > +2\n> > +3\n> > +A\n> > +A\n> > +A\n> > +7\n> > +8\n> > +9\n> > +10\n> > +11\n> > +12\n> > +13\n> > +14\n> > +15\n> > +16\n> > +17\n> > +18\n> > +19\n> > +20\n> > +\n> > +commit refs/heads/added\n> > +mark :20\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485186 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n> > +data 7\n> > +s/6/A/\n> > +from :18\n> > +M 100644 :19 file\n> > +\n> > +blob\n> > +mark :21\n> > +data 50\n> > +1\n> > +2\n> > +3\n> > +A\n> > +A\n> > +A\n> > +7\n> > +8\n> > +9\n> > +10\n> > +B\n> > +12\n> > +13\n> > +14\n> > +15\n> > +16\n> > +17\n> > +18\n> > +19\n> > +20\n> > +\n> > +commit refs/heads/added\n> > +mark :22\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n> > +data 8\n> > +s/11/B/\n> > +from :20\n> > +M 100644 :21 file\n> > +\n> > +blob\n> > +mark :23\n> > +data 49\n> > +1\n> > +2\n> > +3\n> > +A\n> > +A\n> > +A\n> > +7\n> > +8\n> > +9\n> > +10\n> > +B\n> > +B\n> > +13\n> > +14\n> > +15\n> > +16\n> > +17\n> > +18\n> > +19\n> > +20\n> > +\n> > +commit refs/heads/added\n> > +mark :24\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200\n> > +data 8\n> > +s/12/B/\n> > +from :22\n> > +M 100644 :23 file\n> > +\n> > +commit refs/heads/reordered\n> > +mark :25\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n> > +data 7\n> > +s/5/A/\n> > +from :2\n> > +M 100644 :3 file\n> > +\n> > +blob\n> > +mark :26\n> > +data 50\n> > +1\n> > +2\n> > +3\n> > +4\n> > +A\n> > +6\n> > +7\n> > +8\n> > +9\n> > +10\n> > +B\n> > +12\n> > +13\n> > +14\n> > +15\n> > +16\n> > +17\n> > +18\n> > +19\n> > +20\n> > +\n> > +commit refs/heads/reordered\n> > +mark :27\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n> > +data 8\n> > +s/11/B/\n> > +from :25\n> > +M 100644 :26 file\n> > +\n> > +blob\n> > +mark :28\n> > +data 49\n> > +1\n> > +2\n> > +3\n> > +4\n> > +A\n> > +6\n> > +7\n> > +8\n> > +9\n> > +10\n> > +B\n> > +B\n> > +13\n> > +14\n> > +15\n> > +16\n> > +17\n> > +18\n> > +19\n> > +20\n> > +\n> > +commit refs/heads/reordered\n> > +mark :29\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n> > +data 8\n> > +s/12/B/\n> > +from :27\n> > +M 100644 :28 file\n> > +\n> > +commit refs/heads/reordered\n> > +mark :30\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200\n> > +data 7\n> > +s/4/A/\n> > +from :29\n> > +M 100644 :9 file\n> > +\n> > +commit refs/heads/changed\n> > +mark :31\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n> > +data 7\n> > +s/5/A/\n> > +from :2\n> > +M 100644 :3 file\n> > +\n> > +commit refs/heads/changed\n> > +mark :32\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n> > +data 7\n> > +s/4/A/\n> > +from :31\n> > +M 100644 :5 file\n> > +\n> > +blob\n> > +mark :33\n> > +data 51\n> > +1\n> > +2\n> > +3\n> > +A\n> > +A\n> > +6\n> > +7\n> > +8\n> > +9\n> > +10\n> > +BB\n> > +12\n> > +13\n> > +14\n> > +15\n> > +16\n> > +17\n> > +18\n> > +19\n> > +20\n> > +\n> > +commit refs/heads/changed\n> > +mark :34\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n> > +data 8\n> > +s/11/B/\n> > +from :32\n> > +M 100644 :33 file\n> > +\n> > +blob\n> > +mark :35\n> > +data 50\n> > +1\n> > +2\n> > +3\n> > +A\n> > +A\n> > +6\n> > +7\n> > +8\n> > +9\n> > +10\n> > +BB\n> > +B\n> > +13\n> > +14\n> > +15\n> > +16\n> > +17\n> > +18\n> > +19\n> > +20\n> > +\n> > +commit refs/heads/changed\n> > +mark :36\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200\n> > +data 8\n> > +s/12/B/\n> > +from :34\n> > +M 100644 :35 file\n> > +\n> > +commit refs/heads/changed-message\n> > +mark :37\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200\n> > +data 7\n> > +s/5/A/\n> > +from :2\n> > +M 100644 :3 file\n> > +\n> > +commit refs/heads/changed-message\n> > +mark :38\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200\n> > +data 35\n> > +s/4/A/\n> > +\n> > +Also a silly comment here!\n> > +from :37\n> > +M 100644 :5 file\n> > +\n> > +commit refs/heads/changed-message\n> > +mark :39\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200\n> > +data 8\n> > +s/11/B/\n> > +from :38\n> > +M 100644 :7 file\n> > +\n> > +commit refs/heads/changed-message\n> > +mark :40\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200\n> > +data 8\n> > +s/12/B/\n> > +from :39\n> > +M 100644 :9 file\n> > +\n> > +commit refs/heads/unmodified\n> > +mark :41\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200\n> > +data 7\n> > +s/5/A/\n> > +from :2\n> > +M 100644 :3 file\n> > +\n> > +commit refs/heads/unmodified\n> > +mark :42\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200\n> > +data 7\n> > +s/4/A/\n> > +from :41\n> > +M 100644 :5 file\n> > +\n> > +commit refs/heads/unmodified\n> > +mark :43\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200\n> > +data 8\n> > +s/11/B/\n> > +from :42\n> > +M 100644 :7 file\n> > +\n> > +commit refs/heads/unmodified\n> > +mark :44\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200\n> > +data 8\n> > +s/12/B/\n> > +from :43\n> > +M 100644 :9 file\n> > +\n> > +commit refs/heads/removed\n> > +mark :45\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n> > +data 7\n> > +s/5/A/\n> > +from :2\n> > +M 100644 :3 file\n> > +\n> > +commit refs/heads/removed\n> > +mark :46\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n> > +data 8\n> > +s/11/B/\n> > +from :45\n> > +M 100644 :26 file\n> > +\n> > +commit refs/heads/removed\n> > +mark :47\n> > +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200\n> > +committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200\n> > +data 8\n> > +s/12/B/\n> > +from :46\n> > +M 100644 :28 file\n> > +\n> > +reset refs/heads/removed\n> > +from :47\n> > +\n> > -- \n> > gitgitgadget\n> > \n> \n> \n"},{"id":"355549","messageId":"20180814150309.GD3441@sigill.intra.peff.net","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1808141652460.71@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v6 11/21] range-diff: add tests","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-08-14T15:03:10Z","receivedAt":"2018-08-14T15:03:13Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Aug 14, 2018 at 04:53:51PM +0200, Johannes Schindelin wrote:\n\n> > > These are essentially lifted from https://github.com/trast/tbdiff, with\n> > > light touch-ups to account for the command now being named `git\n> > > range-diff`.\n> [...]\n> > Just noticed while reading the whole series again (hopefully for the\n> > last time :)), do we need Thomas Rast's Sign-off here, as he is\n> > credited as the author here? \n> \n> Hmm. I hoped that my commit message was enough to indicate that while he\n> is the author, I assembled this. Maybe I should move him to the footer, as\n> an Original-Authored-By:?\n\nI think the \"Author\" field is actually distinct from the copyright\nprovenance. In this case it ought to be perfectly fine to add your\nsigned-off-by under the DCO's point b:\n\n  The contribution is based upon previous work that, to the best\n  of my knowledge, is covered under an appropriate open source\n  license and I have the right under that license to submit that\n  work with modifications [...]\n\nThis is based on the tests in tbdiff, which is explicitly GPL'd by\nThomas. So your signoff certifies that, which is fine.\n\nAs for the author field, IMHO it serves two purposes:\n\n  - to give credit where it is due\n\n  - so that people digging in history know who to contact for\n    questions/problems\n\nIn this case it probably makes sense for it to be you, as you'd take\nresponsibility for the code in _this_ project. And as you note, you can\ngive credit in the commit message (the only unfortunate thing is that\nmost automated statistics would not credit Thomas, but in theory they\ncould by mentioning him in the trailer).\n\n-Peff\n"},{"id":"355551","messageId":"20180814150616.GE3441@sigill.intra.peff.net","threadId":"48405","inReplyTo":"20180814150309.GD3441@sigill.intra.peff.net","subject":"Re: [PATCH v6 11/21] range-diff: add tests","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-08-14T15:06:16Z","receivedAt":"2018-08-14T15:06:19Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Aug 14, 2018 at 11:03:10AM -0400, Jeff King wrote:\n\n> > Hmm. I hoped that my commit message was enough to indicate that while he\n> > is the author, I assembled this. Maybe I should move him to the footer, as\n> > an Original-Authored-By:?\n> \n> I think the \"Author\" field is actually distinct from the copyright\n> provenance. In this case it ought to be perfectly fine to add your\n> signed-off-by under the DCO's point b:\n> \n>   The contribution is based upon previous work that, to the best\n>   of my knowledge, is covered under an appropriate open source\n>   license and I have the right under that license to submit that\n>   work with modifications [...]\n> \n> This is based on the tests in tbdiff, which is explicitly GPL'd by\n> Thomas. So your signoff certifies that, which is fine.\n> \n> As for the author field, IMHO it serves two purposes:\n> \n>   - to give credit where it is due\n> \n>   - so that people digging in history know who to contact for\n>     questions/problems\n> \n> In this case it probably makes sense for it to be you, as you'd take\n> responsibility for the code in _this_ project. And as you note, you can\n> give credit in the commit message (the only unfortunate thing is that\n> most automated statistics would not credit Thomas, but in theory they\n> could by mentioning him in the trailer).\n\nOne thing I should have made clear: this is all my opinion, and anything\nThomas expresses trumps that. But since he hasn't been active lately,\nthis is all what I would do in the absence of input from him. Obviously\na sign-off from him is better than none. :)\n\n-Peff\n"},{"id":"355554","messageId":"xmqqr2j10yei.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"nycvar.QRO.7.76.6.1808141652460.71@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v6 11/21] range-diff: add tests","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-08-14T15:18:45Z","receivedAt":"2018-08-14T15:18:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Hmm. I hoped that my commit message was enough to indicate that while he\n> is the author, I assembled this. Maybe I should move him to the footer, as\n> an Original-Authored-By:?\n>\n> Junio?\n\nI think the log message gives a clear enough statement to credit the\noriginal author.  Sign-off is not about credit, but is about making\nsure we know the provenance of the contribution we would use in our\ncodebase, so it would be nice to have, but lifting code from another\nproject (i.e. TRast's tbdiff) that is appropriately licensed (GPLv2)\nverbatim is something you can do with your own sign-off, without the\noriginal author's sign-off, so I think what we have is good.\n"},{"id":"357758","messageId":"87lg8a7wj2.fsf@evledraar.gmail.com","threadId":"48405","inReplyTo":"8c5543a0667fffe0cb0684427f726fdfb75b28d0.1534159977.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 17/21] range-diff: populate the man page","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2018-09-09T11:14:25Z","receivedAt":"2018-09-09T11:14:30Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Aug 13 2018, Johannes Schindelin via GitGitGadget wrote:\n\nI realize this topic has long since landed, just seemed like a good\nthing to reply to to ask this question:\n\n> [...]\n> +\t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n> [...]\n> +<range1> <range2>::\n> +\tCompare the commits specified by the two ranges, where\n> +\t`<range1>` is considered an older version of `<range2>`.\n> +\n> +<rev1>...<rev2>::\n> +\tEquivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.\n> +\n> +<base> <rev1> <rev2>::\n> +\tEquivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.\n> +\tNote that `<base>` does not need to be the exact branch point\n> +\tof the branches. Example: after rebasing a branch `my-topic`,\n> +\t`git range-diff my-topic@{u} my-topic@{1} my-topic` would\n> +\tshow the differences introduced by the rebase.\n\nI find myself using range-diff often by watching forced pushes to public\nrepos to see what others are doing, e.g. just now:\n\n     + 38b5f0fe72...718fbdedbc split-index-racy       -> szeder/split-index-racy  (forced update)\n\nAnd then I turn that into:\n\n    # @{u} because I happen to be on 'master' and it's shorter to type\n    # than origin/master...\n    git range-diff @{u} 38b5f0fe72...718fbdedbc\n\nOnly to get an error because it doesn't support that, but just:\n\n    git range-diff @{u} 38b5f0fe72 718fbdedbc\n\nI think it would be convenient given that \"fetch\" produces this output\nto support this sort of invocation as synonymous with the three-arg\nform. Then you can directly copy/paste that from terminals that have a\nconvenient feature to highlight a continuous \\S+ reason to copy/paste\nit.\n\nI can patch it in, but maybe there's UI reasons not to do this that I'm\nmissing, e.g. confusion with the existing <rev1>...<rev2> syntax. What\ndo you think?\n"},{"id":"357762","messageId":"20180909165431.GA17224@localhost","threadId":"48405","inReplyTo":"87lg8a7wj2.fsf@evledraar.gmail.com","subject":"Re: [PATCH v6 17/21] range-diff: populate the man page","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2018-09-09T16:54:31Z","receivedAt":"2018-09-09T16:54:42Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Sun, Sep 09, 2018 at 01:14:25PM +0200, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Mon, Aug 13 2018, Johannes Schindelin via GitGitGadget wrote:\n> \n> I realize this topic has long since landed, just seemed like a good\n> thing to reply to to ask this question:\n> \n> > [...]\n> > +\t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n> > [...]\n> > +<range1> <range2>::\n> > +\tCompare the commits specified by the two ranges, where\n> > +\t`<range1>` is considered an older version of `<range2>`.\n> > +\n> > +<rev1>...<rev2>::\n> > +\tEquivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.\n> > +\n> > +<base> <rev1> <rev2>::\n> > +\tEquivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.\n> > +\tNote that `<base>` does not need to be the exact branch point\n> > +\tof the branches. Example: after rebasing a branch `my-topic`,\n> > +\t`git range-diff my-topic@{u} my-topic@{1} my-topic` would\n> > +\tshow the differences introduced by the rebase.\n> \n> I find myself using range-diff often by watching forced pushes to public\n> repos to see what others are doing, e.g. just now:\n> \n>      + 38b5f0fe72...718fbdedbc split-index-racy       -> szeder/split-index-racy  (forced update)\n\nHeh, spying on my wip bugfixes :)\n\n> And then I turn that into:\n> \n>     # @{u} because I happen to be on 'master' and it's shorter to type\n>     # than origin/master...\n>     git range-diff @{u} 38b5f0fe72...718fbdedbc\n\nI don't understand what you want with that @{u} or 'origin/master' in\nthe first place.  It's unnecessary, the three-dot notation on its own\nworks just fine.\n\n\n> Only to get an error because it doesn't support that, but just:\n> \n>     git range-diff @{u} 38b5f0fe72 718fbdedbc\n> \n> I think it would be convenient given that \"fetch\" produces this output\n> to support this sort of invocation as synonymous with the three-arg\n> form. Then you can directly copy/paste that from terminals that have a\n> convenient feature to highlight a continuous \\S+ reason to copy/paste\n> it.\n> \n> I can patch it in, but maybe there's UI reasons not to do this that I'm\n> missing, e.g. confusion with the existing <rev1>...<rev2> syntax. What\n> do you think?\n"},{"id":"357763","messageId":"87k1nu7fm0.fsf@evledraar.gmail.com","threadId":"48405","inReplyTo":"20180909165431.GA17224@localhost","subject":"Re: [PATCH v6 17/21] range-diff: populate the man page","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2018-09-09T17:19:51Z","receivedAt":"2018-09-09T17:19:59Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sun, Sep 09 2018, SZEDER Gábor wrote:\n\n> On Sun, Sep 09, 2018 at 01:14:25PM +0200, Ævar Arnfjörð Bjarmason wrote:\n>>\n>> On Mon, Aug 13 2018, Johannes Schindelin via GitGitGadget wrote:\n>>\n>> I realize this topic has long since landed, just seemed like a good\n>> thing to reply to to ask this question:\n>>\n>> > [...]\n>> > +\t( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )\n>> > [...]\n>> > +<range1> <range2>::\n>> > +\tCompare the commits specified by the two ranges, where\n>> > +\t`<range1>` is considered an older version of `<range2>`.\n>> > +\n>> > +<rev1>...<rev2>::\n>> > +\tEquivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.\n>> > +\n>> > +<base> <rev1> <rev2>::\n>> > +\tEquivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.\n>> > +\tNote that `<base>` does not need to be the exact branch point\n>> > +\tof the branches. Example: after rebasing a branch `my-topic`,\n>> > +\t`git range-diff my-topic@{u} my-topic@{1} my-topic` would\n>> > +\tshow the differences introduced by the rebase.\n>>\n>> I find myself using range-diff often by watching forced pushes to public\n>> repos to see what others are doing, e.g. just now:\n>>\n>>      + 38b5f0fe72...718fbdedbc split-index-racy       -> szeder/split-index-racy  (forced update)\n>\n> Heh, spying on my wip bugfixes :)\n>\n>> And then I turn that into:\n>>\n>>     # @{u} because I happen to be on 'master' and it's shorter to type\n>>     # than origin/master...\n>>     git range-diff @{u} 38b5f0fe72...718fbdedbc\n>\n> I don't understand what you want with that @{u} or 'origin/master' in\n> the first place.  It's unnecessary, the three-dot notation on its own\n> works just fine.\n\nMaybe I've been using the wrong mode all along, I passed over by habits\nfrom tbdiff, which were surely copy/pasted from somewhere.\n\nLooking at the git-range-diff manpage though it recommends <base> <rev1>\n<rev2> over <rev1>...<rev2> when the topic has been rebased, which is\nusually the case for e.g. a topic that's submitted to git.git (usually\nbe the time feedback has been gathered & a re-submission has been made\nJunio has pushed another \"master\").\n\nSo isn't \"<base> <rev1> <rev2>\" the right thing to use over\n\"<rev1>...<rev2>\" for git.git use? I think so, but I'm not sure.\n\nIn any case, there are going to be those use-case where you should be\nusing \"<base> <rev1> <rev2>\", and a rebase will be propagated by a\nforce-push, so I thought it made sense that range-diff could directly\nconsume the output of \"fetch\" in that case...\n\n>> Only to get an error because it doesn't support that, but just:\n>>\n>>     git range-diff @{u} 38b5f0fe72 718fbdedbc\n>>\n>> I think it would be convenient given that \"fetch\" produces this output\n>> to support this sort of invocation as synonymous with the three-arg\n>> form. Then you can directly copy/paste that from terminals that have a\n>> convenient feature to highlight a continuous \\S+ reason to copy/paste\n>> it.\n>>\n>> I can patch it in, but maybe there's UI reasons not to do this that I'm\n>> missing, e.g. confusion with the existing <rev1>...<rev2> syntax. What\n>> do you think?\n"},{"id":"357784","messageId":"20180910133704.GC5233@sigill.intra.peff.net","threadId":"48405","inReplyTo":"87k1nu7fm0.fsf@evledraar.gmail.com","subject":"Re: [PATCH v6 17/21] range-diff: populate the man page","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-09-10T13:37:04Z","receivedAt":"2018-09-10T13:37:09Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Sep 09, 2018 at 07:19:51PM +0200, Ævar Arnfjörð Bjarmason wrote:\n\n> >> And then I turn that into:\n> >>\n> >>     # @{u} because I happen to be on 'master' and it's shorter to type\n> >>     # than origin/master...\n> >>     git range-diff @{u} 38b5f0fe72...718fbdedbc\n> >\n> > I don't understand what you want with that @{u} or 'origin/master' in\n> > the first place.  It's unnecessary, the three-dot notation on its own\n> > works just fine.\n> \n> Maybe I've been using the wrong mode all along, I passed over by habits\n> from tbdiff, which were surely copy/pasted from somewhere.\n> \n> Looking at the git-range-diff manpage though it recommends <base> <rev1>\n> <rev2> over <rev1>...<rev2> when the topic has been rebased, which is\n> usually the case for e.g. a topic that's submitted to git.git (usually\n> be the time feedback has been gathered & a re-submission has been made\n> Junio has pushed another \"master\").\n> \n> So isn't \"<base> <rev1> <rev2>\" the right thing to use over\n> \"<rev1>...<rev2>\" for git.git use? I think so, but I'm not sure.\n\nThe problem with <rev1>...<rev2> is that it finds the actual merge base,\nnot the beginning of the topic. So if you have a 5-patch topic, but the\nfirst two patches weren't changed in the rebase, it won't show them at\nall!  I made this mistake in [1], for example.\n\nFor a force-push, though, you may not care about seeing the topic as a\nwhole, and that mid-topic merge-base could be just fine. So pasting just\nthe \"A...B\" works.\n\nI don't think your \"@{u} A...B\" makes any sense. You're giving _two_\nbases, which is weird. But even if you wanted to ignore the \"...\" base\nas a convenience to users of fetch, @{u} does not necessarily have\nanything to do with the @{upstream} of the topic at \"A\". You really want\nbranch@{u}, which is on a separate part of the fetch output line (and\nyour branch@{u} and the remote's are not necessarily the same, either;\nin this case you probably do not even have that branch checked out).\n\n-Peff\n\n[1] https://public-inbox.org/git/20180821195102.GB859@sigill.intra.peff.net/\n"},{"id":"357805","messageId":"xmqqmusptgpu.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"87k1nu7fm0.fsf@evledraar.gmail.com","subject":"Re: [PATCH v6 17/21] range-diff: populate the man page","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-09-10T17:17:17Z","receivedAt":"2018-09-10T17:17:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> Looking at the git-range-diff manpage though it recommends <base> <rev1>\n> <rev2> over <rev1>...<rev2> when the topic has been rebased, which is\n> usually the case for e.g. a topic that's submitted to git.git (usually\n> be the time feedback has been gathered & a re-submission has been made\n> Junio has pushed another \"master\").\n>\n> So isn't \"<base> <rev1> <rev2>\" the right thing to use over\n> \"<rev1>...<rev2>\" for git.git use? I think so, but I'm not sure.\n\nIf <rev2> is forked from different base than where <rev1> was\nforked, then <base> <rev1> <rev2> would give you more sensible\nrange.  And such an update is inevitable when <rev2> must rely on\nnew things that recently appeared on <base> since <rev1> forked from\nthe mainline.  But otherwise <rev1>...<rev2> should work just fine.\n\n> In any case, there are going to be those use-case where you should be\n> using \"<base> <rev1> <rev2>\", and a rebase will be propagated by a\n> force-push, so I thought it made sense that range-diff could directly\n> consume the output of \"fetch\" in that case...\n\nI am not absolutely sure if there is *more* useful interpretation\nthat \"<base> <rev1>...<rev2>\" may want to mean than to serve as a\nsynonym for \"<base> <rev1> <rev2>\" for those who are too lazy to\ntype.  But if there isn't, I'd say it is a reasonable synonym to\nwant.\n\n"},{"id":"359426","messageId":"nycvar.QRO.7.76.6.1810021652420.2034@tvgsbejvaqbjf.bet","threadId":"48405","inReplyTo":"20180910133704.GC5233@sigill.intra.peff.net","subject":"Re: [PATCH v6 17/21] range-diff: populate the man page","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-10-02T15:06:42Z","receivedAt":"2018-10-02T15:06:51Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Mon, 10 Sep 2018, Jeff King wrote:\n\n> On Sun, Sep 09, 2018 at 07:19:51PM +0200, Ævar Arnfjörð Bjarmason wrote:\n> \n> > >> And then I turn that into:\n> > >>\n> > >>     # @{u} because I happen to be on 'master' and it's shorter to type\n> > >>     # than origin/master...\n> > >>     git range-diff @{u} 38b5f0fe72...718fbdedbc\n> > >\n> > > I don't understand what you want with that @{u} or 'origin/master' in\n> > > the first place.  It's unnecessary, the three-dot notation on its own\n> > > works just fine.\n> > \n> > Maybe I've been using the wrong mode all along, I passed over by habits\n> > from tbdiff, which were surely copy/pasted from somewhere.\n> > \n> > Looking at the git-range-diff manpage though it recommends <base> <rev1>\n> > <rev2> over <rev1>...<rev2> when the topic has been rebased, which is\n> > usually the case for e.g. a topic that's submitted to git.git (usually\n> > be the time feedback has been gathered & a re-submission has been made\n> > Junio has pushed another \"master\").\n> > \n> > So isn't \"<base> <rev1> <rev2>\" the right thing to use over\n> > \"<rev1>...<rev2>\" for git.git use? I think so, but I'm not sure.\n> \n> The problem with <rev1>...<rev2> is that it finds the actual merge base,\n> not the beginning of the topic.\n\nThat is actually not true, not for `range-diff`. If it sees `A...B`, it\nwill automatically generate `B..A A..B` from it.\n\nThat matters if the branches `A` and `B` have multiple merge bases.\n\n> So if you have a 5-patch topic, but the first two patches weren't\n> changed in the rebase, it won't show them at all!  I made this mistake\n> in [1], for example.\n\nYep, that is very easy to do.\n\nAnother thing to note is that often `A...B` is not doing the right thing\nwith branches that go into `pu` because some of us contributors rebase\nto `master` (or `next`) between iterations. For such a use case, I\nmyself prefer the `@{u}` version that Ævar wants to use. (Although I\nleave off the three dots, in which case everything works quite\nmagically.)\n\n> For a force-push, though, you may not care about seeing the topic as a\n> whole, and that mid-topic merge-base could be just fine. So pasting just\n> the \"A...B\" works.\n> \n> I don't think your \"@{u} A...B\" makes any sense. You're giving _two_\n> bases, which is weird. But even if you wanted to ignore the \"...\" base\n> as a convenience to users of fetch, @{u} does not necessarily have\n> anything to do with the @{upstream} of the topic at \"A\". You really want\n> branch@{u}, which is on a separate part of the fetch output line (and\n> your branch@{u} and the remote's are not necessarily the same, either;\n> in this case you probably do not even have that branch checked out).\n\nWhile `@{u}` in general does not relate to `A` nor `B`, it is quite\npossible that it always does in Ævar's scenario. I would not want to\nlimit them in how they want to use Git from this point of view.\n\nHowever, I would have a little bit of a problem with special-casing the\ntwo-arg version when there are no dots in the first arg, and three dots\nin the second one.\n\nThe problem here: the two-arg version already has a meaning: two commit\nranges. And it *is* conceivable that somebody wants to compare, say, the\nfull history of `git-gui.git` with a certain symmetric range in `pu`.\nGranted, that is very obscure a use case, but it would be hard to\nexplain why the two-arg case refers to two commit ranges in some cases,\nand in other cases not.\n\nCiao,\nDscho\n\n> \n> -Peff\n> \n> [1] https://public-inbox.org/git/20180821195102.GB859@sigill.intra.peff.net/\n> "},{"id":"370672","messageId":"xmqqr2blvnpp.fsf@gitster-ct.c.googlers.com","threadId":"48405","inReplyTo":"08b8c3fc45253737ef6ca860e6cbe3ee6211d7a6.1534159977.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 03/21] range-diff: first rudimentary implementation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-03-05T06:29:06Z","receivedAt":"2019-03-05T06:29:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> +\t\telse if (!line.buf[0] || starts_with(line.buf, \"index \"))\n> +\t\t\t/*\n> +\t\t\t * A completely blank (not ' \\n', which is context)\n> +\t\t\t * line is not valid in a diff.  We skip it\n\nI noticed this while wondering how somebody could teach range-diff\nto honor --notes=amlog while preparing the patches to be compared\n[*1*], but this assumption goes against what POSIX.1 says these\ndays.\n\n    It is implementation-defined whether an empty unaffected line is\n    written as an empty line or a line containing a single <space> character.\n\ncf. http://pubs.opengroup.org/onlinepubs/9699919799/utilities/diff.html#tag_20_34_10_07\n\nWe need to insert \", as we disable user's diff.suppressBlankEmpty\nsettings\" before \".  We skip it\" (and if we get affected by the\nsetting, we need to fix it; it is not ultra-urgent, though).\n\n[Footnote]\n\n*1* ... which I do not have a good answer to, yet.  As discussed\nearlier, the diffopt passed into the show_range_diff() machinery is\nprimarily meant for the final output (i.e. how the matching patches\nfrom the two iterations are compared) and not about how the patches\nto be compared are generated.  Worse, --notes=amlog (and possibly\nother useful options) are parsed by \"git log\" side of the machinery,\nnot \"git diff\" side that populates diffopt.\n"}]}