All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
@ 2018-04-30 21:54     ` Johannes Schindelin via GitGitGadget
  2018-07-06 22:43       ` Junio C Hamano
  2018-07-11 10:07       ` SZEDER Gábor
  2018-05-01 19:42     ` [PATCH v3 02/20] Introduce `range-diff` to compare iterations of a topic branch Johannes Schindelin via GitGitGadget
                       ` (19 subsequent siblings)
  20 siblings, 2 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-04-30 21:54 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

The problem solved by the code introduced in this commit goes like this:
given two sets of items, and a cost matrix which says how much it
"costs" to assign any given item of the first set to any given item of
the second, assign all items (except when the sets have different size)
in the cheapest way.

We use the Jonker-Volgenant algorithm to solve the assignment problem to
answer questions such as: given two different versions of a topic branch
(or iterations of a patch series), what is the best pairing of
commits/patches between the different versions?

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Makefile            |   1 +
 linear-assignment.c | 203 ++++++++++++++++++++++++++++++++++++++++++++
 linear-assignment.h |  22 +++++
 3 files changed, 226 insertions(+)
 create mode 100644 linear-assignment.c
 create mode 100644 linear-assignment.h

diff --git a/Makefile b/Makefile
index 0cb6590f2..c5ba124f1 100644
--- a/Makefile
+++ b/Makefile
@@ -868,6 +868,7 @@ LIB_OBJS += gpg-interface.o
 LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hashmap.o
+LIB_OBJS += linear-assignment.o
 LIB_OBJS += help.o
 LIB_OBJS += hex.o
 LIB_OBJS += ident.o
diff --git a/linear-assignment.c b/linear-assignment.c
new file mode 100644
index 000000000..0b0344b5f
--- /dev/null
+++ b/linear-assignment.c
@@ -0,0 +1,203 @@
+/*
+ * Based on: Jonker, R., & Volgenant, A. (1987). <i>A shortest augmenting path
+ * algorithm for dense and sparse linear assignment problems</i>. Computing,
+ * 38(4), 325-340.
+ */
+#include "cache.h"
+#include "linear-assignment.h"
+
+#define COST(column, row) cost[(column) + column_count * (row)]
+
+/*
+ * The parameter `cost` is the cost matrix: the cost to assign column j to row
+ * i is `cost[j + column_count * i].
+ */
+void compute_assignment(int column_count, int row_count, int *cost,
+			int *column2row, int *row2column)
+{
+	int *v, *d;
+	int *free_row, free_count = 0, saved_free_count, *pred, *col;
+	int i, j, phase;
+
+	memset(column2row, -1, sizeof(int) * column_count);
+	memset(row2column, -1, sizeof(int) * row_count);
+	ALLOC_ARRAY(v, column_count);
+
+	/* column reduction */
+	for (j = column_count - 1; j >= 0; j--) {
+		int i1 = 0;
+
+		for (i = 1; i < row_count; i++)
+			if (COST(j, i1) > COST(j, i))
+				i1 = i;
+		v[j] = COST(j, i1);
+		if (row2column[i1] == -1) {
+			/* row i1 unassigned */
+			row2column[i1] = j;
+			column2row[j] = i1;
+		} else {
+			if (row2column[i1] >= 0)
+				row2column[i1] = -2 - row2column[i1];
+			column2row[j] = -1;
+		}
+	}
+
+	/* reduction transfer */
+	ALLOC_ARRAY(free_row, row_count);
+	for (i = 0; i < row_count; i++) {
+		int j1 = row2column[i];
+		if (j1 == -1)
+			free_row[free_count++] = i;
+		else if (j1 < -1)
+			row2column[i] = -2 - j1;
+		else {
+			int min = COST(!j1, i) - v[!j1];
+			for (j = 1; j < column_count; j++)
+				if (j != j1 && min > COST(j, i) - v[j])
+					min = COST(j, i) - v[j];
+			v[j1] -= min;
+		}
+	}
+
+	if (free_count ==
+	    (column_count < row_count ? row_count - column_count : 0)) {
+		free(v);
+		free(free_row);
+		return;
+	}
+
+	/* augmenting row reduction */
+	for (phase = 0; phase < 2; phase++) {
+		int k = 0;
+
+		saved_free_count = free_count;
+		free_count = 0;
+		while (k < saved_free_count) {
+			int u1, u2;
+			int j1 = 0, j2, i0;
+
+			i = free_row[k++];
+			u1 = COST(j1, i) - v[j1];
+			j2 = -1;
+			u2 = INT_MAX;
+			for (j = 1; j < column_count; j++) {
+				int c = COST(j, i) - v[j];
+				if (u2 > c) {
+					if (u1 < c) {
+						u2 = c;
+						j2 = j;
+					} else {
+						u2 = u1;
+						u1 = c;
+						j2 = j1;
+						j1 = j;
+					}
+				}
+			}
+			if (j2 < 0) {
+				j2 = j1;
+				u2 = u1;
+			}
+
+			i0 = column2row[j1];
+			if (u1 < u2)
+				v[j1] -= u2 - u1;
+			else if (i0 >= 0) {
+				j1 = j2;
+				i0 = column2row[j1];
+			}
+
+			if (i0 >= 0) {
+				if (u1 < u2)
+					free_row[--k] = i0;
+				else
+					free_row[free_count++] = i0;
+			}
+			row2column[i] = j1;
+			column2row[j1] = i;
+		}
+	}
+
+	/* augmentation */
+	saved_free_count = free_count;
+	ALLOC_ARRAY(d, column_count);
+	ALLOC_ARRAY(pred, column_count);
+	ALLOC_ARRAY(col, column_count);
+	for (free_count = 0; free_count < saved_free_count; free_count++) {
+		int i1 = free_row[free_count], low = 0, up = 0, last, k;
+		int min, c, u1;
+
+		for (j = 0; j < column_count; j++) {
+			d[j] = COST(j, i1) - v[j];
+			pred[j] = i1;
+			col[j] = j;
+		}
+
+		j = -1;
+		do {
+			last = low;
+			min = d[col[up++]];
+			for (k = up; k < column_count; k++) {
+				j = col[k];
+				c = d[j];
+				if (c <= min) {
+					if (c < min) {
+						up = low;
+						min = c;
+					}
+					col[k] = col[up];
+					col[up++] = j;
+				}
+			}
+			for (k = low; k < up; k++)
+				if (column2row[col[k]] == -1)
+					goto update;
+
+			/* scan a row */
+			do {
+				int j1 = col[low++];
+
+				i = column2row[j1];
+				u1 = COST(j1, i) - v[j1] - min;
+				for (k = up; k < column_count; k++) {
+					j = col[k];
+					c = COST(j, i) - v[j] - u1;
+					if (c < d[j]) {
+						d[j] = c;
+						pred[j] = i;
+						if (c == min) {
+							if (column2row[j] == -1)
+								goto update;
+							col[k] = col[up];
+							col[up++] = j;
+						}
+					}
+				}
+			} while (low != up);
+		} while (low == up);
+
+update:
+		/* updating of the column pieces */
+		for (k = 0; k < last; k++) {
+			int j1 = col[k];
+			v[j1] += d[j1] - min;
+		}
+
+		/* augmentation */
+		do {
+			if (j < 0)
+				BUG("negative j: %d", j);
+			i = pred[j];
+			column2row[j] = i;
+			k = j;
+			j = row2column[i];
+			row2column[i] = k;
+		} while (i1 != i);
+	}
+
+	free(col);
+	free(pred);
+	free(d);
+	free(v);
+	free(free_row);
+}
diff --git a/linear-assignment.h b/linear-assignment.h
new file mode 100644
index 000000000..fc4c502c8
--- /dev/null
+++ b/linear-assignment.h
@@ -0,0 +1,22 @@
+#ifndef HUNGARIAN_H
+#define HUNGARIAN_H
+
+/*
+ * Compute an assignment of columns -> rows (and vice versa) such that every
+ * column is assigned to at most one row (and vice versa) minimizing the
+ * overall cost.
+ *
+ * The parameter `cost` is the cost matrix: the cost to assign column j to row
+ * i is `cost[j + column_count * i].
+ *
+ * The arrays column2row and row2column will be populated with the respective
+ * assignments (-1 for unassigned, which can happen only if column_count !=
+ * row_count).
+ */
+void compute_assignment(int column_count, int row_count, int *cost,
+			int *column2row, int *row2column);
+
+/* The maximal cost in the cost matrix (to prevent integer overflows). */
+#define COST_MAX (1<<16)
+
+#endif
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v3 02/20] Introduce `range-diff` to compare iterations of a topic branch
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
  2018-04-30 21:54     ` [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems Johannes Schindelin via GitGitGadget
@ 2018-05-01 19:42     ` Johannes Schindelin via GitGitGadget
  2018-05-02  0:34     ` [PATCH v3 03/20] range-diff: first rudimentary implementation Johannes Schindelin via GitGitGadget
                       ` (18 subsequent siblings)
  20 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-05-01 19:42 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

This command does not do a whole lot so far, apart from showing a usage
that is oddly similar to that of `git tbdiff`. And for a good reason:
the next commits will turn `range-branch` into a full-blown replacement
for `tbdiff`.

At this point, we ignore tbdiff's color options, as they will all be
implemented later using diff_options.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitignore           |  1 +
 Makefile             |  1 +
 builtin.h            |  1 +
 builtin/range-diff.c | 25 +++++++++++++++++++++++++
 command-list.txt     |  1 +
 git.c                |  1 +
 6 files changed, 30 insertions(+)
 create mode 100644 builtin/range-diff.c

diff --git a/.gitignore b/.gitignore
index 3284a1e9b..cc0ad74b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -113,6 +113,7 @@
 /git-pull
 /git-push
 /git-quiltimport
+/git-range-diff
 /git-read-tree
 /git-rebase
 /git-rebase--am
diff --git a/Makefile b/Makefile
index c5ba124f1..190384cae 100644
--- a/Makefile
+++ b/Makefile
@@ -1059,6 +1059,7 @@ BUILTIN_OBJS += builtin/prune-packed.o
 BUILTIN_OBJS += builtin/prune.o
 BUILTIN_OBJS += builtin/pull.o
 BUILTIN_OBJS += builtin/push.o
+BUILTIN_OBJS += builtin/range-diff.o
 BUILTIN_OBJS += builtin/read-tree.o
 BUILTIN_OBJS += builtin/rebase--helper.o
 BUILTIN_OBJS += builtin/receive-pack.o
diff --git a/builtin.h b/builtin.h
index 0362f1ce2..99206df4b 100644
--- a/builtin.h
+++ b/builtin.h
@@ -201,6 +201,7 @@ extern int cmd_prune(int argc, const char **argv, const char *prefix);
 extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
 extern int cmd_pull(int argc, const char **argv, const char *prefix);
 extern int cmd_push(int argc, const char **argv, const char *prefix);
+extern int cmd_range_diff(int argc, const char **argv, const char *prefix);
 extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix);
 extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);
diff --git a/builtin/range-diff.c b/builtin/range-diff.c
new file mode 100644
index 000000000..36788ea4f
--- /dev/null
+++ b/builtin/range-diff.c
@@ -0,0 +1,25 @@
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+
+static const char * const builtin_range_diff_usage[] = {
+N_("git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
+N_("git range-diff [<options>] <old-tip>...<new-tip>"),
+N_("git range-diff [<options>] <base> <old-tip> <new-tip>"),
+NULL
+};
+
+int cmd_range_diff(int argc, const char **argv, const char *prefix)
+{
+	int creation_factor = 60;
+	struct option options[] = {
+		OPT_INTEGER(0, "creation-factor", &creation_factor,
+			    N_("Percentage by which creation is weighted")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, NULL, options,
+			     builtin_range_diff_usage, 0);
+
+	return 0;
+}
diff --git a/command-list.txt b/command-list.txt
index e1c26c1bb..a9dda3b8a 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -139,6 +139,7 @@ git-prune-packed                        plumbingmanipulators
 git-pull                                mainporcelain           remote
 git-push                                mainporcelain           remote
 git-quiltimport                         foreignscminterface
+git-range-diff                          mainporcelain
 git-read-tree                           plumbingmanipulators
 git-rebase                              mainporcelain           history
 git-receive-pack                        synchelpers
diff --git a/git.c b/git.c
index 9dbe6ffaa..13e37f1e3 100644
--- a/git.c
+++ b/git.c
@@ -517,6 +517,7 @@ static struct cmd_struct commands[] = {
 	{ "prune-packed", cmd_prune_packed, RUN_SETUP },
 	{ "pull", cmd_pull, RUN_SETUP | NEED_WORK_TREE },
 	{ "push", cmd_push, RUN_SETUP },
+	{ "range-diff", cmd_range_diff, RUN_SETUP | USE_PAGER },
 	{ "read-tree", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX},
 	{ "rebase--helper", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE },
 	{ "receive-pack", cmd_receive_pack },
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v3 03/20] range-diff: first rudimentary implementation
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
  2018-04-30 21:54     ` [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems Johannes Schindelin via GitGitGadget
  2018-05-01 19:42     ` [PATCH v3 02/20] Introduce `range-diff` to compare iterations of a topic branch Johannes Schindelin via GitGitGadget
@ 2018-05-02  0:34     ` Johannes Schindelin via GitGitGadget
  2018-07-16  6:55       ` Eric Sunshine
  2018-05-02 10:22     ` [PATCH v3 04/20] range-diff: improve the order of the shown commits Johannes Schindelin via GitGitGadget
                       ` (17 subsequent siblings)
  20 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-05-02  0:34 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

At this stage, `git range-diff` can determine corresponding commits
of two related commit ranges. This makes use of the recently introduced
implementation of the Hungarian algorithm.

The core of this patch is a straight port of the ideas of tbdiff, the
apparently dormant project at https://github.com/trast/tbdiff.

The output does not at all match `tbdiff`'s output yet, as this patch
really concentrates on getting the patch matching part right.

Note: due to differences in the diff algorithm (`tbdiff` uses the Python
module `difflib`, Git uses its xdiff fork), the cost matrix calculated
by `range-diff` is different (but very similar) to the one calculated
by `tbdiff`. Therefore, it is possible that they find different matching
commits in corner cases (e.g. when a patch was split into two patches of
roughly equal length).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Makefile             |   1 +
 builtin/range-diff.c |  47 ++++++-
 range-diff.c         | 307 +++++++++++++++++++++++++++++++++++++++++++
 range-diff.h         |   7 +
 4 files changed, 359 insertions(+), 3 deletions(-)
 create mode 100644 range-diff.c
 create mode 100644 range-diff.h

diff --git a/Makefile b/Makefile
index 190384cae..f20126e11 100644
--- a/Makefile
+++ b/Makefile
@@ -921,6 +921,7 @@ LIB_OBJS += progress.o
 LIB_OBJS += prompt.o
 LIB_OBJS += protocol.o
 LIB_OBJS += quote.o
+LIB_OBJS += range-diff.o
 LIB_OBJS += reachable.o
 LIB_OBJS += read-cache.o
 LIB_OBJS += reflog-walk.o
diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 36788ea4f..c37a72100 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "builtin.h"
 #include "parse-options.h"
+#include "range-diff.h"
 
 static const char * const builtin_range_diff_usage[] = {
 N_("git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
@@ -17,9 +18,49 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 			    N_("Percentage by which creation is weighted")),
 		OPT_END()
 	};
+	int res = 0;
+	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
 
-	argc = parse_options(argc, argv, NULL, options,
-			     builtin_range_diff_usage, 0);
+	argc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,
+			     0);
 
-	return 0;
+	if (argc == 2) {
+		if (!strstr(argv[0], ".."))
+			warning(_("no .. in range: '%s'"), argv[0]);
+		strbuf_addstr(&range1, argv[0]);
+
+		if (!strstr(argv[1], ".."))
+			warning(_("no .. in range: '%s'"), argv[1]);
+		strbuf_addstr(&range2, argv[1]);
+	} else if (argc == 3) {
+		strbuf_addf(&range1, "%s..%s", argv[0], argv[1]);
+		strbuf_addf(&range2, "%s..%s", argv[0], argv[2]);
+	} else if (argc == 1) {
+		const char *b = strstr(argv[0], "..."), *a = argv[0];
+		int a_len;
+
+		if (!b)
+			die(_("single arg format requires a symmetric range"));
+
+		a_len = (int)(b - a);
+		if (!a_len) {
+			a = "HEAD";
+			a_len = strlen(a);
+		}
+		b += 3;
+		if (!*b)
+			b = "HEAD";
+		strbuf_addf(&range1, "%s..%.*s", b, a_len, a);
+		strbuf_addf(&range2, "%.*s..%s", a_len, a, b);
+	} else {
+		error(_("need two commit ranges"));
+		usage_with_options(builtin_range_diff_usage, options);
+	}
+
+	res = show_range_diff(range1.buf, range2.buf, creation_factor);
+
+	strbuf_release(&range1);
+	strbuf_release(&range2);
+
+	return res;
 }
diff --git a/range-diff.c b/range-diff.c
new file mode 100644
index 000000000..c374333a4
--- /dev/null
+++ b/range-diff.c
@@ -0,0 +1,307 @@
+#include "cache.h"
+#include "range-diff.h"
+#include "string-list.h"
+#include "run-command.h"
+#include "argv-array.h"
+#include "hashmap.h"
+#include "xdiff-interface.h"
+#include "linear-assignment.h"
+
+struct patch_util {
+	/* For the search for an exact match */
+	struct hashmap_entry e;
+	const char *diff, *patch;
+
+	int i;
+	int diffsize;
+	size_t diff_offset;
+	/* the index of the matching item in the other branch, or -1 */
+	int matching;
+	struct object_id oid;
+};
+
+/*
+ * Reads the patches into a string list, with the `util` field being populated
+ * as struct object_id (will need to be free()d).
+ */
+static int read_patches(const char *range, struct string_list *list)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+	FILE *in;
+	struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;
+	struct patch_util *util = NULL;
+	int in_header = 1;
+
+	argv_array_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
+			"--reverse", "--date-order", "--decorate=no",
+			"--no-abbrev-commit", range,
+			NULL);
+	cp.out = -1;
+	cp.no_stdin = 1;
+	cp.git_cmd = 1;
+
+	if (start_command(&cp))
+		return error_errno(_("could not start `log`"));
+	in = fdopen(cp.out, "r");
+	if (!in) {
+		error_errno(_("could not read `log` output"));
+		finish_command(&cp);
+		return -1;
+	}
+
+	while (strbuf_getline(&line, in) != EOF) {
+		const char *p;
+
+		if (skip_prefix(line.buf, "commit ", &p)) {
+			if (util) {
+				string_list_append(list, buf.buf)->util = util;
+				strbuf_reset(&buf);
+			}
+			util = xcalloc(sizeof(*util), 1);
+			if (get_oid(p, &util->oid)) {
+				error(_("could not parse commit '%s'"), p);
+				free(util);
+				string_list_clear(list, 1);
+				strbuf_release(&buf);
+				strbuf_release(&line);
+				fclose(in);
+				finish_command(&cp);
+				return -1;
+			}
+			util->matching = -1;
+			in_header = 1;
+			continue;
+		}
+
+		if (starts_with(line.buf, "diff --git")) {
+			in_header = 0;
+			strbuf_addch(&buf, '\n');
+			if (!util->diff_offset)
+				util->diff_offset = buf.len;
+			strbuf_addbuf(&buf, &line);
+		} else if (in_header) {
+			if (starts_with(line.buf, "Author: ")) {
+				strbuf_addbuf(&buf, &line);
+				strbuf_addstr(&buf, "\n\n");
+			} else if (starts_with(line.buf, "    ")) {
+				strbuf_addbuf(&buf, &line);
+				strbuf_addch(&buf, '\n');
+			}
+			continue;
+		} else if (starts_with(line.buf, "@@ "))
+			strbuf_addstr(&buf, "@@");
+		else if (line.buf[0] && !starts_with(line.buf, "index "))
+			/*
+			 * A completely blank (not ' \n', which is context)
+			 * line is not valid in a diff.  We skip it
+			 * silently, because this neatly handles the blank
+			 * separator line between commits in git-log
+			 * output.
+			 */
+			strbuf_addbuf(&buf, &line);
+		else
+			continue;
+
+		strbuf_addch(&buf, '\n');
+		util->diffsize++;
+	}
+	fclose(in);
+	strbuf_release(&line);
+
+	if (util)
+		string_list_append(list, buf.buf)->util = util;
+	strbuf_release(&buf);
+
+	if (finish_command(&cp))
+		return -1;
+
+	return 0;
+}
+
+static int patch_util_cmp(const void *dummy, const struct patch_util *a,
+		     const struct patch_util *b, const char *keydata)
+{
+	return strcmp(a->diff, keydata ? keydata : b->diff);
+}
+
+static void find_exact_matches(struct string_list *a, struct string_list *b)
+{
+	struct hashmap map;
+	int i;
+
+	hashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);
+
+	/* First, add the patches of a to a hash map */
+	for (i = 0; i < a->nr; i++) {
+		struct patch_util *util = a->items[i].util;
+
+		util->i = i;
+		util->patch = a->items[i].string;
+		util->diff = util->patch + util->diff_offset;
+		hashmap_entry_init(util, strhash(util->diff));
+		hashmap_add(&map, util);
+	}
+
+	/* Now try to find exact matches in b */
+	for (i = 0; i < b->nr; i++) {
+		struct patch_util *util = b->items[i].util, *other;
+
+		util->i = i;
+		util->patch = b->items[i].string;
+		util->diff = util->patch + util->diff_offset;
+		hashmap_entry_init(util, strhash(util->diff));
+		other = hashmap_remove(&map, util, NULL);
+		if (other) {
+			if (other->matching >= 0)
+				BUG("already assigned!");
+
+			other->matching = i;
+			util->matching = other->i;
+		}
+	}
+
+	hashmap_free(&map, 0);
+}
+
+static void diffsize_consume(void *data, char *line, unsigned long len)
+{
+	(*(int *)data)++;
+}
+
+static int diffsize(const char *a, const char *b)
+{
+	xpparam_t pp = { 0 };
+	xdemitconf_t cfg = { 0 };
+	mmfile_t mf1, mf2;
+	int count = 0;
+
+	mf1.ptr = (char *)a;
+	mf1.size = strlen(a);
+	mf2.ptr = (char *)b;
+	mf2.size = strlen(b);
+
+	cfg.ctxlen = 3;
+	if (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))
+		return count;
+
+	error(_("failed to generate diff"));
+	return COST_MAX;
+}
+
+static void get_correspondences(struct string_list *a, struct string_list *b,
+				int creation_factor)
+{
+	int n = a->nr + b->nr;
+	int *cost, c, *a2b, *b2a;
+	int i, j;
+
+	ALLOC_ARRAY(cost, st_mult(n, n));
+	ALLOC_ARRAY(a2b, n);
+	ALLOC_ARRAY(b2a, n);
+
+	for (i = 0; i < a->nr; i++) {
+		struct patch_util *a_util = a->items[i].util;
+
+		for (j = 0; j < b->nr; j++) {
+			struct patch_util *b_util = b->items[j].util;
+
+			if (a_util->matching == j)
+				c = 0;
+			else if (a_util->matching < 0 && b_util->matching < 0)
+				c = diffsize(a_util->diff, b_util->diff);
+			else
+				c = COST_MAX;
+			cost[i + n * j] = c;
+		}
+
+		c = a_util->matching < 0 ?
+			a_util->diffsize * creation_factor / 100 : COST_MAX;
+		for (j = b->nr; j < n; j++)
+			cost[i + n * j] = c;
+	}
+
+	for (j = 0; j < b->nr; j++) {
+		struct patch_util *util = b->items[j].util;
+
+		c = util->matching < 0 ?
+			util->diffsize * creation_factor / 100 : COST_MAX;
+		for (i = a->nr; i < n; i++)
+			cost[i + n * j] = c;
+	}
+
+	for (i = a->nr; i < n; i++)
+		for (j = b->nr; j < n; j++)
+			cost[i + n * j] = 0;
+
+	compute_assignment(n, n, cost, a2b, b2a);
+
+	for (i = 0; i < a->nr; i++)
+		if (a2b[i] >= 0 && a2b[i] < b->nr) {
+			struct patch_util *a_util = a->items[i].util;
+			struct patch_util *b_util = b->items[a2b[i]].util;
+
+			a_util->matching = a2b[i];
+			b_util->matching = i;
+		}
+
+	free(cost);
+	free(a2b);
+	free(b2a);
+}
+
+static const char *short_oid(struct patch_util *util)
+{
+	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
+}
+
+static void output(struct string_list *a, struct string_list *b)
+{
+	int i;
+
+	for (i = 0; i < b->nr; i++) {
+		struct patch_util *util = b->items[i].util, *prev;
+
+		if (util->matching < 0)
+			printf("-: -------- > %d: %s\n",
+					i + 1, short_oid(util));
+		else {
+			prev = a->items[util->matching].util;
+			printf("%d: %s ! %d: %s\n",
+			       util->matching + 1, short_oid(prev),
+			       i + 1, short_oid(util));
+		}
+	}
+
+	for (i = 0; i < a->nr; i++) {
+		struct patch_util *util = a->items[i].util;
+
+		if (util->matching < 0)
+			printf("%d: %s < -: --------\n",
+			       i + 1, short_oid(util));
+	}
+}
+
+int show_range_diff(const char *range1, const char *range2,
+		    int creation_factor)
+{
+	int res = 0;
+
+	struct string_list branch1 = STRING_LIST_INIT_DUP;
+	struct string_list branch2 = STRING_LIST_INIT_DUP;
+
+	if (read_patches(range1, &branch1))
+		res = error(_("could not parse log for '%s'"), range1);
+	if (!res && read_patches(range2, &branch2))
+		res = error(_("could not parse log for '%s'"), range2);
+
+	if (!res) {
+		find_exact_matches(&branch1, &branch2);
+		get_correspondences(&branch1, &branch2, creation_factor);
+		output(&branch1, &branch2);
+	}
+
+	string_list_clear(&branch1, 1);
+	string_list_clear(&branch2, 1);
+
+	return res;
+}
diff --git a/range-diff.h b/range-diff.h
new file mode 100644
index 000000000..dd30449c4
--- /dev/null
+++ b/range-diff.h
@@ -0,0 +1,7 @@
+#ifndef BRANCH_DIFF_H
+#define BRANCH_DIFF_H
+
+int show_range_diff(const char *range1, const char *range2,
+		    int creation_factor);
+
+#endif
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v3 04/20] range-diff: improve the order of the shown commits
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
                       ` (2 preceding siblings ...)
  2018-05-02  0:34     ` [PATCH v3 03/20] range-diff: first rudimentary implementation Johannes Schindelin via GitGitGadget
@ 2018-05-02 10:22     ` Johannes Schindelin via GitGitGadget
  2018-05-02 14:49     ` [PATCH v3 06/20] range-diff: right-trim commit messages Johannes Schindelin via GitGitGadget
                       ` (16 subsequent siblings)
  20 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-05-02 10:22 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

This patch lets `git range-diff` use the same order as tbdiff.

The idea is simple: for left-to-right readers, it is natural to assume
that the `git range-diff` is performed between an older vs a newer
version of the branch. As such, the user is probably more interested in
the question "where did this come from?" rather than "where did that one
go?".

To that end, we list the commits in the order of the second commit range
("the newer version"), inserting the unmatched commits of the first
commit range as soon as all their predecessors have been shown.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 59 +++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 40 insertions(+), 19 deletions(-)

diff --git a/range-diff.c b/range-diff.c
index c374333a4..e71cf0ba7 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -12,7 +12,7 @@ struct patch_util {
 	struct hashmap_entry e;
 	const char *diff, *patch;
 
-	int i;
+	int i, shown;
 	int diffsize;
 	size_t diff_offset;
 	/* the index of the matching item in the other branch, or -1 */
@@ -256,28 +256,49 @@ static const char *short_oid(struct patch_util *util)
 
 static void output(struct string_list *a, struct string_list *b)
 {
-	int i;
-
-	for (i = 0; i < b->nr; i++) {
-		struct patch_util *util = b->items[i].util, *prev;
+	int i = 0, j = 0;
+
+	/*
+	 * We assume the user is really more interested in the second argument
+	 * ("newer" version). To that end, we print the output in the order of
+	 * the RHS (the `b` parameter). To put the LHS (the `a` parameter)
+	 * commits that are no longer in the RHS into a good place, we place
+	 * them once we have shown all of their predecessors in the LHS.
+	 */
+
+	while (i < a->nr || j < b->nr) {
+		struct patch_util *a_util, *b_util;
+		a_util = i < a->nr ? a->items[i].util : NULL;
+		b_util = j < b->nr ? b->items[j].util : NULL;
+
+		/* Skip all the already-shown commits from the LHS. */
+		while (i < a->nr && a_util->shown)
+			a_util = ++i < a->nr ? a->items[i].util : NULL;
+
+		/* Show unmatched LHS commit whose predecessors were shown. */
+		if (i < a->nr && a_util->matching < 0) {
+			printf("%d: %s < -: --------\n",
+			       i + 1, short_oid(a_util));
+			i++;
+			continue;
+		}
 
-		if (util->matching < 0)
+		/* Show unmatched RHS commits. */
+		while (j < b->nr && b_util->matching < 0) {
 			printf("-: -------- > %d: %s\n",
-					i + 1, short_oid(util));
-		else {
-			prev = a->items[util->matching].util;
-			printf("%d: %s ! %d: %s\n",
-			       util->matching + 1, short_oid(prev),
-			       i + 1, short_oid(util));
+			       j + 1, short_oid(b_util));
+			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
-	}
-
-	for (i = 0; i < a->nr; i++) {
-		struct patch_util *util = a->items[i].util;
 
-		if (util->matching < 0)
-			printf("%d: %s < -: --------\n",
-			       i + 1, short_oid(util));
+		/* Show matching LHS/RHS pair. */
+		if (j < b->nr) {
+			a_util = a->items[b_util->matching].util;
+			printf("%d: %s ! %d: %s\n",
+			       b_util->matching + 1, short_oid(a_util),
+			       j + 1, short_oid(b_util));
+			a_util->shown = 1;
+			j++;
+		}
 	}
 }
 
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v3 06/20] range-diff: right-trim commit messages
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
                       ` (3 preceding siblings ...)
  2018-05-02 10:22     ` [PATCH v3 04/20] range-diff: improve the order of the shown commits Johannes Schindelin via GitGitGadget
@ 2018-05-02 14:49     ` Johannes Schindelin via GitGitGadget
  2018-05-02 14:52     ` [PATCH v3 07/20] range-diff: indent the diffs just like tbdiff Johannes Schindelin via GitGitGadget
                       ` (15 subsequent siblings)
  20 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-05-02 14:49 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When comparing commit messages, we need to keep in mind that they are
indented by four spaces. That is, empty lines are no longer empty, but
have "trailing whitespace". When displaying them in color, that results
in those nagging red lines.

Let's just right-trim the lines in the commit message, it's not like
trailing white-space in the commit messages are important enough to care
about in `git range-diff`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/range-diff.c b/range-diff.c
index 530f2fc32..8d3b96455 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -85,6 +85,7 @@ static int read_patches(const char *range, struct string_list *list)
 				strbuf_addbuf(&buf, &line);
 				strbuf_addstr(&buf, "\n\n");
 			} else if (starts_with(line.buf, "    ")) {
+				strbuf_rtrim(&line);
 				strbuf_addbuf(&buf, &line);
 				strbuf_addch(&buf, '\n');
 			}
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v3 07/20] range-diff: indent the diffs just like tbdiff
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
                       ` (4 preceding siblings ...)
  2018-05-02 14:49     ` [PATCH v3 06/20] range-diff: right-trim commit messages Johannes Schindelin via GitGitGadget
@ 2018-05-02 14:52     ` Johannes Schindelin via GitGitGadget
  2018-05-02 14:53     ` [PATCH v3 08/20] range-diff: suppress the diff headers Johannes Schindelin via GitGitGadget
                       ` (14 subsequent siblings)
  20 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-05-02 14:52 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

The main information in the `range-diff` view comes from the list of
matching and non-matching commits, the diffs are additional information.
Indenting them helps with the reading flow.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/range-diff.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 5f12bbfa9..660e1f961 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -11,6 +11,11 @@ N_("git range-diff [<options>] <base> <old-tip> <new-tip>"),
 NULL
 };
 
+static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)
+{
+	return data;
+}
+
 int cmd_range_diff(int argc, const char **argv, const char *prefix)
 {
 	int creation_factor = 60;
@@ -21,12 +26,16 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 	int i, j, res = 0;
+	struct strbuf four_spaces = STRBUF_INIT;
 	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
 
 	git_config(git_diff_ui_config, NULL);
 
 	diff_setup(&diffopt);
 	diffopt.output_format = DIFF_FORMAT_PATCH;
+	diffopt.output_prefix = output_prefix_cb;
+	strbuf_addstr(&four_spaces, "    ");
+	diffopt.output_prefix_data = &four_spaces;
 
 	argc = parse_options(argc, argv, NULL, options,
 			builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);
@@ -78,6 +87,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 
 	strbuf_release(&range1);
 	strbuf_release(&range2);
+	strbuf_release(&four_spaces);
 
 	return res;
 }
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v3 08/20] range-diff: suppress the diff headers
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
                       ` (5 preceding siblings ...)
  2018-05-02 14:52     ` [PATCH v3 07/20] range-diff: indent the diffs just like tbdiff Johannes Schindelin via GitGitGadget
@ 2018-05-02 14:53     ` Johannes Schindelin via GitGitGadget
  2018-05-02 15:19     ` [PATCH v3 11/20] range-diff: add tests Thomas Rast via GitGitGadget
                       ` (13 subsequent siblings)
  20 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-05-02 14:53 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When showing the diff between corresponding patches of the two branch
versions, we have to make up a fake filename to run the diff machinery.

That filename does not carry any meaningful information, hence tbdiff
suppresses it. So we should, too.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/range-diff.c | 1 +
 diff.c               | 5 ++++-
 diff.h               | 1 +
 3 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 660e1f961..7c0967220 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -33,6 +33,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 
 	diff_setup(&diffopt);
 	diffopt.output_format = DIFF_FORMAT_PATCH;
+	diffopt.flags.suppress_diff_headers = 1;
 	diffopt.output_prefix = output_prefix_cb;
 	strbuf_addstr(&four_spaces, "    ");
 	diffopt.output_prefix_data = &four_spaces;
diff --git a/diff.c b/diff.c
index 639eb646b..8c568cbe0 100644
--- a/diff.c
+++ b/diff.c
@@ -3189,13 +3189,16 @@ static void builtin_diff(const char *name_a,
 		memset(&xpp, 0, sizeof(xpp));
 		memset(&xecfg, 0, sizeof(xecfg));
 		memset(&ecbdata, 0, sizeof(ecbdata));
+		if (o->flags.suppress_diff_headers)
+			lbl[0] = NULL;
 		ecbdata.label_path = lbl;
 		ecbdata.color_diff = want_color(o->use_color);
 		ecbdata.ws_rule = whitespace_rule(name_b);
 		if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
 			check_blank_at_eof(&mf1, &mf2, &ecbdata);
 		ecbdata.opt = o;
-		ecbdata.header = header.len ? &header : NULL;
+		if (header.len && !o->flags.suppress_diff_headers)
+			ecbdata.header = &header;
 		xpp.flags = o->xdl_opts;
 		xpp.anchors = o->anchors;
 		xpp.anchors_nr = o->anchors_nr;
diff --git a/diff.h b/diff.h
index dedac472c..928f48995 100644
--- a/diff.h
+++ b/diff.h
@@ -94,6 +94,7 @@ struct diff_flags {
 	unsigned funccontext:1;
 	unsigned default_follow_renames:1;
 	unsigned stat_with_summary:1;
+	unsigned suppress_diff_headers:1;
 };
 
 static inline void diff_flags_or(struct diff_flags *a,
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v3 11/20] range-diff: add tests
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
                       ` (6 preceding siblings ...)
  2018-05-02 14:53     ` [PATCH v3 08/20] range-diff: suppress the diff headers Johannes Schindelin via GitGitGadget
@ 2018-05-02 15:19     ` Thomas Rast via GitGitGadget
  2018-07-16  7:28       ` Eric Sunshine
  2018-05-02 21:35     ` [PATCH v3 09/20] range-diff: adjust the output of the commit pairs Johannes Schindelin via GitGitGadget
                       ` (12 subsequent siblings)
  20 siblings, 1 reply; 387+ messages in thread
From: Thomas Rast via GitGitGadget @ 2018-05-02 15:19 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Thomas Rast

From: Thomas Rast <tr@thomasrast.ch>

These are essentially lifted from https://github.com/trast/tbdiff, with
light touch-ups to account for the command now being an option of `git
branch`.

Apart from renaming `tbdiff` to `range-diff`, only one test case needed
to be adjusted: 11 - 'changed message'.

The underlying reason it had to be adjusted is that diff generation is
sometimes ambiguous. In this case, a comment line and an empty line are
added, but it is ambiguous whether they were added after the existing
empty line, or whether an empty line and the comment line are added
*before* the existing empty line. And apparently xdiff picks a different
option here than Python's difflib.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/.gitattributes       |   1 +
 t/t3206-range-diff.sh  | 145 ++++++++++
 t/t3206/history.export | 604 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 750 insertions(+)
 create mode 100755 t/t3206-range-diff.sh
 create mode 100644 t/t3206/history.export

diff --git a/t/.gitattributes b/t/.gitattributes
index 3bd959ae5..af15d5aee 100644
--- a/t/.gitattributes
+++ b/t/.gitattributes
@@ -18,5 +18,6 @@ t[0-9][0-9][0-9][0-9]/* -whitespace
 /t5515/* eol=lf
 /t556x_common eol=lf
 /t7500/* eol=lf
+/t7910/* eol=lf
 /t8005/*.txt eol=lf
 /t9*/*.dump eol=lf
diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
new file mode 100755
index 000000000..2237c7f4a
--- /dev/null
+++ b/t/t3206-range-diff.sh
@@ -0,0 +1,145 @@
+#!/bin/sh
+
+test_description='range-diff tests'
+
+. ./test-lib.sh
+
+# Note that because of the range-diff's heuristics, test_commit does more
+# harm than good.  We need some real history.
+
+test_expect_success 'setup' '
+	git fast-import < "$TEST_DIRECTORY"/t3206/history.export
+'
+
+test_expect_success 'simple A..B A..C (unmodified)' '
+	git range-diff --no-color master..topic master..unmodified \
+		>actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  35b9b25 s/5/A/
+	2:  fccce22 = 2:  de345ab s/4/A/
+	3:  147e64e = 3:  9af6654 s/11/B/
+	4:  a63e992 = 4:  2901f77 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'simple B...C (unmodified)' '
+	git range-diff --no-color topic...unmodified >actual &&
+	# same "expected" as above
+	test_cmp expected actual
+'
+
+test_expect_success 'simple A B C (unmodified)' '
+	git range-diff --no-color master topic unmodified >actual &&
+	# same "expected" as above
+	test_cmp expected actual
+'
+
+test_expect_success 'trivial reordering' '
+	git range-diff --no-color master topic reordered >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  aca177a s/5/A/
+	3:  147e64e = 2:  14ad629 s/11/B/
+	4:  a63e992 = 3:  ee58208 s/12/B/
+	2:  fccce22 = 4:  307b27a s/4/A/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'removed a commit' '
+	git range-diff --no-color master topic removed >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  7657159 s/5/A/
+	2:  fccce22 < -:  ------- s/4/A/
+	3:  147e64e = 2:  43d84d3 s/11/B/
+	4:  a63e992 = 3:  a740396 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'added a commit' '
+	git range-diff --no-color master topic added >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  2716022 s/5/A/
+	2:  fccce22 = 2:  b62accd s/4/A/
+	-:  ------- > 3:  df46cfa s/6/A/
+	3:  147e64e = 4:  3e64548 s/11/B/
+	4:  a63e992 = 5:  12b4063 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'new base, A B C' '
+	git range-diff --no-color master topic rebased >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  cc9c443 s/5/A/
+	2:  fccce22 = 2:  c5d9641 s/4/A/
+	3:  147e64e = 3:  28cc2b6 s/11/B/
+	4:  a63e992 = 4:  5628ab7 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'new base, B...C' '
+	# this syntax includes the commits from master!
+	git range-diff --no-color topic...rebased >actual &&
+	cat >expected <<-EOF &&
+	-:  ------- > 1:  a31b12e unrelated
+	1:  4de457d = 2:  cc9c443 s/5/A/
+	2:  fccce22 = 3:  c5d9641 s/4/A/
+	3:  147e64e = 4:  28cc2b6 s/11/B/
+	4:  a63e992 = 5:  5628ab7 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'changed commit' '
+	git range-diff --no-color topic...changed >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  a4b3333 s/5/A/
+	2:  fccce22 = 2:  f51d370 s/4/A/
+	3:  147e64e ! 3:  0559556 s/11/B/
+	    @@ -10,7 +10,7 @@
+	      9
+	      10
+	     -11
+	    -+B
+	    ++BB
+	      12
+	      13
+	      14
+	4:  a63e992 ! 4:  d966c5c s/12/B/
+	    @@ -8,7 +8,7 @@
+	     @@
+	      9
+	      10
+	    - B
+	    + BB
+	     -12
+	     +B
+	      13
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'changed message' '
+	git range-diff --no-color topic...changed-message >actual &&
+	sed s/Z/\ /g >expected <<-EOF &&
+	1:  4de457d = 1:  f686024 s/5/A/
+	2:  fccce22 ! 2:  4ab067d s/4/A/
+	    @@ -2,6 +2,8 @@
+	    Z
+	    Z    s/4/A/
+	    Z
+	    +    Also a silly comment here!
+	    +
+	    Zdiff --git a/file b/file
+	    Z--- a/file
+	    Z+++ b/file
+	3:  147e64e = 3:  b9cb956 s/11/B/
+	4:  a63e992 = 4:  8add5f1 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_done
diff --git a/t/t3206/history.export b/t/t3206/history.export
new file mode 100644
index 000000000..b8ffff094
--- /dev/null
+++ b/t/t3206/history.export
@@ -0,0 +1,604 @@
+blob
+mark :1
+data 51
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+reset refs/heads/removed
+commit refs/heads/removed
+mark :2
+author Thomas Rast <trast@inf.ethz.ch> 1374424921 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374484724 +0200
+data 8
+initial
+M 100644 :1 file
+
+blob
+mark :3
+data 51
+1
+2
+3
+4
+A
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :4
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+blob
+mark :5
+data 51
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :6
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+data 7
+s/4/A/
+from :4
+M 100644 :5 file
+
+blob
+mark :7
+data 50
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+B
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :8
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+data 8
+s/11/B/
+from :6
+M 100644 :7 file
+
+blob
+mark :9
+data 49
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+B
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :10
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+data 8
+s/12/B/
+from :8
+M 100644 :9 file
+
+blob
+mark :11
+data 10
+unrelated
+
+commit refs/heads/master
+mark :12
+author Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200
+data 10
+unrelated
+from :2
+M 100644 :11 otherfile
+
+commit refs/heads/rebased
+mark :13
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485137 +0200
+data 7
+s/5/A/
+from :12
+M 100644 :3 file
+
+commit refs/heads/rebased
+mark :14
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
+data 7
+s/4/A/
+from :13
+M 100644 :5 file
+
+commit refs/heads/rebased
+mark :15
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
+data 8
+s/11/B/
+from :14
+M 100644 :7 file
+
+commit refs/heads/rebased
+mark :16
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
+data 8
+s/12/B/
+from :15
+M 100644 :9 file
+
+commit refs/heads/added
+mark :17
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/added
+mark :18
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 7
+s/4/A/
+from :17
+M 100644 :5 file
+
+blob
+mark :19
+data 51
+1
+2
+3
+A
+A
+A
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/added
+mark :20
+author Thomas Rast <trast@inf.ethz.ch> 1374485186 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 7
+s/6/A/
+from :18
+M 100644 :19 file
+
+blob
+mark :21
+data 50
+1
+2
+3
+A
+A
+A
+7
+8
+9
+10
+B
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/added
+mark :22
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 8
+s/11/B/
+from :20
+M 100644 :21 file
+
+blob
+mark :23
+data 49
+1
+2
+3
+A
+A
+A
+7
+8
+9
+10
+B
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/added
+mark :24
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 8
+s/12/B/
+from :22
+M 100644 :23 file
+
+commit refs/heads/reordered
+mark :25
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+blob
+mark :26
+data 50
+1
+2
+3
+4
+A
+6
+7
+8
+9
+10
+B
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/reordered
+mark :27
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 8
+s/11/B/
+from :25
+M 100644 :26 file
+
+blob
+mark :28
+data 49
+1
+2
+3
+4
+A
+6
+7
+8
+9
+10
+B
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/reordered
+mark :29
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 8
+s/12/B/
+from :27
+M 100644 :28 file
+
+commit refs/heads/reordered
+mark :30
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 7
+s/4/A/
+from :29
+M 100644 :9 file
+
+commit refs/heads/changed
+mark :31
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/changed
+mark :32
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 7
+s/4/A/
+from :31
+M 100644 :5 file
+
+blob
+mark :33
+data 51
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+BB
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/changed
+mark :34
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 8
+s/11/B/
+from :32
+M 100644 :33 file
+
+blob
+mark :35
+data 50
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+BB
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/changed
+mark :36
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 8
+s/12/B/
+from :34
+M 100644 :35 file
+
+commit refs/heads/changed-message
+mark :37
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/changed-message
+mark :38
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200
+data 35
+s/4/A/
+
+Also a silly comment here!
+from :37
+M 100644 :5 file
+
+commit refs/heads/changed-message
+mark :39
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200
+data 8
+s/11/B/
+from :38
+M 100644 :7 file
+
+commit refs/heads/changed-message
+mark :40
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200
+data 8
+s/12/B/
+from :39
+M 100644 :9 file
+
+commit refs/heads/unmodified
+mark :41
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/unmodified
+mark :42
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200
+data 7
+s/4/A/
+from :41
+M 100644 :5 file
+
+commit refs/heads/unmodified
+mark :43
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200
+data 8
+s/11/B/
+from :42
+M 100644 :7 file
+
+commit refs/heads/unmodified
+mark :44
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200
+data 8
+s/12/B/
+from :43
+M 100644 :9 file
+
+commit refs/heads/removed
+mark :45
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/removed
+mark :46
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
+data 8
+s/11/B/
+from :45
+M 100644 :26 file
+
+commit refs/heads/removed
+mark :47
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
+data 8
+s/12/B/
+from :46
+M 100644 :28 file
+
+reset refs/heads/removed
+from :47
+
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v3 09/20] range-diff: adjust the output of the commit pairs
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
                       ` (7 preceding siblings ...)
  2018-05-02 15:19     ` [PATCH v3 11/20] range-diff: add tests Thomas Rast via GitGitGadget
@ 2018-05-02 21:35     ` Johannes Schindelin via GitGitGadget
  2018-07-16  7:21       ` Eric Sunshine
  2018-05-02 23:32     ` [PATCH v3 12/20] range-diff: use color for " Johannes Schindelin via GitGitGadget
                       ` (11 subsequent siblings)
  20 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-05-02 21:35 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

This change brings `git range-diff` yet another step closer to
feature parity with tbdiff: it now shows the oneline, too, and indicates
with `=` when the commits have identical diffs.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 66 +++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 57 insertions(+), 9 deletions(-)

diff --git a/range-diff.c b/range-diff.c
index 8d3b96455..0e0e77106 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -7,6 +7,8 @@
 #include "xdiff-interface.h"
 #include "linear-assignment.h"
 #include "diffcore.h"
+#include "commit.h"
+#include "pretty.h"
 
 struct patch_util {
 	/* For the search for an exact match */
@@ -251,9 +253,57 @@ static void get_correspondences(struct string_list *a, struct string_list *b,
 	free(b2a);
 }
 
-static const char *short_oid(struct patch_util *util)
+static void output_pair_header(struct strbuf *buf,
+			       struct patch_util *a_util,
+			       struct patch_util *b_util)
 {
-	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
+	static char *dashes;
+	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
+	struct commit *commit;
+
+	if (!dashes) {
+		char *p;
+
+		dashes = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV));
+		for (p = dashes; *p; p++)
+			*p = '-';
+	}
+
+	strbuf_reset(buf);
+	if (!a_util)
+		strbuf_addf(buf, "-:  %s ", dashes);
+	else
+		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
+			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
+
+	if (!a_util)
+		strbuf_addch(buf, '>');
+	else if (!b_util)
+		strbuf_addch(buf, '<');
+	else if (strcmp(a_util->patch, b_util->patch))
+		strbuf_addch(buf, '!');
+	else
+		strbuf_addch(buf, '=');
+
+	if (!b_util)
+		strbuf_addf(buf, " -:  %s", dashes);
+	else
+		strbuf_addf(buf, " %d:  %s", b_util->i + 1,
+			    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));
+
+	commit = lookup_commit_reference(oid);
+	if (commit) {
+		const char *commit_buffer = get_commit_buffer(commit, NULL);
+		const char *subject;
+
+		find_commit_subject(commit_buffer, &subject);
+		strbuf_addch(buf, ' ');
+		format_subject(buf, subject, " ");
+		unuse_commit_buffer(commit, commit_buffer);
+	}
+	strbuf_addch(buf, '\n');
+
+	fwrite(buf->buf, buf->len, 1, stdout);
 }
 
 static struct diff_filespec *get_filespec(const char *name, const char *p)
@@ -282,6 +332,7 @@ static void patch_diff(const char *a, const char *b,
 static void output(struct string_list *a, struct string_list *b,
 		   struct diff_options *diffopt)
 {
+	struct strbuf buf = STRBUF_INIT;
 	int i = 0, j = 0;
 
 	/*
@@ -303,25 +354,21 @@ static void output(struct string_list *a, struct string_list *b,
 
 		/* Show unmatched LHS commit whose predecessors were shown. */
 		if (i < a->nr && a_util->matching < 0) {
-			printf("%d: %s < -: --------\n",
-			       i + 1, short_oid(a_util));
+			output_pair_header(&buf, a_util, NULL);
 			i++;
 			continue;
 		}
 
 		/* Show unmatched RHS commits. */
 		while (j < b->nr && b_util->matching < 0) {
-			printf("-: -------- > %d: %s\n",
-			       j + 1, short_oid(b_util));
+			output_pair_header(&buf, NULL, b_util);
 			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
 
 		/* Show matching LHS/RHS pair. */
 		if (j < b->nr) {
 			a_util = a->items[b_util->matching].util;
-			printf("%d: %s ! %d: %s\n",
-			       b_util->matching + 1, short_oid(a_util),
-			       j + 1, short_oid(b_util));
+			output_pair_header(&buf, a_util, b_util);
 			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
 				patch_diff(a->items[b_util->matching].string,
 					   b->items[j].string, diffopt);
@@ -329,6 +376,7 @@ static void output(struct string_list *a, struct string_list *b,
 			j++;
 		}
 	}
+	strbuf_release(&buf);
 }
 
 int show_range_diff(const char *range1, const char *range2,
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v3 12/20] range-diff: use color for the commit pairs
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
                       ` (8 preceding siblings ...)
  2018-05-02 21:35     ` [PATCH v3 09/20] range-diff: adjust the output of the commit pairs Johannes Schindelin via GitGitGadget
@ 2018-05-02 23:32     ` Johannes Schindelin via GitGitGadget
  2018-05-03  0:14     ` [PATCH v3 13/20] color: add the meta color GIT_COLOR_REVERSE Johannes Schindelin via GitGitGadget
                       ` (10 subsequent siblings)
  20 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-05-02 23:32 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

Arguably the most important part of `git range-diff`'s output is the
list of commits in the two branches, together with their relationships.

For that reason, tbdiff introduced color-coding that is pretty
intuitive, especially for unchanged patches (all dim yellow, like the
first line in `git show`'s output) vs modified patches (old commit is
red, new commit is green). Let's imitate that color scheme.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 47 ++++++++++++++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 13 deletions(-)

diff --git a/range-diff.c b/range-diff.c
index 8df73da4e..870c3680c 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -254,13 +254,19 @@ static void get_correspondences(struct string_list *a, struct string_list *b,
 	free(b2a);
 }
 
-static void output_pair_header(struct strbuf *buf,
+static void output_pair_header(struct diff_options *diffopt, struct strbuf *buf,
 			       struct patch_util *a_util,
 			       struct patch_util *b_util)
 {
 	static char *dashes;
 	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
 	struct commit *commit;
+	char status;
+	const char *color_reset = diff_get_color_opt(diffopt, DIFF_RESET);
+	const char *color_old = diff_get_color_opt(diffopt, DIFF_FILE_OLD);
+	const char *color_new = diff_get_color_opt(diffopt, DIFF_FILE_NEW);
+	const char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT);
+	const char *color;
 
 	if (!dashes) {
 		char *p;
@@ -270,21 +276,33 @@ static void output_pair_header(struct strbuf *buf,
 			*p = '-';
 	}
 
+	if (!b_util) {
+		color = color_old;
+		status = '<';
+	} else if (!a_util) {
+		color = color_new;
+		status = '>';
+	} else if (strcmp(a_util->patch, b_util->patch)) {
+		color = color_commit;
+		status = '!';
+	} else {
+		color = color_commit;
+		status = '=';
+	}
+
 	strbuf_reset(buf);
+	strbuf_addstr(buf, status == '!' ? color_old : color);
 	if (!a_util)
 		strbuf_addf(buf, "-:  %s ", dashes);
 	else
 		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
 			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
 
-	if (!a_util)
-		strbuf_addch(buf, '>');
-	else if (!b_util)
-		strbuf_addch(buf, '<');
-	else if (strcmp(a_util->patch, b_util->patch))
-		strbuf_addch(buf, '!');
-	else
-		strbuf_addch(buf, '=');
+	if (status == '!')
+		strbuf_addf(buf, "%s%s", color_reset, color);
+	strbuf_addch(buf, status);
+	if (status == '!')
+		strbuf_addf(buf, "%s%s", color_reset, color_new);
 
 	if (!b_util)
 		strbuf_addf(buf, " -:  %s", dashes);
@@ -297,12 +315,15 @@ static void output_pair_header(struct strbuf *buf,
 		const char *commit_buffer = get_commit_buffer(commit, NULL);
 		const char *subject;
 
+		if (status == '!')
+			strbuf_addf(buf, "%s%s", color_reset, color);
+
 		find_commit_subject(commit_buffer, &subject);
 		strbuf_addch(buf, ' ');
 		format_subject(buf, subject, " ");
 		unuse_commit_buffer(commit, commit_buffer);
 	}
-	strbuf_addch(buf, '\n');
+	strbuf_addf(buf, "%s\n", color_reset);
 
 	fwrite(buf->buf, buf->len, 1, stdout);
 }
@@ -360,21 +381,21 @@ static void output(struct string_list *a, struct string_list *b,
 
 		/* Show unmatched LHS commit whose predecessors were shown. */
 		if (i < a->nr && a_util->matching < 0) {
-			output_pair_header(&buf, a_util, NULL);
+			output_pair_header(diffopt, &buf, a_util, NULL);
 			i++;
 			continue;
 		}
 
 		/* Show unmatched RHS commits. */
 		while (j < b->nr && b_util->matching < 0) {
-			output_pair_header(&buf, NULL, b_util);
+			output_pair_header(diffopt, &buf, NULL, b_util);
 			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
 
 		/* Show matching LHS/RHS pair. */
 		if (j < b->nr) {
 			a_util = a->items[b_util->matching].util;
-			output_pair_header(&buf, a_util, b_util);
+			output_pair_header(diffopt, &buf, a_util, b_util);
 			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
 				patch_diff(a->items[b_util->matching].string,
 					   b->items[j].string, diffopt);
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v3 13/20] color: add the meta color GIT_COLOR_REVERSE
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
                       ` (9 preceding siblings ...)
  2018-05-02 23:32     ` [PATCH v3 12/20] range-diff: use color for " Johannes Schindelin via GitGitGadget
@ 2018-05-03  0:14     ` Johannes Schindelin via GitGitGadget
  2018-05-03  0:17     ` [PATCH v3 14/20] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin via GitGitGadget
                       ` (9 subsequent siblings)
  20 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-05-03  0:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

This "color" simply reverts background and foreground. It will be used
in the upcoming "dual color" mode of `git range-diff`, where we will
reverse colors for the -/+ markers and the fragment headers of the
"outer" diff.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 color.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/color.h b/color.h
index 5b744e1bc..33e786342 100644
--- a/color.h
+++ b/color.h
@@ -44,6 +44,7 @@ struct strbuf;
 #define GIT_COLOR_BG_CYAN	"\033[46m"
 #define GIT_COLOR_FAINT		"\033[2m"
 #define GIT_COLOR_FAINT_ITALIC	"\033[2;3m"
+#define GIT_COLOR_REVERSE	"\033[7m"
 
 /* A special value meaning "no color selected" */
 #define GIT_COLOR_NIL "NIL"
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v3 14/20] diff: add an internal option to dual-color diffs of diffs
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
                       ` (10 preceding siblings ...)
  2018-05-03  0:14     ` [PATCH v3 13/20] color: add the meta color GIT_COLOR_REVERSE Johannes Schindelin via GitGitGadget
@ 2018-05-03  0:17     ` Johannes Schindelin via GitGitGadget
  2018-07-09 19:29       ` Stefan Beller
  2018-05-03  1:01     ` [PATCH v3 15/20] range-diff: offer to dual-color the diffs Johannes Schindelin via GitGitGadget
                       ` (8 subsequent siblings)
  20 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-05-03  0:17 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When diffing diffs, it can be quite daunting to figure out what the heck
is going on, as there are nested +/- signs.

Let's make this easier by adding a flag in diff_options that allows
color-coding the outer diff sign with inverted colors, so that the
preimage and postimage is colored like the diff it is.

Of course, this really only makes sense when the preimage and postimage
*are* diffs. So let's not expose this flag via a command-line option for
now.

This is a feature that was invented by git-tbdiff, and it will be used
by `git range-diff` in the next commit, by offering it via a new option:
`--dual-color`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 diff.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++-----------
 diff.h |  1 +
 2 files changed, 69 insertions(+), 15 deletions(-)

diff --git a/diff.c b/diff.c
index 8c568cbe0..26445ffa1 100644
--- a/diff.c
+++ b/diff.c
@@ -562,14 +562,18 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,
 	ecbdata->blank_at_eof_in_postimage = (at - l2) + 1;
 }
 
-static void emit_line_0(struct diff_options *o, const char *set, const char *reset,
+static void emit_line_0(struct diff_options *o,
+			const char *set, unsigned reverse, const char *reset,
 			int first, const char *line, int len)
 {
 	int has_trailing_newline, has_trailing_carriage_return;
 	int nofirst;
 	FILE *file = o->file;
 
-	fputs(diff_line_prefix(o), file);
+	if (first)
+		fputs(diff_line_prefix(o), file);
+	else if (!len)
+		return;
 
 	if (len == 0) {
 		has_trailing_newline = (first == '\n');
@@ -587,8 +591,10 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res
 	}
 
 	if (len || !nofirst) {
+		if (reverse && want_color(o->use_color))
+			fputs(GIT_COLOR_REVERSE, file);
 		fputs(set, file);
-		if (!nofirst)
+		if (first && !nofirst)
 			fputc(first, file);
 		fwrite(line, len, 1, file);
 		fputs(reset, file);
@@ -602,7 +608,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res
 static void emit_line(struct diff_options *o, const char *set, const char *reset,
 		      const char *line, int len)
 {
-	emit_line_0(o, set, reset, line[0], line+1, len-1);
+	emit_line_0(o, set, 0, reset, line[0], line+1, len-1);
 }
 
 enum diff_symbol {
@@ -962,7 +968,8 @@ static void dim_moved_lines(struct diff_options *o)
 
 static void emit_line_ws_markup(struct diff_options *o,
 				const char *set, const char *reset,
-				const char *line, int len, char sign,
+				const char *line, int len,
+				const char *set_sign, char sign,
 				unsigned ws_rule, int blank_at_eof)
 {
 	const char *ws = NULL;
@@ -973,14 +980,20 @@ static void emit_line_ws_markup(struct diff_options *o,
 			ws = NULL;
 	}
 
-	if (!ws)
-		emit_line_0(o, set, reset, sign, line, len);
-	else if (blank_at_eof)
+	if (!ws && !set_sign)
+		emit_line_0(o, set, 0, reset, sign, line, len);
+	else if (!ws) {
+		/* Emit just the prefix, then the rest. */
+		emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
+			    sign, "", 0);
+		emit_line_0(o, set, 0, reset, 0, line, len);
+	} else if (blank_at_eof)
 		/* Blank line at EOF - paint '+' as well */
-		emit_line_0(o, ws, reset, sign, line, len);
+		emit_line_0(o, ws, 0, reset, sign, line, len);
 	else {
 		/* Emit just the prefix, then the rest. */
-		emit_line_0(o, set, reset, sign, "", 0);
+		emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
+			    sign, "", 0);
 		ws_check_emit(line, len, ws_rule,
 			      o->file, set, reset, ws);
 	}
@@ -990,7 +1003,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 					 struct emitted_diff_symbol *eds)
 {
 	static const char *nneof = " No newline at end of file\n";
-	const char *context, *reset, *set, *meta, *fraginfo;
+	const char *context, *reset, *set, *set_sign, *meta, *fraginfo;
 	struct strbuf sb = STRBUF_INIT;
 
 	enum diff_symbol s = eds->s;
@@ -1003,7 +1016,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 		context = diff_get_color_opt(o, DIFF_CONTEXT);
 		reset = diff_get_color_opt(o, DIFF_RESET);
 		putc('\n', o->file);
-		emit_line_0(o, context, reset, '\\',
+		emit_line_0(o, context, 0, reset, '\\',
 			    nneof, strlen(nneof));
 		break;
 	case DIFF_SYMBOL_SUBMODULE_HEADER:
@@ -1030,7 +1043,18 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 	case DIFF_SYMBOL_CONTEXT:
 		set = diff_get_color_opt(o, DIFF_CONTEXT);
 		reset = diff_get_color_opt(o, DIFF_RESET);
-		emit_line_ws_markup(o, set, reset, line, len, ' ',
+		set_sign = NULL;
+		if (o->flags.dual_color_diffed_diffs) {
+			char c = !len ? 0 : line[0];
+
+			if (c == '+')
+				set = diff_get_color_opt(o, DIFF_FILE_NEW);
+			else if (c == '@')
+				set = diff_get_color_opt(o, DIFF_FRAGINFO);
+			else if (c == '-')
+				set = diff_get_color_opt(o, DIFF_FILE_OLD);
+		}
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',
 				    flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);
 		break;
 	case DIFF_SYMBOL_PLUS:
@@ -1057,7 +1081,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 			set = diff_get_color_opt(o, DIFF_FILE_NEW);
 		}
 		reset = diff_get_color_opt(o, DIFF_RESET);
-		emit_line_ws_markup(o, set, reset, line, len, '+',
+		if (!o->flags.dual_color_diffed_diffs)
+			set_sign = NULL;
+		else {
+			char c = !len ? 0 : line[0];
+
+			set_sign = set;
+			if (c == '-')
+				set = diff_get_color_opt(o, DIFF_FILE_OLD);
+			else if (c == '@')
+				set = diff_get_color_opt(o, DIFF_FRAGINFO);
+			else if (c != '+')
+				set = diff_get_color_opt(o, DIFF_CONTEXT);
+		}
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK,
 				    flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);
 		break;
@@ -1085,7 +1122,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 			set = diff_get_color_opt(o, DIFF_FILE_OLD);
 		}
 		reset = diff_get_color_opt(o, DIFF_RESET);
-		emit_line_ws_markup(o, set, reset, line, len, '-',
+		if (!o->flags.dual_color_diffed_diffs)
+			set_sign = NULL;
+		else {
+			char c = !len ? 0 : line[0];
+
+			set_sign = set;
+			if (c == '+')
+				set = diff_get_color_opt(o, DIFF_FILE_NEW);
+			else if (c == '@')
+				set = diff_get_color_opt(o, DIFF_FRAGINFO);
+			else if (c != '-')
+				set = diff_get_color_opt(o, DIFF_CONTEXT);
+		}
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, '-',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);
 		break;
 	case DIFF_SYMBOL_WORDS_PORCELAIN:
@@ -1276,6 +1326,7 @@ static void emit_hunk_header(struct emit_callback *ecbdata,
 	const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
 	const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);
 	const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
+	const char *reverse = ecbdata->color_diff ? GIT_COLOR_REVERSE : "";
 	static const char atat[2] = { '@', '@' };
 	const char *cp, *ep;
 	struct strbuf msgbuf = STRBUF_INIT;
@@ -1296,6 +1347,8 @@ static void emit_hunk_header(struct emit_callback *ecbdata,
 	ep += 2; /* skip over @@ */
 
 	/* The hunk header in fraginfo color */
+	if (ecbdata->opt->flags.dual_color_diffed_diffs)
+		strbuf_addstr(&msgbuf, reverse);
 	strbuf_addstr(&msgbuf, frag);
 	strbuf_add(&msgbuf, line, ep - line);
 	strbuf_addstr(&msgbuf, reset);
diff --git a/diff.h b/diff.h
index 928f48995..79beb6eea 100644
--- a/diff.h
+++ b/diff.h
@@ -95,6 +95,7 @@ struct diff_flags {
 	unsigned default_follow_renames:1;
 	unsigned stat_with_summary:1;
 	unsigned suppress_diff_headers:1;
+	unsigned dual_color_diffed_diffs:1;
 };
 
 static inline void diff_flags_or(struct diff_flags *a,
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v3 15/20] range-diff: offer to dual-color the diffs
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
                       ` (11 preceding siblings ...)
  2018-05-03  0:17     ` [PATCH v3 14/20] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin via GitGitGadget
@ 2018-05-03  1:01     ` Johannes Schindelin via GitGitGadget
  2018-05-03  1:11     ` [PATCH v3 16/20] range-diff --dual-color: work around bogus white-space warning Johannes Schindelin via GitGitGadget
                       ` (7 subsequent siblings)
  20 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-05-03  1:01 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When showing what changed between old and new commits, we show a diff of
the patches. This diff is a diff between diffs, therefore there are
nested +/- signs, and it can be relatively hard to understand what is
going on.

With the --dual-color option, the preimage and the postimage are colored
like the diffs they are, and the *outer* +/- sign is inverted for
clarity.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/range-diff.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 7c0967220..e8f7fe452 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -20,9 +20,12 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 {
 	int creation_factor = 60;
 	struct diff_options diffopt = { NULL };
+	int dual_color = 0;
 	struct option options[] = {
 		OPT_INTEGER(0, "creation-factor", &creation_factor,
 			    N_("Percentage by which creation is weighted")),
+		OPT_BOOL(0, "dual-color", &dual_color,
+			    N_("color both diff and diff-between-diffs")),
 		OPT_END()
 	};
 	int i, j, res = 0;
@@ -50,6 +53,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 	argc = j;
 	diff_setup_done(&diffopt);
 
+	if (dual_color) {
+		diffopt.use_color = 1;
+		diffopt.flags.dual_color_diffed_diffs = 1;
+	}
+
 	if (argc == 2) {
 		if (!strstr(argv[0], ".."))
 			warning(_("no .. in range: '%s'"), argv[0]);
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v3 16/20] range-diff --dual-color: work around bogus white-space warning
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
                       ` (12 preceding siblings ...)
  2018-05-03  1:01     ` [PATCH v3 15/20] range-diff: offer to dual-color the diffs Johannes Schindelin via GitGitGadget
@ 2018-05-03  1:11     ` Johannes Schindelin via GitGitGadget
  2018-07-09 19:34       ` Stefan Beller
  2018-05-03 13:50     ` [PATCH v3 17/20] range-diff: add a man page Johannes Schindelin via GitGitGadget
                       ` (6 subsequent siblings)
  20 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-05-03  1:11 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When displaying a diff of diffs, it is possible that there is an outer
`+` before a context line. That happens when the context changed between
old and new commit. When that context line starts with a tab (after the
space that marks it as context line), our diff machinery spits out a
white-space error (space before tab), but in this case, that is
incorrect.

Work around this by detecting that situation and simply *not* printing
the space in that case.

This is slightly improper a fix because it is conceivable that an
output_prefix might be configured with *just* the right length to let
that tab jump to a different tab stop depending whether we emit that
space or not.

However, the proper fix would be relatively ugly and intrusive because
it would have to *weaken* the WS_SPACE_BEFORE_TAB option in ws.c.
Besides, we do not expose the --dual-color option in cases other than
the `git range-diff` command, which only uses a hard-coded
output_prefix of four spaces (which misses the problem by one
column... ;-)).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 diff.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/diff.c b/diff.c
index 26445ffa1..325007167 100644
--- a/diff.c
+++ b/diff.c
@@ -1093,6 +1093,12 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 				set = diff_get_color_opt(o, DIFF_FRAGINFO);
 			else if (c != '+')
 				set = diff_get_color_opt(o, DIFF_CONTEXT);
+			/* Avoid space-before-tab warning */
+			if (c == ' ' && (len < 2 || line[1] == '\t' ||
+					 line[1] == '\r' || line[1] == '\n')) {
+				line++;
+				len--;
+			}
 		}
 		emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK,
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v3 17/20] range-diff: add a man page
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
                       ` (13 preceding siblings ...)
  2018-05-03  1:11     ` [PATCH v3 16/20] range-diff --dual-color: work around bogus white-space warning Johannes Schindelin via GitGitGadget
@ 2018-05-03 13:50     ` Johannes Schindelin via GitGitGadget
  2018-07-09 18:20       ` Stefan Beller
  2018-07-16  8:01       ` Eric Sunshine
  2018-05-03 14:44     ` [PATCH v3 18/20] completion: support `git range-diff` Johannes Schindelin via GitGitGadget
                       ` (5 subsequent siblings)
  20 siblings, 2 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-05-03 13:50 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

The bulk of this patch consists of a heavily butchered version of
tbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from
https://github.com/trast/tbdiff.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/git-range-diff.txt | 235 +++++++++++++++++++++++++++++++
 1 file changed, 235 insertions(+)
 create mode 100644 Documentation/git-range-diff.txt

diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
new file mode 100644
index 000000000..189236cc6
--- /dev/null
+++ b/Documentation/git-range-diff.txt
@@ -0,0 +1,235 @@
+git-range-diff(1)
+==================
+
+NAME
+----
+git-range-diff - Compare two commit ranges (e.g. two versions of a branch)
+
+SYNOPSIS
+--------
+[verse]
+'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]
+	[--dual-color] [--creation-factor=<factor>]
+	( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
+
+DESCRIPTION
+-----------
+
+This command shows the differences between two versions of a patch
+series, or more generally, two commit ranges (ignoring merges).
+
+To that end, it first finds pairs of commits from both commit ranges
+that correspond with each other. Two commits are said to correspond when
+the diff between their patches (i.e. the author information, the commit
+message and the commit diff) is reasonably small compared to the
+patches' size. See ``Algorithm` below for details.
+
+Finally, the list of matching commits is shown in the order of the
+second commit range, with unmatched commits being inserted just after
+all of their ancestors have been shown.
+
+
+OPTIONS
+-------
+--dual-color::
+	When the commit diffs differ, recreate the original diffs'
+	coloring, and add outer -/+ diff markers with the *background*
+	being red/green to make it easier to see e.g. when there was a
+	change in what exact lines were added.
+
+--creation-factor=<percent>::
+	Set the creation/deletion cost fudge factor to `<percent>`.
+	Defaults to 60. Try a larger value if `git range-diff` erroneously
+	considers a large change a total rewrite (deletion of one commit
+	and addition of another), and a smaller one in the reverse case.
+	See the ``Algorithm`` section below for an explanation why this is
+	needed.
+
+<range1> <range2>::
+	Compare the commits specified by the two ranges, where
+	`<range1>` is considered an older version of `<range2>`.
+
+<rev1>...<rev2>::
+	Equivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.
+
+<base> <rev1> <rev2>::
+	Equivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.
+	Note that `<base>` does not need to be the exact branch point
+	of the branches. Example: after rebasing a branch `my-topic`,
+	`git range-diff my-topic@{u} my-topic@{1} my-topic` would
+	show the differences introduced by the rebase.
+
+`git range-diff` also accepts the regular diff options (see
+linkgit:git-diff[1]), most notably the `--color=[<when>]` and
+`--no-color` options. These options are used when generating the "diff
+between patches", i.e. to compare the author, commit message and diff of
+corresponding old/new commits. There is currently no means to tweak the
+diff options passed to `git log` when generating those patches.
+
+
+CONFIGURATION
+-------------
+This command uses the `diff.color.*` and `pager.range-diff` settings
+(the latter is on by default).
+See linkgit:git-config[1].
+
+
+EXAMPLES
+--------
+
+When a rebase required merge conflicts to be resolved, compare the changes
+introduced by the rebase directly afterwards using:
+
+------------
+$ git range-diff @{u} @{1} @
+------------
+
+
+A typical output of `git range-diff` would look like this:
+
+------------
+-:  ------- > 1:  0ddba11 Prepare for the inevitable!
+1:  c0debee = 2:  cab005e Add a helpful message at the start
+2:  f00dbal ! 3:  decafe1 Describe a bug
+    @@ -1,3 +1,3 @@
+     Author: A U Thor <author@example.com>
+
+    -TODO: Describe a bug
+    +Describe a bug
+    @@ -324,5 +324,6
+      This is expected.
+
+    -+What is unexpected is that it will also crash.
+    ++Unexpectedly, it also crashes. This is a bug, and the jury is
+    ++still out there how to fix it best. See ticket #314 for details.
+
+      Contact
+3:  bedead < -:  ------- TO-UNDO
+------------
+
+In this example, there are 3 old and 3 new commits, where the developer
+removed the 3rd, added a new one before the first two, and modified the
+commit message of the 2nd commit as well its diff.
+
+When the output goes to a terminal, it is color-coded by default, just
+like regular `git diff`'s output. In addition, the first line (adding a
+commit) is green, the last line (deleting a commit) is red, the second
+line (with a perfect match) is yellow like the commit header of `git
+show`'s output, and the third line colors the old commit red, the new
+one green and the rest like `git show`'s commit header.
+
+The color-coded diff is actually a bit hard to read, though, as it
+colors the entire lines red or green. The line that added "What is
+unexpected" in the old commit, for example, is completely red, even if
+the intent of the old commit was to add something.
+
+To help with that, use the `--dual-color` mode. In this mode, the diff
+of diffs will retain the original diff colors, and prefix the lines with
+-/+ markers that have their *background* red or green, to make it more
+obvious that they describe how the diff itself changed.
+
+
+Algorithm
+---------
+
+The general idea is this: we generate a cost matrix between the commits
+in both commit ranges, then solve the least-cost assignment.
+
+To avoid false positives (e.g. when a patch has been removed, and an
+unrelated patch has been added between two iterations of the same patch
+series), the cost matrix is extended to allow for that, by adding
+fixed-cost entries for wholesale deletes/adds.
+
+Example: Let commits `1--2` be the first iteration of a patch series and
+`A--C` the second iteration. Let's assume that `A` is a cherry-pick of
+`2,` and `C` is a cherry-pick of `1` but with a small modification (say,
+a fixed typo). Visualize the commits as a bipartite graph:
+
+------------
+    1            A
+
+    2            B
+
+		 C
+------------
+
+We are looking for a "best" explanation of the new series in terms of
+the old one. We can represent an "explanation" as an edge in the graph:
+
+
+------------
+    1            A
+	       /
+    2 --------'  B
+
+		 C
+------------
+
+This explanation comes for "free" because there was no change. Similarly
+`C` could be explained using `1`, but that comes at some cost c>0
+because of the modification:
+
+------------
+    1 ----.      A
+	  |    /
+    2 ----+---'  B
+	  |
+	  `----- C
+	  c>0
+------------
+
+In mathematical terms, what we are looking for is some sort of a minimum
+cost bipartite matching; `1` is matched to `C` at some cost, etc. The
+underlying graph is in fact a complete bipartite graph; the cost we
+associate with every edge is the size of the diff between the two
+commits' patches. To explain also new commits, we introduce dummy nodes
+on both sides:
+
+------------
+    1 ----.      A
+	  |    /
+    2 ----+---'  B
+	  |
+    o     `----- C
+	  c>0
+    o            o
+
+    o            o
+------------
+
+The cost of an edge `o--C` is the size of `C`'s diff, modified by a
+fudge factor that should be smaller than 100%. The cost of an edge
+`o--o` is free. The fudge factor is necessary because even if `1` and
+`C` have nothing in common, they may still share a few empty lines and
+such, possibly making the assignment `1--C`, `o--o` slightly cheaper
+than `1--o`, `o--C` even if `1` and `C` have nothing in common. With the
+fudge factor we require a much larger common part to consider patches as
+corresponding.
+
+The overall time needed to compute this algorithm is the time needed to
+compute n+m commit diffs and then n*m diffs of patches, plus the time
+needed to compute the least-cost assigment between n and m diffs. Git
+uses an implementation of the Jonker-Volgenant algorithm to solve the
+assignment problem, which has cubic runtime complexity. The matching
+found in this case will look like this:
+
+------------
+    1 ----.      A
+	  |    /
+    2 ----+---'  B
+       .--+-----'
+    o -'  `----- C
+	  c>0
+    o ---------- o
+
+    o ---------- o
+------------
+
+
+SEE ALSO
+--------
+linkgit:git-log[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v3 18/20] completion: support `git range-diff`
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
                       ` (14 preceding siblings ...)
  2018-05-03 13:50     ` [PATCH v3 17/20] range-diff: add a man page Johannes Schindelin via GitGitGadget
@ 2018-05-03 14:44     ` Johannes Schindelin via GitGitGadget
  2018-07-06 22:46       ` Junio C Hamano
  2018-05-05 19:52     ` [PATCH v3 19/20] range-diff: left-pad patch numbers Johannes Schindelin via GitGitGadget
                       ` (4 subsequent siblings)
  20 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-05-03 14:44 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

Tab completion of `git range-diff` is very convenient, especially
given that the revision arguments to specify the commit ranges to
compare are typically more complex than, say, your grandfather's `git
log` arguments.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>

squash! WIP completion: support `git range-diff`

Revert "WIP completion: support `git range-diff`"

This reverts commit 2e7af652af9e53a19fd947f8ebe37a78043afa49.
---
 contrib/completion/git-completion.bash | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 94c95516e..402490673 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1976,6 +1976,20 @@ _git_push ()
 	__git_complete_remote_or_refspec
 }
 
+_git_range_diff ()
+{
+  case "$cur" in
+  --*)
+          __gitcomp "
+	  	--creation-factor= --dual-color
+                  $__git_diff_common_options
+                  "
+          return
+          ;;
+  esac
+  __git_complete_revlist
+}
+
 _git_rebase ()
 {
 	__git_find_repo_path
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike
@ 2018-05-03 15:30 Johannes Schindelin
  2018-05-03 15:30 ` [PATCH 01/18] Add a function to solve least-cost assignment problems Johannes Schindelin
                   ` (21 more replies)
  0 siblings, 22 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 15:30 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

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.

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.

While at it, I forward-ported AEvar's patch to force `--decorate=no` because
`git -p tbdiff` would fail otherwise.


Johannes Schindelin (17):
  Add a function to solve least-cost assignment problems
  Add a new builtin: branch-diff
  branch-diff: first rudimentary implementation
  branch-diff: improve the order of the shown commits
  branch-diff: also show the diff between patches
  branch-diff: right-trim commit messages
  branch-diff: indent the diffs just like tbdiff
  branch-diff: suppress the diff headers
  branch-diff: adjust the output of the commit pairs
  branch-diff: do not show "function names" in hunk headers
  branch-diff: use color for the commit pairs
  color: provide inverted colors, too
  diff: add an internal option to dual-color diffs of diffs
  branch-diff: offer to dual-color the diffs
  branch-diff --dual-color: work around bogus white-space warning
  branch-diff: add a man page
  completion: support branch-diff

Thomas Rast (1):
  branch-diff: add tests

 .gitignore                             |   1 +
 Documentation/git-branch-diff.txt      | 239 ++++++++++
 Makefile                               |   2 +
 builtin.h                              |   1 +
 builtin/branch-diff.c                  | 531 ++++++++++++++++++++++
 color.h                                |   6 +
 command-list.txt                       |   1 +
 contrib/completion/git-completion.bash |  18 +
 diff.c                                 |  76 +++-
 diff.h                                 |   6 +-
 git.c                                  |   1 +
 hungarian.c                            | 205 +++++++++
 hungarian.h                            |  19 +
 t/.gitattributes                       |   1 +
 t/t7910-branch-diff.sh                 | 144 ++++++
 t/t7910/history.export                 | 604 +++++++++++++++++++++++++
 16 files changed, 1843 insertions(+), 12 deletions(-)
 create mode 100644 Documentation/git-branch-diff.txt
 create mode 100644 builtin/branch-diff.c
 create mode 100644 hungarian.c
 create mode 100644 hungarian.h
 create mode 100755 t/t7910-branch-diff.sh
 create mode 100644 t/t7910/history.export


base-commit: 1f1cddd558b54bb0ce19c8ace353fd07b758510d
Published-As: https://github.com/dscho/git/releases/tag/branch-diff-v1
Fetch-It-Via: git fetch https://github.com/dscho/git branch-diff-v1
-- 
2.17.0.395.g6a618d6010f.dirty


^ permalink raw reply	[flat|nested] 387+ messages in thread

* [PATCH 01/18] Add a function to solve least-cost assignment problems
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
@ 2018-05-03 15:30 ` Johannes Schindelin
  2018-05-13 18:19   ` Duy Nguyen
  2018-05-03 15:30 ` [PATCH 02/18] Add a new builtin: branch-diff Johannes Schindelin
                   ` (20 subsequent siblings)
  21 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 15:30 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

The Jonker-Volgenant algorithm was implemented to answer questions such
as: given two different versions of a topic branch (or iterations of a
patch series), what is the best pairing of commits/patches between the
different versions?

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Makefile    |   1 +
 hungarian.c | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 hungarian.h |  19 +++++
 3 files changed, 225 insertions(+)
 create mode 100644 hungarian.c
 create mode 100644 hungarian.h

diff --git a/Makefile b/Makefile
index 50da82b0169..96f2e76a904 100644
--- a/Makefile
+++ b/Makefile
@@ -829,6 +829,7 @@ LIB_OBJS += gpg-interface.o
 LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hashmap.o
+LIB_OBJS += hungarian.o
 LIB_OBJS += help.o
 LIB_OBJS += hex.o
 LIB_OBJS += ident.o
diff --git a/hungarian.c b/hungarian.c
new file mode 100644
index 00000000000..346299a97d9
--- /dev/null
+++ b/hungarian.c
@@ -0,0 +1,205 @@
+/*
+ * Based on: Jonker, R., & Volgenant, A. (1987). <i>A shortest augmenting path
+ * algorithm for dense and sparse linear assignment problems</i>. Computing,
+ * 38(4), 325-340.
+ */
+#include "cache.h"
+#include "hungarian.h"
+#include <float.h>
+
+#define COST(column, row) cost[(column) + column_count * (row)]
+
+/*
+ * The parameter `cost` is the cost matrix: the cost to assign column j to row
+ * i is `cost[j + column_count * i].
+ */
+int compute_assignment(int column_count, int row_count, double *cost,
+		       int *column2row, int *row2column)
+{
+	double *v = xmalloc(sizeof(double) * column_count), *d;
+	int *free_row, free_count = 0, saved_free_count, *pred, *col;
+	int i, j, phase;
+
+	memset(column2row, -1, sizeof(int) * column_count);
+	memset(row2column, -1, sizeof(int) * row_count);
+
+	/* column reduction */
+	for (j = column_count - 1; j >= 0; j--) {
+		int i1 = 0;
+
+		for (i = 1; i < row_count; i++)
+			if (COST(j, i1) > COST(j, i))
+				i1 = i;
+		v[j] = COST(j, i1);
+		if (row2column[i1] == -1) {
+			/* row i1 unassigned */
+			row2column[i1] = j;
+			column2row[j] = i1;
+		} else {
+			if (row2column[i1] >= 0)
+				row2column[i1] = -2 - row2column[i1];
+			column2row[j] = -1;
+		}
+	}
+
+	/* reduction transfer */
+	free_row = xmalloc(sizeof(int) * row_count);
+	for (int i = 0; i < row_count; i++) {
+		int j1 = row2column[i];
+		if (j1 == -1)
+			free_row[free_count++] = i;
+		else if (j1 < -1)
+			row2column[i] = -2 - j1;
+		else {
+			double min = COST(!j1, i) - v[!j1];
+			for (j = 1; j < column_count; j++)
+				if (j != j1 && min > COST(j, i) - v[j])
+					min = COST(j, i) - v[j];
+			v[j1] -= min;
+		}
+	}
+
+	if (free_count ==
+	    (column_count < row_count ? row_count - column_count : 0)) {
+		free(v);
+		free(free_row);
+		return 0;
+	}
+
+	/* augmenting row reduction */
+	for (phase = 0; phase < 2; phase++) {
+		int k = 0;
+
+		saved_free_count = free_count;
+		free_count = 0;
+		while (k < saved_free_count) {
+			double u1, u2;
+			int j1 = 0, j2, i0;
+
+			i = free_row[k++];
+			u1 = COST(j1, i) - v[j1];
+			j2 = -1;
+			u2 = DBL_MAX;
+			for (j = 1; j < column_count; j++) {
+				double c = COST(j, i) - v[j];
+				if (u2 > c) {
+					if (u1 < c) {
+						u2 = c;
+						j2 = j;
+					} else {
+						u2 = u1;
+						u1 = c;
+						j2 = j1;
+						j1 = j;
+					}
+				}
+			}
+			if (j2 < 0) {
+				j2 = j1;
+				u2 = u1;
+			}
+
+			i0 = column2row[j1];
+			if (u1 < u2)
+				v[j1] -= u2 - u1;
+			else if (i0 >= 0) {
+				j1 = j2;
+				i0 = column2row[j1];
+			}
+
+			if (i0 >= 0) {
+				if (u1 < u2)
+					free_row[--k] = i0;
+				else
+					free_row[free_count++] = i0;
+			}
+			row2column[i] = j1;
+			column2row[j1] = i;
+		}
+	}
+
+	/* augmentation */
+	saved_free_count = free_count;
+	d = xmalloc(sizeof(double) * column_count);
+	pred = xmalloc(sizeof(int) * column_count);
+	col = xmalloc(sizeof(int) * column_count);
+	for (free_count = 0; free_count < saved_free_count; free_count++) {
+		int i1 = free_row[free_count], low = 0, up = 0, last, k;
+		double min, c, u1;
+
+		for (j = 0; j < column_count; j++) {
+			d[j] = COST(j, i1) - v[j];
+			pred[j] = i1;
+			col[j] = j;
+		}
+
+		j = -1;
+		do {
+			last = low;
+			min = d[col[up++]];
+			for (k = up; k < column_count; k++) {
+				j = col[k];
+				c = d[j];
+				if (c <= min) {
+					if (c < min) {
+						up = low;
+						min = c;
+					}
+					col[k] = col[up];
+					col[up++] = j;
+				}
+			}
+			for (k = low; k < up; k++)
+				if (column2row[col[k]] == -1)
+					goto update;
+
+			/* scan a row */
+			do {
+				int j1 = col[low++];
+
+				i = column2row[j1];
+				u1 = COST(j1, i) - v[j1] - min;
+				for (k = up; k < column_count; k++) {
+					j = col[k];
+					c = COST(j, i) - v[j] - u1;
+					if (c < d[j]) {
+						d[j] = c;
+						pred[j] = i;
+						if (c == min) {
+							if (column2row[j] == -1)
+								goto update;
+							col[k] = col[up];
+							col[up++] = j;
+						}
+					}
+				}
+			} while (low != up);
+		} while (low == up);
+
+update:
+		/* updating of the column pieces */
+		for (k = 0; k < last; k++) {
+			int j1 = col[k];
+			v[j1] += d[j1] - min;
+		}
+
+		/* augmentation */
+		do {
+			if (j < 0)
+				BUG("negative j: %d", j);
+			i = pred[j];
+			column2row[j] = i;
+			k = j;
+			j = row2column[i];
+			row2column[i] = k;
+		} while (i1 != i);
+	}
+
+	free(col);
+	free(pred);
+	free(d);
+	free(v);
+	free(free_row);
+
+	return 0;
+}
diff --git a/hungarian.h b/hungarian.h
new file mode 100644
index 00000000000..e7cee4bb039
--- /dev/null
+++ b/hungarian.h
@@ -0,0 +1,19 @@
+#ifndef HUNGARIAN_H
+#define HUNGARIAN_H
+
+/*
+ * Compute an assignment of columns -> rows (and vice versa) such that every
+ * column is assigned to at most one row (and vice versa) minimizing the
+ * overall cost.
+ *
+ * The parameter `cost` is the cost matrix: the cost to assign column j to row
+ * i is `cost[j + column_count * i].
+ *
+ * The arrays column2row and row2column will be populated with the respective
+ * assignments (-1 for unassigned, which can happen only if column_count !=
+ * row_count).
+ */
+int compute_assignment(int column_count, int row_count, double *cost,
+		       int *column2row, int *row2column);
+
+#endif
-- 
2.17.0.395.g6a618d6010f.dirty



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
  2018-05-03 15:30 ` [PATCH 01/18] Add a function to solve least-cost assignment problems Johannes Schindelin
@ 2018-05-03 15:30 ` Johannes Schindelin
  2018-05-03 16:10   ` Ramsay Jones
                     ` (3 more replies)
  2018-05-03 15:30 ` [PATCH 03/18] branch-diff: first rudimentary implementation Johannes Schindelin
                   ` (19 subsequent siblings)
  21 siblings, 4 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 15:30 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

This builtin does not do a whole lot so far, apart from showing a usage
that is oddly similar to that of `git tbdiff`. And for a good reason:
the next commits will turn `branch-diff` into a full-blown replacement
for `tbdiff`.

At this point, we ignore tbdiff's color options, as they will all be
implemented later and require some patches to the diff machinery.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitignore            |  1 +
 Makefile              |  1 +
 builtin.h             |  1 +
 builtin/branch-diff.c | 40 ++++++++++++++++++++++++++++++++++++++++
 command-list.txt      |  1 +
 git.c                 |  1 +
 6 files changed, 45 insertions(+)
 create mode 100644 builtin/branch-diff.c

diff --git a/.gitignore b/.gitignore
index 833ef3b0b78..1346a64492f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,7 @@
 /git-bisect--helper
 /git-blame
 /git-branch
+/git-branch-diff
 /git-bundle
 /git-cat-file
 /git-check-attr
diff --git a/Makefile b/Makefile
index 96f2e76a904..9b1984776d8 100644
--- a/Makefile
+++ b/Makefile
@@ -953,6 +953,7 @@ BUILTIN_OBJS += builtin/archive.o
 BUILTIN_OBJS += builtin/bisect--helper.o
 BUILTIN_OBJS += builtin/blame.o
 BUILTIN_OBJS += builtin/branch.o
+BUILTIN_OBJS += builtin/branch-diff.o
 BUILTIN_OBJS += builtin/bundle.o
 BUILTIN_OBJS += builtin/cat-file.o
 BUILTIN_OBJS += builtin/check-attr.o
diff --git a/builtin.h b/builtin.h
index 42378f3aa47..e1c4d2a529a 100644
--- a/builtin.h
+++ b/builtin.h
@@ -135,6 +135,7 @@ extern int cmd_archive(int argc, const char **argv, const char *prefix);
 extern int cmd_bisect__helper(int argc, const char **argv, const char *prefix);
 extern int cmd_blame(int argc, const char **argv, const char *prefix);
 extern int cmd_branch(int argc, const char **argv, const char *prefix);
+extern int cmd_branch_diff(int argc, const char **argv, const char *prefix);
 extern int cmd_bundle(int argc, const char **argv, const char *prefix);
 extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
 extern int cmd_checkout(int argc, const char **argv, const char *prefix);
diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
new file mode 100644
index 00000000000..97266cd326d
--- /dev/null
+++ b/builtin/branch-diff.c
@@ -0,0 +1,40 @@
+#include "cache.h"
+#include "parse-options.h"
+
+static const char * const builtin_branch_diff_usage[] = {
+	N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),
+	NULL
+};
+
+#define COLOR_DUAL_MODE 2
+
+static int parse_creation_weight(const struct option *opt, const char *arg,
+				 int unset)
+{
+	double *d = opt->value;
+	if (unset)
+		*d = 0.6;
+	else
+		*d = atof(arg);
+	return 0;
+}
+
+int cmd_branch_diff(int argc, const char **argv, const char *prefix)
+{
+	int no_patches = 0;
+	double creation_weight = 0.6;
+	struct option options[] = {
+		OPT_BOOL(0, "no-patches", &no_patches,
+			 N_("short format (no diffs)")),
+		{ OPTION_CALLBACK,
+			0, "creation-weight", &creation_weight, N_("factor"),
+			N_("Fudge factor by which creation is weighted [0.6]"),
+			0, parse_creation_weight },
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, NULL, options,
+			builtin_branch_diff_usage, 0);
+
+	return 0;
+}
diff --git a/command-list.txt b/command-list.txt
index a1fad28fd82..c89ac8f417f 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -19,6 +19,7 @@ git-archive                             mainporcelain
 git-bisect                              mainporcelain           info
 git-blame                               ancillaryinterrogators
 git-branch                              mainporcelain           history
+git-branch-diff                         mainporcelain           info
 git-bundle                              mainporcelain
 git-cat-file                            plumbinginterrogators
 git-check-attr                          purehelpers
diff --git a/git.c b/git.c
index f598fae7b7a..d2794fb6f5d 100644
--- a/git.c
+++ b/git.c
@@ -377,6 +377,7 @@ static struct cmd_struct commands[] = {
 	{ "bisect--helper", cmd_bisect__helper, RUN_SETUP },
 	{ "blame", cmd_blame, RUN_SETUP },
 	{ "branch", cmd_branch, RUN_SETUP | DELAY_PAGER_CONFIG },
+	{ "branch-diff", cmd_branch_diff, RUN_SETUP | USE_PAGER },
 	{ "bundle", cmd_bundle, RUN_SETUP_GENTLY | NO_PARSEOPT },
 	{ "cat-file", cmd_cat_file, RUN_SETUP },
 	{ "check-attr", cmd_check_attr, RUN_SETUP },
-- 
2.17.0.395.g6a618d6010f.dirty



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 03/18] branch-diff: first rudimentary implementation
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
  2018-05-03 15:30 ` [PATCH 01/18] Add a function to solve least-cost assignment problems Johannes Schindelin
  2018-05-03 15:30 ` [PATCH 02/18] Add a new builtin: branch-diff Johannes Schindelin
@ 2018-05-03 15:30 ` Johannes Schindelin
  2018-05-03 16:30   ` Ramsay Jones
                     ` (3 more replies)
  2018-05-03 15:30 ` [PATCH 04/18] branch-diff: improve the order of the shown commits Johannes Schindelin
                   ` (18 subsequent siblings)
  21 siblings, 4 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 15:30 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

At this stage, `git branch-diff` can determine corresponding commits of
two related commit ranges. This makes use of the recently introduced
implementation of the Hungarian algorithm.

The core of this patch is a straight port of the ideas of tbdiff, the
seemingly dormant project at https://github.com/trast/tbdiff.

The output does not at all match `tbdiff`'s output yet, as this patch
really concentrates on getting the patch matching part right.

Note: due to differences in the diff algorithm (`tbdiff` uses the
Pythong module `difflib`, Git uses its xdiff fork), the cost matrix
calculated by `branch-diff` is different (but very similar) to the one
calculated by `tbdiff`. Therefore, it is possible that they find
different matching commits in corner cases (e.g. when a patch was split
into two patches of roughly equal length).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 337 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 334 insertions(+), 3 deletions(-)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index 97266cd326d..02dc06a57ca 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -1,13 +1,17 @@
 #include "cache.h"
 #include "parse-options.h"
+#include "string-list.h"
+#include "run-command.h"
+#include "argv-array.h"
+#include "hashmap.h"
+#include "xdiff-interface.h"
+#include "hungarian.h"
 
 static const char * const builtin_branch_diff_usage[] = {
 	N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),
 	NULL
 };
 
-#define COLOR_DUAL_MODE 2
-
 static int parse_creation_weight(const struct option *opt, const char *arg,
 				 int unset)
 {
@@ -19,6 +23,279 @@ static int parse_creation_weight(const struct option *opt, const char *arg,
 	return 0;
 }
 
+struct patch_util {
+	/* For the search for an exact match */
+	struct hashmap_entry e;
+	const char *diff, *patch;
+
+	int i;
+	int diffsize;
+	size_t diff_offset;
+	/* the index of the matching item in the other branch, or -1 */
+	int matching;
+	struct object_id oid;
+};
+
+/*
+ * Reads the patches into a string list, with the `util` field being populated
+ * as struct object_id (will need to be free()d).
+ */
+static int read_patches(const char *range, struct string_list *list)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+	FILE *in;
+	struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;
+	struct patch_util *util = NULL;
+	int in_header = 1;
+
+	argv_array_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
+			"--reverse", "--date-order", "--decorate=no",
+			"--no-abbrev-commit", range,
+			NULL);
+	cp.out = -1;
+	cp.no_stdin = 1;
+	cp.git_cmd = 1;
+
+	if (start_command(&cp))
+		return error_errno(_("could not start `log`"));
+	in = fdopen(cp.out, "r");
+	if (!in) {
+		error_errno(_("could not read `log` output"));
+		finish_command(&cp);
+		return -1;
+	}
+
+	while (strbuf_getline(&line, in) != EOF) {
+		const char *p;
+
+		if (skip_prefix(line.buf, "commit ", &p)) {
+			if (util) {
+				string_list_append(list, buf.buf)->util = util;
+				strbuf_reset(&buf);
+			}
+			util = xcalloc(sizeof(*util), 1);
+			if (get_oid(p, &util->oid)) {
+				error(_("could not parse commit '%s'"), p);
+				free(util);
+				string_list_clear(list, 1);
+				strbuf_release(&buf);
+				strbuf_release(&line);
+				fclose(in);
+				finish_command(&cp);
+				return -1;
+			}
+			util->matching = -1;
+			in_header = 1;
+			continue;
+		}
+
+		if (starts_with(line.buf, "diff --git")) {
+			in_header = 0;
+			strbuf_addch(&buf, '\n');
+			if (!util->diff_offset)
+				util->diff_offset = buf.len;
+			strbuf_addbuf(&buf, &line);
+		} else if (in_header) {
+			if (starts_with(line.buf, "Author: ")) {
+				strbuf_addbuf(&buf, &line);
+				strbuf_addstr(&buf, "\n\n");
+			} else if (starts_with(line.buf, "    ")) {
+				strbuf_addbuf(&buf, &line);
+				strbuf_addch(&buf, '\n');
+			}
+
+			continue;
+		} else if (starts_with(line.buf, "@@ "))
+			strbuf_addstr(&buf, "@@");
+		else if (line.buf[0] && !starts_with(line.buf, "index "))
+			/*
+			 * A completely blank (not ' \n', which is context)
+			 * line is not valid in a diff.  We skip it
+			 * silently, because this neatly handles the blank
+			 * separator line between commits in git-log
+			 * output.
+			 */
+			strbuf_addbuf(&buf, &line);
+		else
+			continue;
+
+		strbuf_addch(&buf, '\n');
+		util->diffsize++;
+	}
+	fclose(in);
+
+	if (util)
+		string_list_append(list, buf.buf)->util = util;
+	strbuf_release(&buf);
+
+	if (finish_command(&cp))
+		return -1;
+
+	return 0;
+}
+
+static int patch_util_cmp(const void *dummy, const struct patch_util *a,
+		     const struct patch_util *b, const char *keydata)
+{
+	return strcmp(a->diff, keydata ? keydata : b->diff);
+}
+
+static void find_exact_matches(struct string_list *a, struct string_list *b)
+{
+	struct hashmap map;
+	int i;
+
+	hashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);
+
+	/* First, add the patches of a to a hash map */
+	for (i = 0; i < a->nr; i++) {
+		struct patch_util *util = a->items[i].util;
+
+		util->i = i;
+		util->patch = a->items[i].string;
+		util->diff = util->patch + util->diff_offset;
+		hashmap_entry_init(util, strhash(util->diff));
+		hashmap_add(&map, util);
+	}
+
+	/* Now try to find exact matches in b */
+	for (i = 0; i < b->nr; i++) {
+		struct patch_util *util = b->items[i].util, *other;
+
+		util->i = i;
+		util->patch = b->items[i].string;
+		util->diff = util->patch + util->diff_offset;
+		hashmap_entry_init(util, strhash(util->diff));
+		other = hashmap_remove(&map, util, NULL);
+		if (other) {
+			if (other->matching >= 0)
+				BUG("already assigned!");
+
+			other->matching = i;
+			util->matching = other->i;
+		}
+	}
+
+	hashmap_free(&map, 0);
+}
+
+static void diffsize_consume(void *data, char *line, unsigned long len)
+{
+	(*(int *)data)++;
+}
+
+static int diffsize(const char *a, const char *b)
+{
+	xpparam_t pp = { 0 };
+	xdemitconf_t cfg = { 0 };
+	mmfile_t mf1, mf2;
+	int count = 0;
+
+	mf1.ptr = (char *)a;
+	mf1.size = strlen(a);
+	mf2.ptr = (char *)b;
+	mf2.size = strlen(b);
+
+	cfg.ctxlen = 3;
+	if (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))
+		return count;
+
+	error(_("failed to generate diff"));
+	return INT_MAX;
+}
+
+static int get_correspondences(struct string_list *a, struct string_list *b,
+			       double creation_weight)
+{
+	int n = a->nr + b->nr;
+	double *cost = xmalloc(sizeof(double) * n * n), c;
+	int *a2b = xmalloc(sizeof(int) * n), *b2a = xmalloc(sizeof(int) * n);
+	int i, j, res;
+
+	for (i = 0; i < a->nr; i++) {
+		struct patch_util *a_util = a->items[i].util;
+
+		for (j = 0; j < b->nr; j++) {
+			struct patch_util *b_util = b->items[j].util;
+
+			if (a_util->matching == j)
+				c = 0;
+			else if (a_util->matching < 0 && b_util->matching < 0)
+				c = diffsize(a_util->diff, b_util->diff);
+			else
+				c = INT_MAX;
+			cost[i + n * j] = c;
+		}
+
+		c = a_util->matching < 0 ?
+			a_util->diffsize * creation_weight : INT_MAX;
+		for (j = b->nr; j < n; j++)
+			cost[i + n * j] = c;
+	}
+
+	for (j = 0; j < b->nr; j++) {
+		struct patch_util *util = b->items[j].util;
+
+		c = util->matching < 0 ?
+			util->diffsize * creation_weight : INT_MAX;
+		for (i = a->nr; i < n; i++)
+			cost[i + n * j] = c;
+	}
+
+	for (i = a->nr; i < n; i++)
+		for (j = b->nr; j < n; j++)
+			cost[i + n * j] = 0;
+
+	res = compute_assignment(n, n, cost, a2b, b2a);
+
+	for (i = 0; i < a->nr; i++)
+		if (a2b[i] >= 0 && a2b[i] < b->nr) {
+			struct patch_util *a_util = a->items[i].util;
+			struct patch_util *b_util = b->items[a2b[i]].util;
+
+			a_util->matching = a2b[i];
+			b_util->matching = i;
+		}
+
+	free(cost);
+	free(a2b);
+	free(b2a);
+
+	return res;
+}
+
+static const char *short_oid(struct patch_util *util)
+{
+	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
+}
+
+static void output(struct string_list *a, struct string_list *b)
+{
+	int i;
+
+	for (i = 0; i < b->nr; i++) {
+		struct patch_util *util = b->items[i].util, *prev;
+
+		if (util->matching < 0)
+			printf("-: -------- > %d: %s\n",
+					i + 1, short_oid(util));
+		else {
+			prev = a->items[util->matching].util;
+			printf("%d: %s ! %d: %s\n",
+			       util->matching + 1, short_oid(prev),
+			       i + 1, short_oid(util));
+		}
+	}
+
+	for (i = 0; i < a->nr; i++) {
+		struct patch_util *util = a->items[i].util;
+
+		if (util->matching < 0)
+			printf("%d: %s < -: --------\n",
+			       i + 1, short_oid(util));
+	}
+}
+
 int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 {
 	int no_patches = 0;
@@ -32,9 +309,63 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 			0, parse_creation_weight },
 		OPT_END()
 	};
+	int res = 0;
+	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
+	struct string_list branch1 = STRING_LIST_INIT_DUP;
+	struct string_list branch2 = STRING_LIST_INIT_DUP;
 
 	argc = parse_options(argc, argv, NULL, options,
 			builtin_branch_diff_usage, 0);
 
-	return 0;
+	if (argc == 2) {
+		if (!strstr(argv[0], ".."))
+			warning(_("no .. in range: '%s'"), argv[0]);
+		strbuf_addstr(&range1, argv[0]);
+
+		if (!strstr(argv[1], ".."))
+			warning(_("no .. in range: '%s'"), argv[1]);
+		strbuf_addstr(&range2, argv[1]);
+	} else if (argc == 3) {
+		strbuf_addf(&range1, "%s..%s", argv[0], argv[1]);
+		strbuf_addf(&range2, "%s..%s", argv[0], argv[2]);
+	} else if (argc == 1) {
+		const char *b = strstr(argv[0], "..."), *a = argv[0];
+		int a_len;
+
+		if (!b)
+			die(_("single arg format requires a symmetric range"));
+
+		a_len = (int)(b - a);
+		if (!a_len) {
+			a = "HEAD";
+			a_len = strlen(a);
+		}
+		b += 3;
+		if (!*b)
+			b = "HEAD";
+		strbuf_addf(&range1, "%s..%.*s", b, a_len, a);
+		strbuf_addf(&range2, "%.*s..%s", a_len, a, b);
+	} else {
+		error("Need two commit ranges");
+		usage_with_options(builtin_branch_diff_usage, options);
+	}
+
+	if (read_patches(range1.buf, &branch1))
+		res = error(_("could not parse log for '%s'"), range1.buf);
+	if (!res && read_patches(range2.buf, &branch2))
+		res = error(_("could not parse log for '%s'"), range2.buf);
+
+	if (!res) {
+		find_exact_matches(&branch1, &branch2);
+		res = get_correspondences(&branch1, &branch2, creation_weight);
+		if (!res)
+			output(&branch1, &branch2);
+	}
+
+	strbuf_release(&range1);
+	strbuf_release(&range2);
+	string_list_clear(&branch1, 1);
+	string_list_clear(&branch2, 1);
+
+	return !!res;
 }
-- 
2.17.0.395.g6a618d6010f.dirty



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 04/18] branch-diff: improve the order of the shown commits
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
                   ` (2 preceding siblings ...)
  2018-05-03 15:30 ` [PATCH 03/18] branch-diff: first rudimentary implementation Johannes Schindelin
@ 2018-05-03 15:30 ` Johannes Schindelin
  2018-05-03 15:30 ` [PATCH 05/18] branch-diff: also show the diff between patches Johannes Schindelin
                   ` (17 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 15:30 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

This patch lets branch-diff use the same order as tbdiff.

The idea is simple: for left-to-right readers, it is natural to assume
that the branch-diff is performed between an older vs a newer version of
the branch. As such, the user is probably more interested in the
question "where did this come from?" rather than "where did that one
go?".

To that end, we list the commits in the order of the second commit range
("the newer version"), inserting the unmatched commits of the first
commit range as soon as all their predecessors have been shown.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 59 +++++++++++++++++++++++++++++--------------
 1 file changed, 40 insertions(+), 19 deletions(-)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index 02dc06a57ca..59423498194 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -28,7 +28,7 @@ struct patch_util {
 	struct hashmap_entry e;
 	const char *diff, *patch;
 
-	int i;
+	int i, shown;
 	int diffsize;
 	size_t diff_offset;
 	/* the index of the matching item in the other branch, or -1 */
@@ -271,28 +271,49 @@ static const char *short_oid(struct patch_util *util)
 
 static void output(struct string_list *a, struct string_list *b)
 {
-	int i;
-
-	for (i = 0; i < b->nr; i++) {
-		struct patch_util *util = b->items[i].util, *prev;
+	int i = 0, j = 0;
+
+	/*
+	 * We assume the user is really more interested in the second argument
+	 * ("newer" version). To that end, we print the output in the order of
+	 * the RHS (the `b` parameter). To put the LHS (the `a` parameter)
+	 * commits that are no longer in the RHS into a good place, we place
+	 * them once we have shown all of their predecessors in the LHS.
+	 */
+
+	while (i < a->nr || j < b->nr) {
+		struct patch_util *a_util, *b_util;
+		a_util = i < a->nr ? a->items[i].util : NULL;
+		b_util = j < b->nr ? b->items[j].util : NULL;
+
+		/* Skip all the already-shown commits from the LHS. */
+		while (i < a->nr && a_util->shown)
+			a_util = ++i < a->nr ? a->items[i].util : NULL;
+
+		/* Show unmatched LHS commit whose predecessors were shown. */
+		if (i < a->nr && a_util->matching < 0) {
+			printf("%d: %s < -: --------\n",
+			       i + 1, short_oid(a_util));
+			i++;
+			continue;
+		}
 
-		if (util->matching < 0)
+		/* Show unmatched RHS commits. */
+		while (j < b->nr && b_util->matching < 0) {
 			printf("-: -------- > %d: %s\n",
-					i + 1, short_oid(util));
-		else {
-			prev = a->items[util->matching].util;
-			printf("%d: %s ! %d: %s\n",
-			       util->matching + 1, short_oid(prev),
-			       i + 1, short_oid(util));
+			       j + 1, short_oid(b_util));
+			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
-	}
-
-	for (i = 0; i < a->nr; i++) {
-		struct patch_util *util = a->items[i].util;
 
-		if (util->matching < 0)
-			printf("%d: %s < -: --------\n",
-			       i + 1, short_oid(util));
+		/* Show matching LHS/RHS pair. */
+		if (j < b->nr) {
+			a_util = a->items[b_util->matching].util;
+			printf("%d: %s ! %d: %s\n",
+			       b_util->matching + 1, short_oid(a_util),
+			       j + 1, short_oid(b_util));
+			a_util->shown = 1;
+			j++;
+		}
 	}
 }
 
-- 
2.17.0.395.g6a618d6010f.dirty



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 05/18] branch-diff: also show the diff between patches
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
                   ` (3 preceding siblings ...)
  2018-05-03 15:30 ` [PATCH 04/18] branch-diff: improve the order of the shown commits Johannes Schindelin
@ 2018-05-03 15:30 ` Johannes Schindelin
  2018-05-04  2:51   ` Eric Sunshine
  2018-05-03 15:30 ` [PATCH 06/18] branch-diff: right-trim commit messages Johannes Schindelin
                   ` (16 subsequent siblings)
  21 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 15:30 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

Just like tbdiff, we now show the diff between matching patches. This is
a "diff of two diffs", so it can be a bit daunting to read for the
beginnger.

This brings branch-diff closer to be feature-complete with regard to
tbdiff.

An alternative would be to display an interdiff, i.e. the hypothetical
diff which is the result of first reverting the old diff and then
applying the new diff.

Especially when rebasing often, an interdiff is often not feasible,
though: if the old diff cannot be applied in reverse (due to a moving
upstream), an interdiff can simply not be inferred.

Note: while we now parse diff options such as --color, the effect is not
yet the same as in tbdiff, where also the commit pairs would be colored.
This is left for a later commit.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 56 +++++++++++++++++++++++++++++++++++++------
 1 file changed, 49 insertions(+), 7 deletions(-)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index 59423498194..3b565a37492 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -6,6 +6,8 @@
 #include "hashmap.h"
 #include "xdiff-interface.h"
 #include "hungarian.h"
+#include "diff.h"
+#include "diffcore.h"
 
 static const char * const builtin_branch_diff_usage[] = {
 	N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),
@@ -269,7 +271,31 @@ static const char *short_oid(struct patch_util *util)
 	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
 }
 
-static void output(struct string_list *a, struct string_list *b)
+static struct diff_filespec *get_filespec(const char *name, const char *p)
+{
+	struct diff_filespec *spec = alloc_filespec(name);
+
+	fill_filespec(spec, &null_oid, 0, 0644);
+	spec->data = (char *)p;
+	spec->size = strlen(p);
+	spec->should_munmap = 0;
+	spec->is_stdin = 1;
+
+	return spec;
+}
+
+static void patch_diff(const char *a, const char *b,
+			      struct diff_options *diffopt)
+{
+	diff_queue(&diff_queued_diff,
+		   get_filespec("a", a), get_filespec("b", b));
+
+	diffcore_std(diffopt);
+	diff_flush(diffopt);
+}
+
+static void output(struct string_list *a, struct string_list *b,
+		   struct diff_options *diffopt)
 {
 	int i = 0, j = 0;
 
@@ -311,6 +337,9 @@ static void output(struct string_list *a, struct string_list *b)
 			printf("%d: %s ! %d: %s\n",
 			       b_util->matching + 1, short_oid(a_util),
 			       j + 1, short_oid(b_util));
+			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
+				patch_diff(a->items[b_util->matching].string,
+					   b->items[j].string, diffopt);
 			a_util->shown = 1;
 			j++;
 		}
@@ -319,24 +348,37 @@ static void output(struct string_list *a, struct string_list *b)
 
 int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 {
-	int no_patches = 0;
+	struct diff_options diffopt = { 0 };
 	double creation_weight = 0.6;
 	struct option options[] = {
-		OPT_BOOL(0, "no-patches", &no_patches,
-			 N_("short format (no diffs)")),
+		OPT_SET_INT(0, "no-patches", &diffopt.output_format,
+			    N_("short format (no diffs)"),
+			    DIFF_FORMAT_NO_OUTPUT),
 		{ OPTION_CALLBACK,
 			0, "creation-weight", &creation_weight, N_("factor"),
 			N_("Fudge factor by which creation is weighted [0.6]"),
 			0, parse_creation_weight },
 		OPT_END()
 	};
-	int res = 0;
+	int i, j, res = 0;
 	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
 	struct string_list branch1 = STRING_LIST_INIT_DUP;
 	struct string_list branch2 = STRING_LIST_INIT_DUP;
 
+	diff_setup(&diffopt);
+	diffopt.output_format = DIFF_FORMAT_PATCH;
+
 	argc = parse_options(argc, argv, NULL, options,
-			builtin_branch_diff_usage, 0);
+			builtin_branch_diff_usage, PARSE_OPT_KEEP_UNKNOWN);
+
+	for (i = j = 0; i < argc; i++) {
+		int c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);
+
+		if (!c)
+			argv[j++] = argv[i];
+	}
+	argc = j;
+	diff_setup_done(&diffopt);
 
 	if (argc == 2) {
 		if (!strstr(argv[0], ".."))
@@ -380,7 +422,7 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 		find_exact_matches(&branch1, &branch2);
 		res = get_correspondences(&branch1, &branch2, creation_weight);
 		if (!res)
-			output(&branch1, &branch2);
+			output(&branch1, &branch2, &diffopt);
 	}
 
 	strbuf_release(&range1);
-- 
2.17.0.395.g6a618d6010f.dirty



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 06/18] branch-diff: right-trim commit messages
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
                   ` (4 preceding siblings ...)
  2018-05-03 15:30 ` [PATCH 05/18] branch-diff: also show the diff between patches Johannes Schindelin
@ 2018-05-03 15:30 ` Johannes Schindelin
  2018-05-03 15:30 ` [PATCH 07/18] branch-diff: indent the diffs just like tbdiff Johannes Schindelin
                   ` (15 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 15:30 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

When comparing commit messages, we need to keep in mind that they are
indented by four spaces. That is, empty lines are no longer empty, but
have "trailing whitespace". When displaying them in color, that results
in those nagging red lines.

Let's just right-trim the lines in the commit message, it's not like
trailing white-space in the commit messages are important enough to care
about in branch-diff.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index 3b565a37492..9dc581087bb 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -102,6 +102,7 @@ static int read_patches(const char *range, struct string_list *list)
 				strbuf_addbuf(&buf, &line);
 				strbuf_addstr(&buf, "\n\n");
 			} else if (starts_with(line.buf, "    ")) {
+				strbuf_rtrim(&line);
 				strbuf_addbuf(&buf, &line);
 				strbuf_addch(&buf, '\n');
 			}
-- 
2.17.0.395.g6a618d6010f.dirty



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 07/18] branch-diff: indent the diffs just like tbdiff
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
                   ` (5 preceding siblings ...)
  2018-05-03 15:30 ` [PATCH 06/18] branch-diff: right-trim commit messages Johannes Schindelin
@ 2018-05-03 15:30 ` Johannes Schindelin
  2018-05-03 15:30 ` [PATCH 08/18] branch-diff: suppress the diff headers Johannes Schindelin
                   ` (14 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 15:30 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

The main information in the branch-diff view comes from the list of
matching and non-matching commits, the diffs are additional information.
Indenting them helps with the reading flow.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index 9dc581087bb..a4e602deb5d 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -272,6 +272,11 @@ static const char *short_oid(struct patch_util *util)
 	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
 }
 
+static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)
+{
+	return data;
+}
+
 static struct diff_filespec *get_filespec(const char *name, const char *p)
 {
 	struct diff_filespec *spec = alloc_filespec(name);
@@ -350,6 +355,7 @@ static void output(struct string_list *a, struct string_list *b,
 int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 {
 	struct diff_options diffopt = { 0 };
+	struct strbuf four_spaces = STRBUF_INIT;
 	double creation_weight = 0.6;
 	struct option options[] = {
 		OPT_SET_INT(0, "no-patches", &diffopt.output_format,
@@ -368,6 +374,9 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 
 	diff_setup(&diffopt);
 	diffopt.output_format = DIFF_FORMAT_PATCH;
+	diffopt.output_prefix = output_prefix_cb;
+	strbuf_addstr(&four_spaces, "    ");
+	diffopt.output_prefix_data = &four_spaces;
 
 	argc = parse_options(argc, argv, NULL, options,
 			builtin_branch_diff_usage, PARSE_OPT_KEEP_UNKNOWN);
-- 
2.17.0.395.g6a618d6010f.dirty



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 08/18] branch-diff: suppress the diff headers
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
                   ` (6 preceding siblings ...)
  2018-05-03 15:30 ` [PATCH 07/18] branch-diff: indent the diffs just like tbdiff Johannes Schindelin
@ 2018-05-03 15:30 ` Johannes Schindelin
  2018-05-03 15:30 ` [PATCH 09/18] branch-diff: adjust the output of the commit pairs Johannes Schindelin
                   ` (13 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 15:30 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

When showing the diff between corresponding patches of the two branch
versions, we have to make up a fake filename to run the diff machinery.

That filename does not carry any meaningful information, hence tbdiff
suppresses it. So we should, too.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 1 +
 diff.c                | 5 ++++-
 diff.h                | 1 +
 3 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index a4e602deb5d..9edc5a0e89b 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -374,6 +374,7 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 
 	diff_setup(&diffopt);
 	diffopt.output_format = DIFF_FORMAT_PATCH;
+	diffopt.flags.suppress_diff_headers = 1;
 	diffopt.output_prefix = output_prefix_cb;
 	strbuf_addstr(&four_spaces, "    ");
 	diffopt.output_prefix_data = &four_spaces;
diff --git a/diff.c b/diff.c
index 1289df4b1f9..f1bda0db3f5 100644
--- a/diff.c
+++ b/diff.c
@@ -3197,13 +3197,16 @@ static void builtin_diff(const char *name_a,
 		memset(&xpp, 0, sizeof(xpp));
 		memset(&xecfg, 0, sizeof(xecfg));
 		memset(&ecbdata, 0, sizeof(ecbdata));
+		if (o->flags.suppress_diff_headers)
+			lbl[0] = NULL;
 		ecbdata.label_path = lbl;
 		ecbdata.color_diff = want_color(o->use_color);
 		ecbdata.ws_rule = whitespace_rule(name_b);
 		if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
 			check_blank_at_eof(&mf1, &mf2, &ecbdata);
 		ecbdata.opt = o;
-		ecbdata.header = header.len ? &header : NULL;
+		if (header.len && !o->flags.suppress_diff_headers)
+			ecbdata.header = &header;
 		xpp.flags = o->xdl_opts;
 		xpp.anchors = o->anchors;
 		xpp.anchors_nr = o->anchors_nr;
diff --git a/diff.h b/diff.h
index d29560f822c..0dd6a71af60 100644
--- a/diff.h
+++ b/diff.h
@@ -94,6 +94,7 @@ struct diff_flags {
 	unsigned funccontext:1;
 	unsigned default_follow_renames:1;
 	unsigned stat_with_summary:1;
+	unsigned suppress_diff_headers:1;
 };
 
 static inline void diff_flags_or(struct diff_flags *a,
-- 
2.17.0.395.g6a618d6010f.dirty



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 09/18] branch-diff: adjust the output of the commit pairs
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
                   ` (7 preceding siblings ...)
  2018-05-03 15:30 ` [PATCH 08/18] branch-diff: suppress the diff headers Johannes Schindelin
@ 2018-05-03 15:30 ` Johannes Schindelin
  2018-05-03 15:30 ` [PATCH 10/18] branch-diff: do not show "function names" in hunk headers Johannes Schindelin
                   ` (12 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 15:30 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

This change brings branch-diff yet another step closer to feature parity
with tbdiff: it now shows the oneline, too, and indicates with `=` when
the commits have identical diffs.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 67 +++++++++++++++++++++++++++++++++++++------
 1 file changed, 58 insertions(+), 9 deletions(-)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index 9edc5a0e89b..0a0564b8ec2 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -8,6 +8,8 @@
 #include "hungarian.h"
 #include "diff.h"
 #include "diffcore.h"
+#include "commit.h"
+#include "pretty.h"
 
 static const char * const builtin_branch_diff_usage[] = {
 	N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),
@@ -267,9 +269,57 @@ static int get_correspondences(struct string_list *a, struct string_list *b,
 	return res;
 }
 
-static const char *short_oid(struct patch_util *util)
+static void output_pair_header(struct strbuf *buf,
+			       int i, struct patch_util *a_util,
+			       int j, struct patch_util *b_util)
 {
-	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
+	static char *dashes;
+	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
+	struct commit *commit;
+
+	if (!dashes) {
+		char *p;
+
+		dashes = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV));
+		for (p = dashes; *p; p++)
+			*p = '-';
+	}
+
+	strbuf_reset(buf);
+	if (i < 0)
+		strbuf_addf(buf, "-:  %s ", dashes);
+	else
+		strbuf_addf(buf, "%d:  %s ", i + 1,
+			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
+
+	if (i < 0)
+		strbuf_addch(buf, '>');
+	else if (j < 0)
+		strbuf_addch(buf, '<');
+	else if (strcmp(a_util->patch, b_util->patch))
+		strbuf_addch(buf, '!');
+	else
+		strbuf_addch(buf, '=');
+
+	if (j < 0)
+		strbuf_addf(buf, " -:  %s", dashes);
+	else
+		strbuf_addf(buf, " %d:  %s", j + 1,
+			    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));
+
+	commit = lookup_commit_reference(oid);
+	if (commit) {
+		const char *commit_buffer = get_commit_buffer(commit, NULL);
+		const char *subject;
+
+		find_commit_subject(commit_buffer, &subject);
+		strbuf_addch(buf, ' ');
+		format_subject(buf, subject, " ");
+		unuse_commit_buffer(commit, commit_buffer);
+	}
+	strbuf_addch(buf, '\n');
+
+	fwrite(buf->buf, buf->len, 1, stdout);
 }
 
 static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)
@@ -303,6 +353,7 @@ static void patch_diff(const char *a, const char *b,
 static void output(struct string_list *a, struct string_list *b,
 		   struct diff_options *diffopt)
 {
+	struct strbuf buf = STRBUF_INIT;
 	int i = 0, j = 0;
 
 	/*
@@ -324,25 +375,22 @@ static void output(struct string_list *a, struct string_list *b,
 
 		/* Show unmatched LHS commit whose predecessors were shown. */
 		if (i < a->nr && a_util->matching < 0) {
-			printf("%d: %s < -: --------\n",
-			       i + 1, short_oid(a_util));
+			output_pair_header(&buf, i, a_util, -1, NULL);
 			i++;
 			continue;
 		}
 
 		/* Show unmatched RHS commits. */
 		while (j < b->nr && b_util->matching < 0) {
-			printf("-: -------- > %d: %s\n",
-			       j + 1, short_oid(b_util));
+			output_pair_header(&buf, -1, NULL, j, b_util);
 			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
 
 		/* Show matching LHS/RHS pair. */
 		if (j < b->nr) {
 			a_util = a->items[b_util->matching].util;
-			printf("%d: %s ! %d: %s\n",
-			       b_util->matching + 1, short_oid(a_util),
-			       j + 1, short_oid(b_util));
+			output_pair_header(&buf,
+					   b_util->matching, a_util, j, b_util);
 			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
 				patch_diff(a->items[b_util->matching].string,
 					   b->items[j].string, diffopt);
@@ -350,6 +398,7 @@ static void output(struct string_list *a, struct string_list *b,
 			j++;
 		}
 	}
+	strbuf_release(&buf);
 }
 
 int cmd_branch_diff(int argc, const char **argv, const char *prefix)
-- 
2.17.0.395.g6a618d6010f.dirty



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 10/18] branch-diff: do not show "function names" in hunk headers
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
                   ` (8 preceding siblings ...)
  2018-05-03 15:30 ` [PATCH 09/18] branch-diff: adjust the output of the commit pairs Johannes Schindelin
@ 2018-05-03 15:30 ` Johannes Schindelin
  2018-05-03 15:30 ` [PATCH 11/18] branch-diff: add tests Johannes Schindelin
                   ` (11 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 15:30 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

We are comparing complete, formatted commit messages with patches. There
are no function names here, so stop looking for them.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index 0a0564b8ec2..7625da09e6f 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -10,6 +10,7 @@
 #include "diffcore.h"
 #include "commit.h"
 #include "pretty.h"
+#include "userdiff.h"
 
 static const char * const builtin_branch_diff_usage[] = {
 	N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),
@@ -327,6 +328,10 @@ static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)
 	return data;
 }
 
+static struct userdiff_driver no_func_name = {
+	.funcname = { "$^", 0 }
+};
+
 static struct diff_filespec *get_filespec(const char *name, const char *p)
 {
 	struct diff_filespec *spec = alloc_filespec(name);
@@ -336,6 +341,7 @@ static struct diff_filespec *get_filespec(const char *name, const char *p)
 	spec->size = strlen(p);
 	spec->should_munmap = 0;
 	spec->is_stdin = 1;
+	spec->driver = &no_func_name;
 
 	return spec;
 }
-- 
2.17.0.395.g6a618d6010f.dirty



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 11/18] branch-diff: add tests
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
                   ` (9 preceding siblings ...)
  2018-05-03 15:30 ` [PATCH 10/18] branch-diff: do not show "function names" in hunk headers Johannes Schindelin
@ 2018-05-03 15:30 ` Johannes Schindelin
  2018-05-03 16:56   ` Ævar Arnfjörð Bjarmason
                     ` (2 more replies)
  2018-05-03 15:30 ` [PATCH 12/18] branch-diff: use color for the commit pairs Johannes Schindelin
                   ` (10 subsequent siblings)
  21 siblings, 3 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 15:30 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Thomas Rast, Junio C Hamano,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

From: Thomas Rast <tr@thomasrast.ch>

These are essentially lifted from https://github.com/trast/tbdiff, with
light touch-ups to account for the new command name.

Apart from renaming `tbdiff` to `branch-diff`, only one test case needed
to be adjusted: 11 - 'changed message'.

The underlying reason it had to be adjusted is that diff generation is
sometimes ambiguous. In this case, a comment line and an empty line are
added, but it is ambiguous whether they were added after the existing
empty line, or whether an empty line and the comment line are added
*before* the existing emtpy line. And apparently xdiff picks a different
option here than Python's difflib.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/.gitattributes       |   1 +
 t/t7910-branch-diff.sh | 144 ++++++++++
 t/t7910/history.export | 604 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 749 insertions(+)
 create mode 100755 t/t7910-branch-diff.sh
 create mode 100644 t/t7910/history.export

diff --git a/t/.gitattributes b/t/.gitattributes
index 3bd959ae523..af15d5aeedd 100644
--- a/t/.gitattributes
+++ b/t/.gitattributes
@@ -18,5 +18,6 @@ t[0-9][0-9][0-9][0-9]/* -whitespace
 /t5515/* eol=lf
 /t556x_common eol=lf
 /t7500/* eol=lf
+/t7910/* eol=lf
 /t8005/*.txt eol=lf
 /t9*/*.dump eol=lf
diff --git a/t/t7910-branch-diff.sh b/t/t7910-branch-diff.sh
new file mode 100755
index 00000000000..a7fece88045
--- /dev/null
+++ b/t/t7910-branch-diff.sh
@@ -0,0 +1,144 @@
+#!/bin/sh
+
+test_description='branch-diff tests'
+
+. ./test-lib.sh
+
+# Note that because of git-branch-diff's heuristics, test_commit does more
+# harm than good.  We need some real history.
+
+test_expect_success 'setup' '
+	git fast-import < "$TEST_DIRECTORY"/t7910/history.export
+'
+
+test_expect_success 'simple A..B A..C (unmodified)' '
+	git branch-diff --no-color master..topic master..unmodified >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  35b9b25 s/5/A/
+	2:  fccce22 = 2:  de345ab s/4/A/
+	3:  147e64e = 3:  9af6654 s/11/B/
+	4:  a63e992 = 4:  2901f77 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'simple B...C (unmodified)' '
+	git branch-diff --no-color topic...unmodified >actual &&
+	# same "expected" as above
+	test_cmp expected actual
+'
+
+test_expect_success 'simple A B C (unmodified)' '
+	git branch-diff --no-color master topic unmodified >actual &&
+	# same "expected" as above
+	test_cmp expected actual
+'
+
+test_expect_success 'trivial reordering' '
+	git branch-diff --no-color master topic reordered >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  aca177a s/5/A/
+	3:  147e64e = 2:  14ad629 s/11/B/
+	4:  a63e992 = 3:  ee58208 s/12/B/
+	2:  fccce22 = 4:  307b27a s/4/A/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'removed a commit' '
+	git branch-diff --no-color master topic removed >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  7657159 s/5/A/
+	2:  fccce22 < -:  ------- s/4/A/
+	3:  147e64e = 2:  43d84d3 s/11/B/
+	4:  a63e992 = 3:  a740396 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'added a commit' '
+	git branch-diff --no-color master topic added >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  2716022 s/5/A/
+	2:  fccce22 = 2:  b62accd s/4/A/
+	-:  ------- > 3:  df46cfa s/6/A/
+	3:  147e64e = 4:  3e64548 s/11/B/
+	4:  a63e992 = 5:  12b4063 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'new base, A B C' '
+	git branch-diff --no-color master topic rebased >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  cc9c443 s/5/A/
+	2:  fccce22 = 2:  c5d9641 s/4/A/
+	3:  147e64e = 3:  28cc2b6 s/11/B/
+	4:  a63e992 = 4:  5628ab7 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'new base, B...C' '
+	# this syntax includes the commits from master!
+	git branch-diff --no-color topic...rebased >actual &&
+	cat >expected <<-EOF &&
+	-:  ------- > 1:  a31b12e unrelated
+	1:  4de457d = 2:  cc9c443 s/5/A/
+	2:  fccce22 = 3:  c5d9641 s/4/A/
+	3:  147e64e = 4:  28cc2b6 s/11/B/
+	4:  a63e992 = 5:  5628ab7 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'changed commit' '
+	git branch-diff --no-color topic...changed >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  a4b3333 s/5/A/
+	2:  fccce22 = 2:  f51d370 s/4/A/
+	3:  147e64e ! 3:  0559556 s/11/B/
+	    @@ -10,7 +10,7 @@
+	      9
+	      10
+	     -11
+	    -+B
+	    ++BB
+	      12
+	      13
+	      14
+	4:  a63e992 ! 4:  d966c5c s/12/B/
+	    @@ -8,7 +8,7 @@
+	     @@
+	      9
+	      10
+	    - B
+	    + BB
+	     -12
+	     +B
+	      13
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'changed message' '
+	git branch-diff --no-color topic...changed-message >actual &&
+	sed s/Z/\ /g >expected <<-EOF &&
+	1:  4de457d = 1:  f686024 s/5/A/
+	2:  fccce22 ! 2:  4ab067d s/4/A/
+	    @@ -2,6 +2,8 @@
+	    Z
+	    Z    s/4/A/
+	    Z
+	    +    Also a silly comment here!
+	    +
+	    Zdiff --git a/file b/file
+	    Z--- a/file
+	    Z+++ b/file
+	3:  147e64e = 3:  b9cb956 s/11/B/
+	4:  a63e992 = 4:  8add5f1 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_done
diff --git a/t/t7910/history.export b/t/t7910/history.export
new file mode 100644
index 00000000000..b8ffff0940d
--- /dev/null
+++ b/t/t7910/history.export
@@ -0,0 +1,604 @@
+blob
+mark :1
+data 51
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+reset refs/heads/removed
+commit refs/heads/removed
+mark :2
+author Thomas Rast <trast@inf.ethz.ch> 1374424921 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374484724 +0200
+data 8
+initial
+M 100644 :1 file
+
+blob
+mark :3
+data 51
+1
+2
+3
+4
+A
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :4
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+blob
+mark :5
+data 51
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :6
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+data 7
+s/4/A/
+from :4
+M 100644 :5 file
+
+blob
+mark :7
+data 50
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+B
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :8
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+data 8
+s/11/B/
+from :6
+M 100644 :7 file
+
+blob
+mark :9
+data 49
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+B
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :10
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+data 8
+s/12/B/
+from :8
+M 100644 :9 file
+
+blob
+mark :11
+data 10
+unrelated
+
+commit refs/heads/master
+mark :12
+author Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200
+data 10
+unrelated
+from :2
+M 100644 :11 otherfile
+
+commit refs/heads/rebased
+mark :13
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485137 +0200
+data 7
+s/5/A/
+from :12
+M 100644 :3 file
+
+commit refs/heads/rebased
+mark :14
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
+data 7
+s/4/A/
+from :13
+M 100644 :5 file
+
+commit refs/heads/rebased
+mark :15
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
+data 8
+s/11/B/
+from :14
+M 100644 :7 file
+
+commit refs/heads/rebased
+mark :16
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
+data 8
+s/12/B/
+from :15
+M 100644 :9 file
+
+commit refs/heads/added
+mark :17
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/added
+mark :18
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 7
+s/4/A/
+from :17
+M 100644 :5 file
+
+blob
+mark :19
+data 51
+1
+2
+3
+A
+A
+A
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/added
+mark :20
+author Thomas Rast <trast@inf.ethz.ch> 1374485186 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 7
+s/6/A/
+from :18
+M 100644 :19 file
+
+blob
+mark :21
+data 50
+1
+2
+3
+A
+A
+A
+7
+8
+9
+10
+B
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/added
+mark :22
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 8
+s/11/B/
+from :20
+M 100644 :21 file
+
+blob
+mark :23
+data 49
+1
+2
+3
+A
+A
+A
+7
+8
+9
+10
+B
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/added
+mark :24
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 8
+s/12/B/
+from :22
+M 100644 :23 file
+
+commit refs/heads/reordered
+mark :25
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+blob
+mark :26
+data 50
+1
+2
+3
+4
+A
+6
+7
+8
+9
+10
+B
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/reordered
+mark :27
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 8
+s/11/B/
+from :25
+M 100644 :26 file
+
+blob
+mark :28
+data 49
+1
+2
+3
+4
+A
+6
+7
+8
+9
+10
+B
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/reordered
+mark :29
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 8
+s/12/B/
+from :27
+M 100644 :28 file
+
+commit refs/heads/reordered
+mark :30
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 7
+s/4/A/
+from :29
+M 100644 :9 file
+
+commit refs/heads/changed
+mark :31
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/changed
+mark :32
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 7
+s/4/A/
+from :31
+M 100644 :5 file
+
+blob
+mark :33
+data 51
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+BB
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/changed
+mark :34
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 8
+s/11/B/
+from :32
+M 100644 :33 file
+
+blob
+mark :35
+data 50
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+BB
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/changed
+mark :36
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 8
+s/12/B/
+from :34
+M 100644 :35 file
+
+commit refs/heads/changed-message
+mark :37
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/changed-message
+mark :38
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200
+data 35
+s/4/A/
+
+Also a silly comment here!
+from :37
+M 100644 :5 file
+
+commit refs/heads/changed-message
+mark :39
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200
+data 8
+s/11/B/
+from :38
+M 100644 :7 file
+
+commit refs/heads/changed-message
+mark :40
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200
+data 8
+s/12/B/
+from :39
+M 100644 :9 file
+
+commit refs/heads/unmodified
+mark :41
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/unmodified
+mark :42
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200
+data 7
+s/4/A/
+from :41
+M 100644 :5 file
+
+commit refs/heads/unmodified
+mark :43
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200
+data 8
+s/11/B/
+from :42
+M 100644 :7 file
+
+commit refs/heads/unmodified
+mark :44
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200
+data 8
+s/12/B/
+from :43
+M 100644 :9 file
+
+commit refs/heads/removed
+mark :45
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/removed
+mark :46
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
+data 8
+s/11/B/
+from :45
+M 100644 :26 file
+
+commit refs/heads/removed
+mark :47
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
+data 8
+s/12/B/
+from :46
+M 100644 :28 file
+
+reset refs/heads/removed
+from :47
+
-- 
2.17.0.395.g6a618d6010f.dirty



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 12/18] branch-diff: use color for the commit pairs
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
                   ` (10 preceding siblings ...)
  2018-05-03 15:30 ` [PATCH 11/18] branch-diff: add tests Johannes Schindelin
@ 2018-05-03 15:30 ` Johannes Schindelin
  2018-05-03 15:30 ` [PATCH 13/18] color: provide inverted colors, too Johannes Schindelin
                   ` (9 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 15:30 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

Arguably the most important part of branch-diff's output is the list of
commits in the two branches, together with their relationships.

For that reason, tbdiff introduced color-coding that is pretty
intuitive, especially for unchanged patches (all dim yellow, like the
first line in `git show`'s output) vs modified patches (old commit is
red, new commit is green). Let's imitate that color scheme.

While at it, also copy tbdiff's change of the fragment color to magenta.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 49 +++++++++++++++++++++++++++++++------------
 1 file changed, 36 insertions(+), 13 deletions(-)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index 7625da09e6f..e505b696d11 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -270,13 +270,19 @@ static int get_correspondences(struct string_list *a, struct string_list *b,
 	return res;
 }
 
-static void output_pair_header(struct strbuf *buf,
+static void output_pair_header(struct diff_options *diffopt, struct strbuf *buf,
 			       int i, struct patch_util *a_util,
 			       int j, struct patch_util *b_util)
 {
 	static char *dashes;
 	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
 	struct commit *commit;
+	char status;
+	const char *color_reset = diff_get_color_opt(diffopt, DIFF_RESET);
+	const char *color_old = diff_get_color_opt(diffopt, DIFF_FILE_OLD);
+	const char *color_new = diff_get_color_opt(diffopt, DIFF_FILE_NEW);
+	const char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT);
+	const char *color;
 
 	if (!dashes) {
 		char *p;
@@ -286,21 +292,33 @@ static void output_pair_header(struct strbuf *buf,
 			*p = '-';
 	}
 
+	if (j < 0) {
+		color = color_old;
+		status = '<';
+	} else if (i < 0) {
+		color = color_new;
+		status = '>';
+	} else if (strcmp(a_util->patch, b_util->patch)) {
+		color = color_commit;
+		status = '!';
+	} else {
+		color = color_commit;
+		status = '=';
+	}
+
 	strbuf_reset(buf);
+	strbuf_addstr(buf, status == '!' ? color_old : color);
 	if (i < 0)
 		strbuf_addf(buf, "-:  %s ", dashes);
 	else
 		strbuf_addf(buf, "%d:  %s ", i + 1,
 			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
 
-	if (i < 0)
-		strbuf_addch(buf, '>');
-	else if (j < 0)
-		strbuf_addch(buf, '<');
-	else if (strcmp(a_util->patch, b_util->patch))
-		strbuf_addch(buf, '!');
-	else
-		strbuf_addch(buf, '=');
+	if (status == '!')
+		strbuf_addf(buf, "%s%s", color_reset, color);
+	strbuf_addch(buf, status);
+	if (status == '!')
+		strbuf_addf(buf, "%s%s", color_reset, color_new);
 
 	if (j < 0)
 		strbuf_addf(buf, " -:  %s", dashes);
@@ -313,12 +331,15 @@ static void output_pair_header(struct strbuf *buf,
 		const char *commit_buffer = get_commit_buffer(commit, NULL);
 		const char *subject;
 
+		if (status == '!')
+			strbuf_addf(buf, "%s%s", color_reset, color);
+
 		find_commit_subject(commit_buffer, &subject);
 		strbuf_addch(buf, ' ');
 		format_subject(buf, subject, " ");
 		unuse_commit_buffer(commit, commit_buffer);
 	}
-	strbuf_addch(buf, '\n');
+	strbuf_addf(buf, "%s\n", color_reset);
 
 	fwrite(buf->buf, buf->len, 1, stdout);
 }
@@ -381,21 +402,21 @@ static void output(struct string_list *a, struct string_list *b,
 
 		/* Show unmatched LHS commit whose predecessors were shown. */
 		if (i < a->nr && a_util->matching < 0) {
-			output_pair_header(&buf, i, a_util, -1, NULL);
+			output_pair_header(diffopt, &buf, i, a_util, -1, NULL);
 			i++;
 			continue;
 		}
 
 		/* Show unmatched RHS commits. */
 		while (j < b->nr && b_util->matching < 0) {
-			output_pair_header(&buf, -1, NULL, j, b_util);
+			output_pair_header(diffopt, &buf, -1, NULL, j, b_util);
 			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
 
 		/* Show matching LHS/RHS pair. */
 		if (j < b->nr) {
 			a_util = a->items[b_util->matching].util;
-			output_pair_header(&buf,
+			output_pair_header(diffopt, &buf,
 					   b_util->matching, a_util, j, b_util);
 			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
 				patch_diff(a->items[b_util->matching].string,
@@ -427,6 +448,8 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 	struct string_list branch1 = STRING_LIST_INIT_DUP;
 	struct string_list branch2 = STRING_LIST_INIT_DUP;
 
+	git_diff_basic_config("diff.color.frag", "magenta", NULL);
+
 	diff_setup(&diffopt);
 	diffopt.output_format = DIFF_FORMAT_PATCH;
 	diffopt.flags.suppress_diff_headers = 1;
-- 
2.17.0.395.g6a618d6010f.dirty



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 13/18] color: provide inverted colors, too
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
                   ` (11 preceding siblings ...)
  2018-05-03 15:30 ` [PATCH 12/18] branch-diff: use color for the commit pairs Johannes Schindelin
@ 2018-05-03 15:30 ` Johannes Schindelin
  2018-05-03 15:30 ` [PATCH 14/18] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin
                   ` (8 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 15:30 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

For every regular color, there exists the inverted equivalent where
background and foreground colors are exchanged.

We will use this in the next commit to allow inverting *just* the +/-
signs in a diff.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 color.h | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/color.h b/color.h
index cd0bcedd084..f0984b09583 100644
--- a/color.h
+++ b/color.h
@@ -36,6 +36,12 @@ struct strbuf;
 #define GIT_COLOR_BOLD_BLUE	"\033[1;34m"
 #define GIT_COLOR_BOLD_MAGENTA	"\033[1;35m"
 #define GIT_COLOR_BOLD_CYAN	"\033[1;36m"
+#define GIT_COLOR_INV_RED	"\033[7;31m"
+#define GIT_COLOR_INV_GREEN	"\033[7;32m"
+#define GIT_COLOR_INV_YELLOW	"\033[7;33m"
+#define GIT_COLOR_INV_BLUE	"\033[7;34m"
+#define GIT_COLOR_INV_MAGENTA	"\033[7;35m"
+#define GIT_COLOR_INV_CYAN	"\033[7;36m"
 #define GIT_COLOR_BG_RED	"\033[41m"
 #define GIT_COLOR_BG_GREEN	"\033[42m"
 #define GIT_COLOR_BG_YELLOW	"\033[43m"
-- 
2.17.0.395.g6a618d6010f.dirty



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 14/18] diff: add an internal option to dual-color diffs of diffs
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
                   ` (12 preceding siblings ...)
  2018-05-03 15:30 ` [PATCH 13/18] color: provide inverted colors, too Johannes Schindelin
@ 2018-05-03 15:30 ` Johannes Schindelin
  2018-05-03 15:31 ` [PATCH 15/18] branch-diff: offer to dual-color the diffs Johannes Schindelin
                   ` (7 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 15:30 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

When diffing diffs, it can be quite daunting to figure out what the heck
is going on, as there are nested +/- signs.

Let's make this easier by adding a flag in diff_options that allows
color-coding the outer diff sign with inverted colors, so that the
preimage and postimage is colored like the diff it is.

Of course, this really only makes sense when the preimage and postimage
*are* diffs. So let's not expose this flag via a command-line option for
now.

This is a feature that was invented by git-tbdiff, and it will be used
in `branch-diff` in the next commit.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 diff.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++---------
 diff.h |  5 ++++-
 2 files changed, 59 insertions(+), 11 deletions(-)

diff --git a/diff.c b/diff.c
index f1bda0db3f5..98a41e88620 100644
--- a/diff.c
+++ b/diff.c
@@ -67,6 +67,8 @@ static char diff_colors[][COLOR_MAXLEN] = {
 	GIT_COLOR_BOLD_YELLOW,	/* NEW_MOVED ALTERNATIVE */
 	GIT_COLOR_FAINT,	/* NEW_MOVED_DIM */
 	GIT_COLOR_FAINT_ITALIC,	/* NEW_MOVED_ALTERNATIVE_DIM */
+	GIT_COLOR_INV_RED,	/* OLD_INV */
+	GIT_COLOR_INV_GREEN,	/* NEW_INV */
 };
 
 static NORETURN void die_want_option(const char *option_name)
@@ -108,6 +110,10 @@ static int parse_diff_color_slot(const char *var)
 		return DIFF_FILE_NEW_MOVED_DIM;
 	if (!strcasecmp(var, "newmovedalternativedimmed"))
 		return DIFF_FILE_NEW_MOVED_ALT_DIM;
+	if (!strcasecmp(var, "oldinv"))
+		return DIFF_FILE_OLD_INV;
+	if (!strcasecmp(var, "newinv"))
+		return DIFF_FILE_NEW_INV;
 	return -1;
 }
 
@@ -577,7 +583,10 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res
 	int nofirst;
 	FILE *file = o->file;
 
-	fputs(diff_line_prefix(o), file);
+	if (first)
+		fputs(diff_line_prefix(o), file);
+	else if (!len)
+		return;
 
 	if (len == 0) {
 		has_trailing_newline = (first == '\n');
@@ -596,7 +605,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res
 
 	if (len || !nofirst) {
 		fputs(set, file);
-		if (!nofirst)
+		if (first && !nofirst)
 			fputc(first, file);
 		fwrite(line, len, 1, file);
 		fputs(reset, file);
@@ -970,7 +979,8 @@ static void dim_moved_lines(struct diff_options *o)
 
 static void emit_line_ws_markup(struct diff_options *o,
 				const char *set, const char *reset,
-				const char *line, int len, char sign,
+				const char *line, int len,
+				const char *set_sign, char sign,
 				unsigned ws_rule, int blank_at_eof)
 {
 	const char *ws = NULL;
@@ -981,14 +991,18 @@ static void emit_line_ws_markup(struct diff_options *o,
 			ws = NULL;
 	}
 
-	if (!ws)
+	if (!ws && set_sign == set)
 		emit_line_0(o, set, reset, sign, line, len);
-	else if (blank_at_eof)
+	else if (!ws) {
+		/* Emit just the prefix, then the rest. */
+		emit_line_0(o, set_sign, reset, sign, "", 0);
+		emit_line_0(o, set, reset, 0, line, len);
+	} else if (blank_at_eof)
 		/* Blank line at EOF - paint '+' as well */
 		emit_line_0(o, ws, reset, sign, line, len);
 	else {
 		/* Emit just the prefix, then the rest. */
-		emit_line_0(o, set, reset, sign, "", 0);
+		emit_line_0(o, set_sign, reset, sign, "", 0);
 		ws_check_emit(line, len, ws_rule,
 			      o->file, set, reset, ws);
 	}
@@ -998,7 +1012,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 					 struct emitted_diff_symbol *eds)
 {
 	static const char *nneof = " No newline at end of file\n";
-	const char *context, *reset, *set, *meta, *fraginfo;
+	const char *context, *reset, *set, *set_sign, *meta, *fraginfo;
 	struct strbuf sb = STRBUF_INIT;
 
 	enum diff_symbol s = eds->s;
@@ -1038,7 +1052,16 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 	case DIFF_SYMBOL_CONTEXT:
 		set = diff_get_color_opt(o, DIFF_CONTEXT);
 		reset = diff_get_color_opt(o, DIFF_RESET);
-		emit_line_ws_markup(o, set, reset, line, len, ' ',
+		set_sign = set;
+		if (o->flags.dual_color_diffed_diffs) {
+			char c = !len ? 0 : line[0];
+
+			if (c == '+')
+				set = diff_get_color_opt(o, DIFF_FILE_NEW);
+			else if (c == '-')
+				set = diff_get_color_opt(o, DIFF_FILE_OLD);
+		}
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',
 				    flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);
 		break;
 	case DIFF_SYMBOL_PLUS:
@@ -1065,7 +1088,18 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 			set = diff_get_color_opt(o, DIFF_FILE_NEW);
 		}
 		reset = diff_get_color_opt(o, DIFF_RESET);
-		emit_line_ws_markup(o, set, reset, line, len, '+',
+		if (!o->flags.dual_color_diffed_diffs)
+			set_sign = set;
+		else {
+			char c = !len ? 0 : line[0];
+
+			set_sign = diff_get_color_opt(o, DIFF_FILE_NEW_INV);
+			if (c == '-')
+				set = diff_get_color_opt(o, DIFF_FILE_OLD);
+			else if (c != '+')
+				set = diff_get_color_opt(o, DIFF_CONTEXT);
+		}
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK,
 				    flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);
 		break;
@@ -1093,7 +1127,18 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 			set = diff_get_color_opt(o, DIFF_FILE_OLD);
 		}
 		reset = diff_get_color_opt(o, DIFF_RESET);
-		emit_line_ws_markup(o, set, reset, line, len, '-',
+		if (!o->flags.dual_color_diffed_diffs)
+			set_sign = set;
+		else {
+			char c = !len ? 0 : line[0];
+
+			set_sign = diff_get_color_opt(o, DIFF_FILE_OLD_INV);
+			if (c == '+')
+				set = diff_get_color_opt(o, DIFF_FILE_NEW);
+			else if (c != '-')
+				set = diff_get_color_opt(o, DIFF_CONTEXT);
+		}
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, '-',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);
 		break;
 	case DIFF_SYMBOL_WORDS_PORCELAIN:
diff --git a/diff.h b/diff.h
index 0dd6a71af60..c3e5d27967c 100644
--- a/diff.h
+++ b/diff.h
@@ -95,6 +95,7 @@ struct diff_flags {
 	unsigned default_follow_renames:1;
 	unsigned stat_with_summary:1;
 	unsigned suppress_diff_headers:1;
+	unsigned dual_color_diffed_diffs:1;
 };
 
 static inline void diff_flags_or(struct diff_flags *a,
@@ -242,7 +243,9 @@ enum color_diff {
 	DIFF_FILE_NEW_MOVED = 13,
 	DIFF_FILE_NEW_MOVED_ALT = 14,
 	DIFF_FILE_NEW_MOVED_DIM = 15,
-	DIFF_FILE_NEW_MOVED_ALT_DIM = 16
+	DIFF_FILE_NEW_MOVED_ALT_DIM = 16,
+	DIFF_FILE_OLD_INV = 17,
+	DIFF_FILE_NEW_INV = 18
 };
 const char *diff_get_color(int diff_use_color, enum color_diff ix);
 #define diff_get_color_opt(o, ix) \
-- 
2.17.0.395.g6a618d6010f.dirty



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 15/18] branch-diff: offer to dual-color the diffs
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
                   ` (13 preceding siblings ...)
  2018-05-03 15:30 ` [PATCH 14/18] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin
@ 2018-05-03 15:31 ` Johannes Schindelin
  2018-05-03 15:31 ` [PATCH 16/18] branch-diff --dual-color: work around bogus white-space warning Johannes Schindelin
                   ` (6 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 15:31 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

When showing what changed between old and new commits, we show a diff of
the patches. This diff is a diff between diffs, therefore there are
nested +/- signs, and it can be relatively hard to understand what is
going on.

With the --dual-color option, the preimage and the postimage are colored
like the diffs they are, and the *outer* +/- sign is inverted for
clarity.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index e505b696d11..edf80ecb736 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -432,8 +432,11 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 {
 	struct diff_options diffopt = { 0 };
 	struct strbuf four_spaces = STRBUF_INIT;
+	int dual_color = 0;
 	double creation_weight = 0.6;
 	struct option options[] = {
+		OPT_BOOL(0, "dual-color", &dual_color,
+			    N_("color both diff and diff-between-diffs")),
 		OPT_SET_INT(0, "no-patches", &diffopt.output_format,
 			    N_("short format (no diffs)"),
 			    DIFF_FORMAT_NO_OUTPUT),
@@ -469,6 +472,11 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 	argc = j;
 	diff_setup_done(&diffopt);
 
+	if (dual_color) {
+		diffopt.use_color = 1;
+		diffopt.flags.dual_color_diffed_diffs = 1;
+	}
+
 	if (argc == 2) {
 		if (!strstr(argv[0], ".."))
 			warning(_("no .. in range: '%s'"), argv[0]);
-- 
2.17.0.395.g6a618d6010f.dirty



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 16/18] branch-diff --dual-color: work around bogus white-space warning
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
                   ` (14 preceding siblings ...)
  2018-05-03 15:31 ` [PATCH 15/18] branch-diff: offer to dual-color the diffs Johannes Schindelin
@ 2018-05-03 15:31 ` Johannes Schindelin
  2018-05-03 15:31 ` [PATCH 17/18] branch-diff: add a man page Johannes Schindelin
                   ` (5 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 15:31 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

When displaying a diff of diffs, it is possible that there is an outer
`+` before a context line. That happens when the context changed between
old and new commit. When that context line starts with a tab (after the
space that marks it as context line), our diff machinery spits out a
white-space error (space before tab), but in this case, that is
incorrect.

Work around this by detecting that situation and simply *not* printing
the space in that case.

This is slightly improper a fix because it is conceivable that an
output_prefix might be configured with *just* the right length to let
that tab jump to a different tab stop depending whether we emit that
space or not.

However, the proper fix would be relatively ugly and intrusive because
it would have to *weaken* the WS_SPACE_BEFORE_TAB option in ws.c.
Besides, we do not expose the --dual-color option in cases other than
the `branch-diff` command, which only uses a hard-coded output_prefix of
four spaces (which misses the problem by one column ;-)).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 diff.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/diff.c b/diff.c
index 98a41e88620..b98a18fe014 100644
--- a/diff.c
+++ b/diff.c
@@ -1098,6 +1098,12 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 				set = diff_get_color_opt(o, DIFF_FILE_OLD);
 			else if (c != '+')
 				set = diff_get_color_opt(o, DIFF_CONTEXT);
+			/* Avoid space-before-tab warning */
+			if (c == ' ' && (len < 2 || line[1] == '\t' ||
+					 line[1] == '\r' || line[1] == '\n')) {
+				line++;
+				len--;
+			}
 		}
 		emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK,
-- 
2.17.0.395.g6a618d6010f.dirty



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 17/18] branch-diff: add a man page
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
                   ` (15 preceding siblings ...)
  2018-05-03 15:31 ` [PATCH 16/18] branch-diff --dual-color: work around bogus white-space warning Johannes Schindelin
@ 2018-05-03 15:31 ` Johannes Schindelin
  2018-05-04  3:27   ` Eric Sunshine
  2018-05-03 15:31 ` [PATCH 18/18] completion: support branch-diff Johannes Schindelin
                   ` (4 subsequent siblings)
  21 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 15:31 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

This is a heavily butchered version of the README written by Thomas
Rast and Thomas Gummerer, lifted from https://github.com/trast/tbdiff.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/git-branch-diff.txt | 239 ++++++++++++++++++++++++++++++
 1 file changed, 239 insertions(+)
 create mode 100644 Documentation/git-branch-diff.txt

diff --git a/Documentation/git-branch-diff.txt b/Documentation/git-branch-diff.txt
new file mode 100644
index 00000000000..b841586735c
--- /dev/null
+++ b/Documentation/git-branch-diff.txt
@@ -0,0 +1,239 @@
+git-branch-diff(1)
+==================
+
+NAME
+----
+git-branch-diff - Compare two versions of a branch
+
+SYNOPSIS
+--------
+[verse]
+'git branch-diff' [--color=[<when>]] [--no-color] [<diff-options>]
+	[--dual-color] [--no-patches] [--creation-weight=<weight>]
+	( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
+
+DESCRIPTION
+-----------
+
+This command shows the differences between two versions of a patch
+series, or more generally, two commit ranges (ignoring merges).
+
+To that end, it first finds pairs of commits from both commit ranges
+that correspond with each other. Two commits are said to correspond when
+the diff between their patches (i.e. the author information, the commit
+message and the commit diff) is reasonably small compared to the
+patches' size. See ``Algorithm` below for details.
+
+Finally, the list of matching commits is shown in the order of the
+second commit range, with unmatched commits being inserted just after
+all of their ancestors have been shown.
+
+
+OPTIONS
+-------
+--no-patches::
+	Suppress the diffs between commit pairs that were deemed to
+	correspond; only show the pairings.
+
+--dual-color::
+	When the commit diffs differ, recreate the original diffs'
+	coloring, and add outer -/+ diff markers with the *background*
+	being red/green to make it easier to see e.g. when there was a
+	change in what exact lines were added.
+
+--creation-weight=<factor>::
+	Set the creation/deletion cost fudge factor to `<factor>`.
+	Defaults to 0.6. Try a larger value if `git branch-diff`
+	erroneously considers a large change a total rewrite (deletion
+	of one commit and addition of another), and a smaller one in
+	the reverse case. See the ``Algorithm`` section below for an
+	explanation why this is needed.
+
+<range1> <range2>::
+	Compare the commits specified by the two ranges, where
+	`<range1>` is considered an older version of `<range2>`.
+
+<rev1>...<rev2>::
+	Equivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.
+
+<base> <rev1> <rev2>::
+	Equivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.
+	Note that `<base>` does not need to be the exact branch point
+	of the branches. Example: after rebasing a branch `my-topic`,
+	`git branch-diff my-topic@{u} my-topic@{1} my-topic` would
+	show the differences introduced by the rebase.
+
+`git branch-diff` also accepts the regular diff options (see
+linkgit:git-diff[1]), most notably the `--color=[<when>]` and
+`--no-color` options. These options are used when generating the "diff
+between patches", i.e. to compare the author, commit message and diff of
+corresponding old/new commits. There is currently no means to tweak the
+diff options passed to `git log` when generating those patches.
+
+
+CONFIGURATION
+-------------
+This command uses the `diff.color.*` and `pager.branch-diff` settings
+(the latter is on by default).
+See linkgit:git-config[1].
+
+
+Examples
+--------
+
+When a rebase required merge conflicts to be resolved, compare the changes
+introduced by the rebase directly afterwards using:
+
+------------
+$ git branch-diff @{u} @{1} @
+------------
+
+
+A typical output of `git branch-diff` would look like this:
+
+------------
+-:  ------- > 1:  0ddba11 Prepare for the inevitable!
+1:  c0debee = 2:  cab005e Add a helpful message at the start
+2:  f00dbal ! 3:  decafe1 Describe a bug
+    @@ -1,3 +1,3 @@
+     Author: A U Thor <author@example.com>
+
+    -TODO: Describe a bug
+    +Describe a bug
+    @@ -324,5 +324,6
+      This is expected.
+
+    -+What is unexpected is that it will also crash.
+    ++Unexpectedly, it also crashes. This is a bug, and the jury is
+    ++still out there how to fix it best. See ticket #314 for details.
+
+      Contact
+3:  bedead < -:  ------- TO-UNDO
+------------
+
+In this example, there are 3 old and 3 new commits, where the developer
+removed the 3rd, added a new one before the first two, and modified the
+commit message of the 2nd commit as well its diff.
+
+When the output goes to a terminal, it is color-coded by default, just
+like regular `git diff`'s output. In addition, the first line (adding a
+commit) is green, the last line (deleting a commit) is red, the second
+line (with a perfect match) is yellow like the commit header of `git
+show`'s output, and the third line colors the old commit red, the new
+one green and the rest like `git show`'s commit header.
+
+The color-coded diff is actually a bit hard to read, though, as it
+colors the entire lines red or green. The line that added "What is
+unexpected" in the old commit, for example, is completely red, even if
+the intent of the old commit was to add something.
+
+To help with that, use the `--dual-color` mode. In this mode, the diff
+of diffs will retain the original diff colors, and prefix the lines with
+-/+ markers that have their *background* red or green, to make it more
+obvious that they describe how the diff itself changed.
+
+
+Algorithm
+---------
+
+The general idea is this: we generate a cost matrix between the commits
+in both commit ranges, then solve the least-cost assignment.
+
+To avoid false positives (e.g. when a patch has been removed, and an
+unrelated patch has been added between two iterations of the same patch
+series), the cost matrix is extended to allow for that, by adding
+fixed-cost entries for wholesale deletes/adds.
+
+Example: let commits `1--2` be the first iteration of a patch series and
+`A--C` the second iteration. Let's assume that `A` is a cherry-pick of
+`2,` and `C` is a cherry-pick of `1` but with a small modification (say,
+a fixed typo). Visualize the commits as a bipartite graph:
+
+------------
+    1            A
+
+    2            B
+
+		 C
+------------
+
+We are looking for a "best" explanation of the new series in terms of
+the old one. We can represent an "explanation" as an edge in the graph:
+
+
+------------
+    1            A
+	       /
+    2 --------'  B
+
+		 C
+------------
+
+This explanation comes for "free" because there was no change. Similarly
+`C` could be explained using `1`, but that comes at some cost c>0
+because of the modification:
+
+------------
+    1 ----.      A
+	  |    /
+    2 ----+---'  B
+	  |
+	  `----- C
+	  c>0
+------------
+
+In mathematical terms, what we are looking for is some sort of a minimum
+cost bipartite matching; `1` is matched to `C` at some cost, etc. The
+underlying graph is in fact a complete bipartite graph; the cost we
+associate with every edge is the size of the diff between the two
+commits' patches. To explain also new commits, we introduce dummy nodes
+on both sides:
+
+------------
+    1 ----.      A
+	  |    /
+    2 ----+---'  B
+	  |
+    o     `----- C
+	  c>0
+    o            o
+
+    o            o
+------------
+
+The cost of an edge `o--C` is the size of `C`'s diff, modified by a
+fudge factor that should be smaller than 1.0. The cost of an edge `o--o`
+is free. The fudge factor is necessary because even if `1` and `C` have
+nothing in common, they may still share a few empty lines and such,
+possibly making the assignment `1--C`, `o--o` slightly cheaper than
+`1--o`, `o--C` even if `1` and `C` have nothing in common. With the
+fudge factor we require a much larger common part to consider patches as
+corresponding.
+
+The overall time needed to compute this algorithm is the time needed to
+compute n+m commit diffs and then n*m diffs of patches, plus the time
+needed to compute the least-cost assigment between n and m diffs. Git
+uses an implementation of the Jonker-Volgenant algorithm to solve the
+assignment problem, which has cubic runtime complexity. The matching
+found in this case will look like this:
+
+------------
+    1 ----.      A
+	  |    /
+    2 ----+---'  B
+       .--+-----'
+    o -'  `----- C
+	  c>0
+    o ---------- o
+
+    o ---------- o
+------------
+
+
+SEE ALSO
+--------
+linkgit:git-log[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
-- 
2.17.0.395.g6a618d6010f.dirty



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 18/18] completion: support branch-diff
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
                   ` (16 preceding siblings ...)
  2018-05-03 15:31 ` [PATCH 17/18] branch-diff: add a man page Johannes Schindelin
@ 2018-05-03 15:31 ` Johannes Schindelin
  2018-05-03 18:05 ` [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Ævar Arnfjörð Bjarmason
                   ` (3 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 15:31 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

Tab completion of `branch-diff` is very convenient, especially given
that the revision arguments that need to be passed to `git branch-diff`
are typically more complex than, say, your grandfather's `git log`
arguments.

Without this patch, we would only complete the `branch-diff` part but
not the options and other arguments.

This of itself may already be slightly disruptive for well-trained
fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to
`git branch origin/master`, as we now no longer automatically append a
space after completing `git branch`: this is now ambiguous.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 contrib/completion/git-completion.bash | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 01dd9ff07a2..45addd525ac 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1496,6 +1496,24 @@ _git_format_patch ()
 	__git_complete_revlist
 }
 
+__git_branch_diff_options="
+	--no-patches --creation-weight= --dual-color
+"
+
+_git_branch_diff ()
+{
+	case "$cur" in
+	--*)
+		__gitcomp "
+			$__git_branch_diff_options
+			$__git_diff_common_options
+			"
+		return
+		;;
+	esac
+	__git_complete_revlist
+}
+
 _git_fsck ()
 {
 	case "$cur" in
-- 
2.17.0.395.g6a618d6010f.dirty

^ permalink raw reply related	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-03 15:30 ` [PATCH 02/18] Add a new builtin: branch-diff Johannes Schindelin
@ 2018-05-03 16:10   ` Ramsay Jones
  2018-05-03 20:25     ` Johannes Schindelin
  2018-05-03 16:41   ` Duy Nguyen
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 387+ messages in thread
From: Ramsay Jones @ 2018-05-03 16:10 UTC (permalink / raw)
  To: Johannes Schindelin, git
  Cc: Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason



On 03/05/18 16:30, Johannes Schindelin wrote:
> This builtin does not do a whole lot so far, apart from showing a usage
> that is oddly similar to that of `git tbdiff`. And for a good reason:
> the next commits will turn `branch-diff` into a full-blown replacement
> for `tbdiff`.
> 
> At this point, we ignore tbdiff's color options, as they will all be
> implemented later and require some patches to the diff machinery.
> 
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  .gitignore            |  1 +
>  Makefile              |  1 +
>  builtin.h             |  1 +
>  builtin/branch-diff.c | 40 ++++++++++++++++++++++++++++++++++++++++
>  command-list.txt      |  1 +
>  git.c                 |  1 +
>  6 files changed, 45 insertions(+)
>  create mode 100644 builtin/branch-diff.c
> 
> diff --git a/.gitignore b/.gitignore
> index 833ef3b0b78..1346a64492f 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -20,6 +20,7 @@
>  /git-bisect--helper
>  /git-blame
>  /git-branch
> +/git-branch-diff
>  /git-bundle
>  /git-cat-file
>  /git-check-attr
> diff --git a/Makefile b/Makefile
> index 96f2e76a904..9b1984776d8 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -953,6 +953,7 @@ BUILTIN_OBJS += builtin/archive.o
>  BUILTIN_OBJS += builtin/bisect--helper.o
>  BUILTIN_OBJS += builtin/blame.o
>  BUILTIN_OBJS += builtin/branch.o
> +BUILTIN_OBJS += builtin/branch-diff.o
>  BUILTIN_OBJS += builtin/bundle.o
>  BUILTIN_OBJS += builtin/cat-file.o
>  BUILTIN_OBJS += builtin/check-attr.o
> diff --git a/builtin.h b/builtin.h
> index 42378f3aa47..e1c4d2a529a 100644
> --- a/builtin.h
> +++ b/builtin.h
> @@ -135,6 +135,7 @@ extern int cmd_archive(int argc, const char **argv, const char *prefix);
>  extern int cmd_bisect__helper(int argc, const char **argv, const char *prefix);
>  extern int cmd_blame(int argc, const char **argv, const char *prefix);
>  extern int cmd_branch(int argc, const char **argv, const char *prefix);
> +extern int cmd_branch_diff(int argc, const char **argv, const char *prefix);
>  extern int cmd_bundle(int argc, const char **argv, const char *prefix);
>  extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
>  extern int cmd_checkout(int argc, const char **argv, const char *prefix);
> diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
> new file mode 100644
> index 00000000000..97266cd326d
> --- /dev/null
> +++ b/builtin/branch-diff.c
> @@ -0,0 +1,40 @@
> +#include "cache.h"
> +#include "parse-options.h"
> +
> +static const char * const builtin_branch_diff_usage[] = {
> +	N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),

s/rebase--helper/branch-diff/

ATB,
Ramsay Jones


^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 03/18] branch-diff: first rudimentary implementation
  2018-05-03 15:30 ` [PATCH 03/18] branch-diff: first rudimentary implementation Johannes Schindelin
@ 2018-05-03 16:30   ` Ramsay Jones
  2018-05-03 20:44     ` Johannes Schindelin
  2018-05-03 17:06   ` Stefan Beller
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 387+ messages in thread
From: Ramsay Jones @ 2018-05-03 16:30 UTC (permalink / raw)
  To: Johannes Schindelin, git
  Cc: Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason



On 03/05/18 16:30, Johannes Schindelin wrote:
> At this stage, `git branch-diff` can determine corresponding commits of
> two related commit ranges. This makes use of the recently introduced
> implementation of the Hungarian algorithm.
> 
> The core of this patch is a straight port of the ideas of tbdiff, the
> seemingly dormant project at https://github.com/trast/tbdiff.
> 
> The output does not at all match `tbdiff`'s output yet, as this patch
> really concentrates on getting the patch matching part right.
> 
> Note: due to differences in the diff algorithm (`tbdiff` uses the
> Pythong module `difflib`, Git uses its xdiff fork), the cost matrix
> calculated by `branch-diff` is different (but very similar) to the one
> calculated by `tbdiff`. Therefore, it is possible that they find
> different matching commits in corner cases (e.g. when a patch was split
> into two patches of roughly equal length).
> 
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  builtin/branch-diff.c | 337 +++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 334 insertions(+), 3 deletions(-)
> 
> diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
> index 97266cd326d..02dc06a57ca 100644
> --- a/builtin/branch-diff.c
> +++ b/builtin/branch-diff.c
> @@ -1,13 +1,17 @@
>  #include "cache.h"
>  #include "parse-options.h"
> +#include "string-list.h"
> +#include "run-command.h"
> +#include "argv-array.h"
> +#include "hashmap.h"
> +#include "xdiff-interface.h"
> +#include "hungarian.h"
>  
>  static const char * const builtin_branch_diff_usage[] = {
>  	N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),
>  	NULL
>  };
>  
> -#define COLOR_DUAL_MODE 2
> -

This #define was introduced in the previous patch, without being
used in that patch, and is now deleted here.

ATB,
Ramsay Jones

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-03 15:30 ` [PATCH 02/18] Add a new builtin: branch-diff Johannes Schindelin
  2018-05-03 16:10   ` Ramsay Jones
@ 2018-05-03 16:41   ` Duy Nguyen
  2018-05-03 20:30     ` Johannes Schindelin
  2018-05-03 16:43   ` Stefan Beller
  2018-05-04  2:35   ` Eric Sunshine
  3 siblings, 1 reply; 387+ messages in thread
From: Duy Nguyen @ 2018-05-03 16:41 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git Mailing List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

On Thu, May 3, 2018 at 5:30 PM, Johannes Schindelin
<johannes.schindelin@gmx.de> wrote:
> diff --git a/command-list.txt b/command-list.txt
> index a1fad28fd82..c89ac8f417f 100644
> --- a/command-list.txt
> +++ b/command-list.txt
> @@ -19,6 +19,7 @@ git-archive                             mainporcelain
>  git-bisect                              mainporcelain           info
>  git-blame                               ancillaryinterrogators
>  git-branch                              mainporcelain           history
> +git-branch-diff                         mainporcelain           info

Making it part of "git help" with the info keywords at this stage may
be premature. "git help" is about _common_ commands and we don't know
(yet) how popular this will be.

(I'm not complaining though, I almost wrote "what witchcraft is this
and where can I get it" when I see branch-diff output mentioned in
AEvar's mail)
-- 
Duy

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-03 15:30 ` [PATCH 02/18] Add a new builtin: branch-diff Johannes Schindelin
  2018-05-03 16:10   ` Ramsay Jones
  2018-05-03 16:41   ` Duy Nguyen
@ 2018-05-03 16:43   ` Stefan Beller
  2018-05-03 20:42     ` Johannes Schindelin
  2018-05-04  2:35   ` Eric Sunshine
  3 siblings, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-05-03 16:43 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Johannes,

On Thu, May 3, 2018 at 8:30 AM, Johannes Schindelin
<johannes.schindelin@gmx.de> wrote:
> This builtin does not do a whole lot so far, apart from showing a usage
> that is oddly similar to that of `git tbdiff`. And for a good reason:
> the next commits will turn `branch-diff` into a full-blown replacement
> for `tbdiff`.

While I appreciate the 1:1 re-implementation, I'll comment as if this
was a newly invented tool, questioning design choices. They are probably
chosen pretty well, and fudge facotrs as below are at tweaked to a reasonable
factor, but I'll try to look with fresh eyes.

>
> At this point, we ignore tbdiff's color options, as they will all be
> implemented later and require some patches to the diff machinery.

Speaking of colors, for origin/sb/blame-color Junio hinted at re-using
cyan for "uninteresting" parts to deliver a consistent color scheme for
Git. Eventually he dreams of having 2 layers of indirection IIUC, with
    "uninteresting" -> cyan
    "repeated lines in blame" -> uninteresting

Maybe we can fit the coloring of this tool in this scheme, too?

> +       double creation_weight = 0.6;

I wondered if we use doubles in Gits code base at all,
and I found

khash.h:59:static const double __ac_HASH_UPPER = 0.77;
pack-bitmap-write.c:248:        static const double
REUSE_BITMAP_THRESHOLD = 0.2;
pack-bitmap.c:751:      static const double REUSE_PERCENT = 0.9;

all other occurrences of `git grep double` are mentioning it in other
contexts (e.g. "double linked list" or comments).

When implementing diff heuristics in 433860f3d0b (diff: improve
positioning of add/delete blocks in diffs, 2016-09-05), Michael broke
it down to fixed integers instead of floating point.

Do we need to dynamic of a floating point, or would a rather small range
suffice here? (Also see rename detection settings, that take percents as
integers)

Thanks,
Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 11/18] branch-diff: add tests
  2018-05-03 15:30 ` [PATCH 11/18] branch-diff: add tests Johannes Schindelin
@ 2018-05-03 16:56   ` Ævar Arnfjörð Bjarmason
  2018-05-03 21:03     ` Johannes Schindelin
  2018-05-03 17:11   ` Stefan Beller
  2018-05-03 23:27   ` Philip Oakley
  2 siblings, 1 reply; 387+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-03 16:56 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git, Thomas Rast, Junio C Hamano, Thomas Gummerer


On Thu, May 03 2018, Johannes Schindelin wrote:

> *before* the existing emtpy line. And apparently xdiff picks a different

s/emtpy/empty/

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 03/18] branch-diff: first rudimentary implementation
  2018-05-03 15:30 ` [PATCH 03/18] branch-diff: first rudimentary implementation Johannes Schindelin
  2018-05-03 16:30   ` Ramsay Jones
@ 2018-05-03 17:06   ` Stefan Beller
  2018-05-03 21:01     ` Johannes Schindelin
  2018-05-04  2:35   ` Eric Sunshine
  2018-05-04  4:56   ` Junio C Hamano
  3 siblings, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-05-03 17:06 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

On Thu, May 3, 2018 at 8:30 AM, Johannes Schindelin
<johannes.schindelin@gmx.de> wrote:

> Note: due to differences in the diff algorithm (`tbdiff` uses the
> Pythong module `difflib`, Git uses its xdiff fork), the cost matrix
> calculated by `branch-diff` is different (but very similar) to the one
> calculated by `tbdiff`. Therefore, it is possible that they find
> different matching commits in corner cases (e.g. when a patch was split
> into two patches of roughly equal length).

Does that mean, we may want to tweak the underlying diff parameters for
this special use case eventually?

>
> -#define COLOR_DUAL_MODE 2
> -

Leave this out in the first patch?

> @@ -19,6 +23,279 @@ static int parse_creation_weight(const struct option *opt, const char *arg,
>         return 0;
>  }
>
> +struct patch_util {
> +       /* For the search for an exact match */
> +       struct hashmap_entry e;
> +       const char *diff, *patch;
> +
> +       int i;
> +       int diffsize;
> +       size_t diff_offset;
> +       /* the index of the matching item in the other branch, or -1 */
> +       int matching;
> +       struct object_id oid;
> +};
> +
> +/*
> + * Reads the patches into a string list, with the `util` field being populated
> + * as struct object_id (will need to be free()d).
> + */
> +static int read_patches(const char *range, struct string_list *list)
> +{
> +       struct child_process cp = CHILD_PROCESS_INIT;
> +       FILE *in;
> +       struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;
> +       struct patch_util *util = NULL;
> +       int in_header = 1;
> +
> +       argv_array_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
> +                       "--reverse", "--date-order", "--decorate=no",
> +                       "--no-abbrev-commit", range,
> +                       NULL);
> +       cp.out = -1;
> +       cp.no_stdin = 1;
> +       cp.git_cmd = 1;
> +
> +       if (start_command(&cp))
> +               return error_errno(_("could not start `log`"));
> +       in = fdopen(cp.out, "r");
> +       if (!in) {
> +               error_errno(_("could not read `log` output"));
> +               finish_command(&cp);
> +               return -1;
> +       }

With the implementation of --color-moved, there is an option
to buffer all diff output in memory e6e045f8031 (diff.c: buffer
all output if asked to, 2017-06-29), so I posit that running this
diff in-core may be "not too hard". Famous last words.

In addition to that patch, we'd have to buffer commit messages
and buffer multiple commits, as that only buffers a diff of a single
commit.

The benefit would be no invocation of new processes, letting us
do more in core. This would allow for tweaking revision walking
internally, e.g. passing of options to this command such as rename
detection factors, can be passed through easily without the need
of translating it back to the command line.
Let's read on.

> +
> +               if (starts_with(line.buf, "diff --git")) {

When using the internal buffers, you would not need to
string compare, but could just check for the
DIFF_SYMBOL_HEADER.

> +               } else if (starts_with(line.buf, "@@ "))
> +                       strbuf_addstr(&buf, "@@");

So we omit line information for hunks. Makes sense,
though then we could also skip the "index ..." lines?

Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 11/18] branch-diff: add tests
  2018-05-03 15:30 ` [PATCH 11/18] branch-diff: add tests Johannes Schindelin
  2018-05-03 16:56   ` Ævar Arnfjörð Bjarmason
@ 2018-05-03 17:11   ` Stefan Beller
  2018-05-03 21:05     ` Johannes Schindelin
  2018-05-03 23:27   ` Philip Oakley
  2 siblings, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-05-03 17:11 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Thomas Rast, Junio C Hamano, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

On Thu, May 3, 2018 at 8:30 AM, Johannes Schindelin
<johannes.schindelin@gmx.de> wrote:
> From: Thomas Rast <tr@thomasrast.ch>
>
> These are essentially lifted from https://github.com/trast/tbdiff, with
> light touch-ups to account for the new command name.
>
> Apart from renaming `tbdiff` to `branch-diff`, only one test case needed
> to be adjusted: 11 - 'changed message'.
>
> The underlying reason it had to be adjusted is that diff generation is
> sometimes ambiguous. In this case, a comment line and an empty line are
> added, but it is ambiguous whether they were added after the existing
> empty line, or whether an empty line and the comment line are added
> *before* the existing emtpy line. And apparently xdiff picks a different
> option here than Python's difflib.

I think that is the fallout of the diff heuristics. If you are keen on
a 1:1 port, you can disable the diff sliding heuristics and it should produce
the same diff with trailing new lines.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
                   ` (17 preceding siblings ...)
  2018-05-03 15:31 ` [PATCH 18/18] completion: support branch-diff Johannes Schindelin
@ 2018-05-03 18:05 ` Ævar Arnfjörð Bjarmason
  2018-05-03 21:07   ` Johannes Schindelin
  2018-05-03 21:50   ` Jacob Keller
  2018-05-04  5:24 ` Junio C Hamano
                   ` (2 subsequent siblings)
  21 siblings, 2 replies; 387+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-03 18:05 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer


On Thu, May 03 2018, Johannes Schindelin wrote:

> 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.
>
> 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.

I've been using branch-diff and haven't found issues with it yet, it
works like tbdiff but better. Faster, uses the same diff as git
(better), and spews to the pager by default.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-03 16:10   ` Ramsay Jones
@ 2018-05-03 20:25     ` Johannes Schindelin
  2018-05-03 23:20       ` Ramsay Jones
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 20:25 UTC (permalink / raw)
  To: Ramsay Jones
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Ramsay,

On Thu, 3 May 2018, Ramsay Jones wrote:

> On 03/05/18 16:30, Johannes Schindelin wrote:
> > This builtin does not do a whole lot so far, apart from showing a usage
> > that is oddly similar to that of `git tbdiff`. And for a good reason:
> > the next commits will turn `branch-diff` into a full-blown replacement
> > for `tbdiff`.
> > 
> > At this point, we ignore tbdiff's color options, as they will all be
> > implemented later and require some patches to the diff machinery.
> > 
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> >  .gitignore            |  1 +
> >  Makefile              |  1 +
> >  builtin.h             |  1 +
> >  builtin/branch-diff.c | 40 ++++++++++++++++++++++++++++++++++++++++
> >  command-list.txt      |  1 +
> >  git.c                 |  1 +
> >  6 files changed, 45 insertions(+)
> >  create mode 100644 builtin/branch-diff.c
> > 
> > diff --git a/.gitignore b/.gitignore
> > index 833ef3b0b78..1346a64492f 100644
> > --- a/.gitignore
> > +++ b/.gitignore
> > @@ -20,6 +20,7 @@
> >  /git-bisect--helper
> >  /git-blame
> >  /git-branch
> > +/git-branch-diff
> >  /git-bundle
> >  /git-cat-file
> >  /git-check-attr
> > diff --git a/Makefile b/Makefile
> > index 96f2e76a904..9b1984776d8 100644
> > --- a/Makefile
> > +++ b/Makefile
> > @@ -953,6 +953,7 @@ BUILTIN_OBJS += builtin/archive.o
> >  BUILTIN_OBJS += builtin/bisect--helper.o
> >  BUILTIN_OBJS += builtin/blame.o
> >  BUILTIN_OBJS += builtin/branch.o
> > +BUILTIN_OBJS += builtin/branch-diff.o
> >  BUILTIN_OBJS += builtin/bundle.o
> >  BUILTIN_OBJS += builtin/cat-file.o
> >  BUILTIN_OBJS += builtin/check-attr.o
> > diff --git a/builtin.h b/builtin.h
> > index 42378f3aa47..e1c4d2a529a 100644
> > --- a/builtin.h
> > +++ b/builtin.h
> > @@ -135,6 +135,7 @@ extern int cmd_archive(int argc, const char **argv, const char *prefix);
> >  extern int cmd_bisect__helper(int argc, const char **argv, const char *prefix);
> >  extern int cmd_blame(int argc, const char **argv, const char *prefix);
> >  extern int cmd_branch(int argc, const char **argv, const char *prefix);
> > +extern int cmd_branch_diff(int argc, const char **argv, const char *prefix);
> >  extern int cmd_bundle(int argc, const char **argv, const char *prefix);
> >  extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
> >  extern int cmd_checkout(int argc, const char **argv, const char *prefix);
> > diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
> > new file mode 100644
> > index 00000000000..97266cd326d
> > --- /dev/null
> > +++ b/builtin/branch-diff.c
> > @@ -0,0 +1,40 @@
> > +#include "cache.h"
> > +#include "parse-options.h"
> > +
> > +static const char * const builtin_branch_diff_usage[] = {
> > +	N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),
> 
> s/rebase--helper/branch-diff/

Whoops!

BTW funny side note: when I saw that you replied, I instinctively thought
"oh no, I forgot to mark a function as `static`!" ;-)

Thank you for helping me improve the patches,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-03 16:41   ` Duy Nguyen
@ 2018-05-03 20:30     ` Johannes Schindelin
  2018-05-03 20:32       ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 20:30 UTC (permalink / raw)
  To: Duy Nguyen
  Cc: Git Mailing List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Duy,

On Thu, 3 May 2018, Duy Nguyen wrote:

> On Thu, May 3, 2018 at 5:30 PM, Johannes Schindelin
> <johannes.schindelin@gmx.de> wrote:
> > diff --git a/command-list.txt b/command-list.txt
> > index a1fad28fd82..c89ac8f417f 100644
> > --- a/command-list.txt
> > +++ b/command-list.txt
> > @@ -19,6 +19,7 @@ git-archive                             mainporcelain
> >  git-bisect                              mainporcelain           info
> >  git-blame                               ancillaryinterrogators
> >  git-branch                              mainporcelain           history
> > +git-branch-diff                         mainporcelain           info
> 
> Making it part of "git help" with the info keywords at this stage may
> be premature. "git help" is about _common_ commands and we don't know
> (yet) how popular this will be.

Makes sense. I removed the `mainporcelain` keyword locally.

> (I'm not complaining though, I almost wrote "what witchcraft is this
> and where can I get it" when I see branch-diff output mentioned in
> AEvar's mail)

Yeah, it is pretty neat.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-03 20:30     ` Johannes Schindelin
@ 2018-05-03 20:32       ` Johannes Schindelin
  2018-05-04  5:15         ` Duy Nguyen
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 20:32 UTC (permalink / raw)
  To: Duy Nguyen
  Cc: Git Mailing List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Duy,

On Thu, 3 May 2018, Johannes Schindelin wrote:

> On Thu, 3 May 2018, Duy Nguyen wrote:
> 
> > On Thu, May 3, 2018 at 5:30 PM, Johannes Schindelin
> > <johannes.schindelin@gmx.de> wrote:
> > > diff --git a/command-list.txt b/command-list.txt
> > > index a1fad28fd82..c89ac8f417f 100644
> > > --- a/command-list.txt
> > > +++ b/command-list.txt
> > > @@ -19,6 +19,7 @@ git-archive                             mainporcelain
> > >  git-bisect                              mainporcelain           info
> > >  git-blame                               ancillaryinterrogators
> > >  git-branch                              mainporcelain           history
> > > +git-branch-diff                         mainporcelain           info
> > 
> > Making it part of "git help" with the info keywords at this stage may
> > be premature. "git help" is about _common_ commands and we don't know
> > (yet) how popular this will be.
> 
> Makes sense. I removed the `mainporcelain` keyword locally.

On second thought, I *think* you meant to imply that I should remove that
line altogether. Will do that now.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-03 16:43   ` Stefan Beller
@ 2018-05-03 20:42     ` Johannes Schindelin
  2018-05-03 21:12       ` Stefan Beller
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 20:42 UTC (permalink / raw)
  To: Stefan Beller
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Stefan,

On Thu, 3 May 2018, Stefan Beller wrote:

> On Thu, May 3, 2018 at 8:30 AM, Johannes Schindelin
> <johannes.schindelin@gmx.de> wrote:
> > This builtin does not do a whole lot so far, apart from showing a
> > usage that is oddly similar to that of `git tbdiff`. And for a good
> > reason: the next commits will turn `branch-diff` into a full-blown
> > replacement for `tbdiff`.
> 
> While I appreciate the 1:1 re-implementation, I'll comment as if this
> was a newly invented tool, questioning design choices. They are probably
> chosen pretty well, and fudge facotrs as below are at tweaked to a
> reasonable factor, but I'll try to look with fresh eyes.

Absolutely. While tbdiff got some testing over time, it has definitely not
gotten as much exposure as branch-diff hopefully will.

BTW I chose a different command name on purpose, so that we are free to
change the design and not harm existing tbdiff users.

> > At this point, we ignore tbdiff's color options, as they will all be
> > implemented later and require some patches to the diff machinery.
> 
> Speaking of colors, for origin/sb/blame-color Junio hinted at re-using
> cyan for "uninteresting" parts to deliver a consistent color scheme for
> Git. Eventually he dreams of having 2 layers of indirection IIUC, with
>     "uninteresting" -> cyan
>     "repeated lines in blame" -> uninteresting
> 
> Maybe we can fit the coloring of this tool in this scheme, too?

Sure. So you mean I should use cyan for... what part of the colored
output? ;-)

> > +       double creation_weight = 0.6;
> 
> I wondered if we use doubles in Gits code base at all,
> and I found
> 
> khash.h:59:static const double __ac_HASH_UPPER = 0.77;
> pack-bitmap-write.c:248:        static const double
> REUSE_BITMAP_THRESHOLD = 0.2;
> pack-bitmap.c:751:      static const double REUSE_PERCENT = 0.9;
> 
> all other occurrences of `git grep double` are mentioning it in other
> contexts (e.g. "double linked list" or comments).
> 
> When implementing diff heuristics in 433860f3d0b (diff: improve
> positioning of add/delete blocks in diffs, 2016-09-05), Michael broke
> it down to fixed integers instead of floating point.
> 
> Do we need to dynamic of a floating point, or would a rather small range
> suffice here? (Also see rename detection settings, that take percents as
> integers)

I guess you are right, and we do not need floats. It was just very, very
convenient to do that instead of using integers because

- I already had the Jonker-Volgenant implementation "lying around" from my
  previous life as an image processing expert, using doubles (but it was
  in Java, not in C, so I quickly converted it for branch-diff).

- I was actually not paying attention whether divisions are a thing in the
  algorithm. From a cursory glance, it would appear that we are never
  dividing in hungarian.c, so theoretically integers should be fine.

- using doubles neatly side-steps the overflow problem. If I use integers
  instead, I always will have to worry what to do if, say, adding
  `INT_MAX` to `INT_MAX`.

I am particularly worried about that last thing: it could easily lead to
incorrect results if we blindly, say, pretend that `INT_MAX + INT_MAX ==
INT_MAX` for the purpose of avoiding overflows.

If, however, I misunderstood and you are only concerned about using
*double-precision* floating point numbers, and would suggest using `float`
typed variables instead, that would be totally cool with me.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 03/18] branch-diff: first rudimentary implementation
  2018-05-03 16:30   ` Ramsay Jones
@ 2018-05-03 20:44     ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 20:44 UTC (permalink / raw)
  To: Ramsay Jones
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Ramsay,

On Thu, 3 May 2018, Ramsay Jones wrote:

> On 03/05/18 16:30, Johannes Schindelin wrote:
> > At this stage, `git branch-diff` can determine corresponding commits of
> > two related commit ranges. This makes use of the recently introduced
> > implementation of the Hungarian algorithm.
> > 
> > The core of this patch is a straight port of the ideas of tbdiff, the
> > seemingly dormant project at https://github.com/trast/tbdiff.
> > 
> > The output does not at all match `tbdiff`'s output yet, as this patch
> > really concentrates on getting the patch matching part right.
> > 
> > Note: due to differences in the diff algorithm (`tbdiff` uses the
> > Pythong module `difflib`, Git uses its xdiff fork), the cost matrix
> > calculated by `branch-diff` is different (but very similar) to the one
> > calculated by `tbdiff`. Therefore, it is possible that they find
> > different matching commits in corner cases (e.g. when a patch was split
> > into two patches of roughly equal length).
> > 
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> >  builtin/branch-diff.c | 337 +++++++++++++++++++++++++++++++++++++++++-
> >  1 file changed, 334 insertions(+), 3 deletions(-)
> > 
> > diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
> > index 97266cd326d..02dc06a57ca 100644
> > --- a/builtin/branch-diff.c
> > +++ b/builtin/branch-diff.c
> > @@ -1,13 +1,17 @@
> >  #include "cache.h"
> >  #include "parse-options.h"
> > +#include "string-list.h"
> > +#include "run-command.h"
> > +#include "argv-array.h"
> > +#include "hashmap.h"
> > +#include "xdiff-interface.h"
> > +#include "hungarian.h"
> >  
> >  static const char * const builtin_branch_diff_usage[] = {
> >  	N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),
> >  	NULL
> >  };
> >  
> > -#define COLOR_DUAL_MODE 2
> > -
> 
> This #define was introduced in the previous patch, without being
> used in that patch, and is now deleted here.

Darn, darn, darn. You know, this macro simply won't die. I tried to kill
it off but it keeps showing its ugly head everywhere.

I *think* I finally killed it for good.

Thanks!
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 03/18] branch-diff: first rudimentary implementation
  2018-05-03 17:06   ` Stefan Beller
@ 2018-05-03 21:01     ` Johannes Schindelin
  2018-05-03 21:19       ` Stefan Beller
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 21:01 UTC (permalink / raw)
  To: Stefan Beller
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Stefan,

On Thu, 3 May 2018, Stefan Beller wrote:

> On Thu, May 3, 2018 at 8:30 AM, Johannes Schindelin
> <johannes.schindelin@gmx.de> wrote:
> 
> > Note: due to differences in the diff algorithm (`tbdiff` uses the
> > Pythong module `difflib`, Git uses its xdiff fork), the cost matrix
> > calculated by `branch-diff` is different (but very similar) to the one
> > calculated by `tbdiff`. Therefore, it is possible that they find
> > different matching commits in corner cases (e.g. when a patch was
> > split into two patches of roughly equal length).
> 
> Does that mean, we may want to tweak the underlying diff parameters for
> this special use case eventually?

I don't think that will be necessary. Generating diffs is an ambiguous
business, after all, and we just have to live with the consequence that it
might even be possible that the cost is non-symmetric, i.e. that the
length (i.e. line count) of the diff is different depending whether we
compare old patch to new patch vs new patch to old patch.

If the result changes due to these vagaries, it means that there is no
single one good answer to the question which old/new commits form a pair.
I would expect that only to happen if a commit with a lengthy diff is cut
into two commits whose diffs have roughly equal lengths (so that the
difference of the commit message won't matter that much).

> > -#define COLOR_DUAL_MODE 2
> > -
> 
> Leave this out in the first patch?

Yep, as Ramsay said.

> > @@ -19,6 +23,279 @@ static int parse_creation_weight(const struct option *opt, const char *arg,
> >         return 0;
> >  }
> >
> > +struct patch_util {
> > +       /* For the search for an exact match */
> > +       struct hashmap_entry e;
> > +       const char *diff, *patch;
> > +
> > +       int i;
> > +       int diffsize;
> > +       size_t diff_offset;
> > +       /* the index of the matching item in the other branch, or -1 */
> > +       int matching;
> > +       struct object_id oid;
> > +};
> > +
> > +/*
> > + * Reads the patches into a string list, with the `util` field being populated
> > + * as struct object_id (will need to be free()d).
> > + */
> > +static int read_patches(const char *range, struct string_list *list)
> > +{
> > +       struct child_process cp = CHILD_PROCESS_INIT;
> > +       FILE *in;
> > +       struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;
> > +       struct patch_util *util = NULL;
> > +       int in_header = 1;
> > +
> > +       argv_array_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
> > +                       "--reverse", "--date-order", "--decorate=no",
> > +                       "--no-abbrev-commit", range,
> > +                       NULL);
> > +       cp.out = -1;
> > +       cp.no_stdin = 1;
> > +       cp.git_cmd = 1;
> > +
> > +       if (start_command(&cp))
> > +               return error_errno(_("could not start `log`"));
> > +       in = fdopen(cp.out, "r");
> > +       if (!in) {
> > +               error_errno(_("could not read `log` output"));
> > +               finish_command(&cp);
> > +               return -1;
> > +       }
> 
> With the implementation of --color-moved, there is an option
> to buffer all diff output in memory e6e045f8031 (diff.c: buffer
> all output if asked to, 2017-06-29), so I posit that running this
> diff in-core may be "not too hard". Famous last words.

True. I *did* stumble over emitted_symbols and thought that there might be
a way to leverage it, but I did not find any existing knob, and did not
want to interfere with any other user case of that feature (which we may
want to allow combining with branch-diff one day, afer all).

To be honest, the main reason I spawn here is that I did not want to be
bothered with resetting the commit flags after traversing the first commit
range. But I guess I was just too cheap and should really do it?

OTOH spawning here is a lot easier than not spawning, so maybe it would be
premature optimization?

> In addition to that patch, we'd have to buffer commit messages
> and buffer multiple commits, as that only buffers a diff of a single
> commit.

... and make sure that the moved-code logic (which is currently the only
user of emitted_symbols, correct?) would never be called at the same time
as we generate the diff.

> The benefit would be no invocation of new processes, letting us
> do more in core. This would allow for tweaking revision walking
> internally, e.g. passing of options to this command such as rename
> detection factors, can be passed through easily without the need
> of translating it back to the command line.

On the other hand, we can simply copy those options to the command-line
for `log`. Which might even be better, as e.g. `--format` changes global
state :-(

> > +
> > +               if (starts_with(line.buf, "diff --git")) {
> 
> When using the internal buffers, you would not need to
> string compare, but could just check for the
> DIFF_SYMBOL_HEADER.

True. Not sure whether the current way is *that* terrible, though, as the
`diff` line is meant for parsing by other commands, anyway.

> > +               } else if (starts_with(line.buf, "@@ "))
> > +                       strbuf_addstr(&buf, "@@");
> 
> So we omit line information for hunks. Makes sense,
> though then we could also skip the "index ..." lines?

And we do, in the next line:

> > +               else if (line.buf[0] && !starts_with(line.buf, "index "))

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 11/18] branch-diff: add tests
  2018-05-03 16:56   ` Ævar Arnfjörð Bjarmason
@ 2018-05-03 21:03     ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 21:03 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Thomas Rast, Junio C Hamano, Thomas Gummerer

[-- Attachment #1: Type: text/plain, Size: 261 bytes --]

Hi Ævar,

On Thu, 3 May 2018, Ævar Arnfjörð Bjarmason wrote:

> On Thu, May 03 2018, Johannes Schindelin wrote:
> 
> > *before* the existing emtpy line. And apparently xdiff picks a different
> 
> s/emtpy/empty/

Thanks for the spell check!
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 11/18] branch-diff: add tests
  2018-05-03 17:11   ` Stefan Beller
@ 2018-05-03 21:05     ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 21:05 UTC (permalink / raw)
  To: Stefan Beller
  Cc: git, Thomas Rast, Junio C Hamano, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Stefan,

On Thu, 3 May 2018, Stefan Beller wrote:

> On Thu, May 3, 2018 at 8:30 AM, Johannes Schindelin
> <johannes.schindelin@gmx.de> wrote:
> > From: Thomas Rast <tr@thomasrast.ch>
> >
> > These are essentially lifted from https://github.com/trast/tbdiff, with
> > light touch-ups to account for the new command name.
> >
> > Apart from renaming `tbdiff` to `branch-diff`, only one test case needed
> > to be adjusted: 11 - 'changed message'.
> >
> > The underlying reason it had to be adjusted is that diff generation is
> > sometimes ambiguous. In this case, a comment line and an empty line are
> > added, but it is ambiguous whether they were added after the existing
> > empty line, or whether an empty line and the comment line are added
> > *before* the existing emtpy line. And apparently xdiff picks a different
> > option here than Python's difflib.
> 
> I think that is the fallout of the diff heuristics. If you are keen on
> a 1:1 port, you can disable the diff sliding heuristics and it should
> produce the same diff with trailing new lines.

I am not keen on a 1:1 port. I am fine with having xdiff generate
different diffs than Python's difflib. That's par for the course when
relying on a not-quite-well-defined metric.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-03 18:05 ` [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Ævar Arnfjörð Bjarmason
@ 2018-05-03 21:07   ` Johannes Schindelin
  2018-05-03 21:50   ` Jacob Keller
  1 sibling, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 21:07 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer

[-- Attachment #1: Type: text/plain, Size: 1148 bytes --]

Hi Ævar,

On Thu, 3 May 2018, Ævar Arnfjörð Bjarmason wrote:

> On Thu, May 03 2018, Johannes Schindelin wrote:
> 
> > 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.
> >
> > 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.
> 
> I've been using branch-diff and haven't found issues with it yet, it
> works like tbdiff but better. Faster, uses the same diff as git
> (better), and spews to the pager by default.

Thanks for your enthusiasm!
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-03 20:42     ` Johannes Schindelin
@ 2018-05-03 21:12       ` Stefan Beller
  2018-05-03 21:49         ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-05-03 21:12 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

On Thu, May 3, 2018 at 1:42 PM, Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:

>> Speaking of colors, for origin/sb/blame-color Junio hinted at re-using
>> cyan for "uninteresting" parts to deliver a consistent color scheme for
>> Git. Eventually he dreams of having 2 layers of indirection IIUC, with
>>     "uninteresting" -> cyan
>>     "repeated lines in blame" -> uninteresting
>>
>> Maybe we can fit the coloring of this tool in this scheme, too?
>
> Sure. So you mean I should use cyan for... what part of the colored
> output? ;-)
>

It is just a FYI heads up, not an actionable bikeshed painting plan. ;)

>> Do we need to dynamic of a floating point, or would a rather small range
>> suffice here? (Also see rename detection settings, that take percents as
>> integers)
>
> I guess you are right, and we do not need floats. It was just very, very
> convenient to do that instead of using integers because
>
> - I already had the Jonker-Volgenant implementation "lying around" from my
>   previous life as an image processing expert, using doubles (but it was
>   in Java, not in C, so I quickly converted it for branch-diff).
>
> - I was actually not paying attention whether divisions are a thing in the
>   algorithm. From a cursory glance, it would appear that we are never
>   dividing in hungarian.c, so theoretically integers should be fine.
>
> - using doubles neatly side-steps the overflow problem. If I use integers
>   instead, I always will have to worry what to do if, say, adding
>   `INT_MAX` to `INT_MAX`.
>
> I am particularly worried about that last thing: it could easily lead to
> incorrect results if we blindly, say, pretend that `INT_MAX + INT_MAX ==
> INT_MAX` for the purpose of avoiding overflows.
>
> If, however, I misunderstood and you are only concerned about using
> *double-precision* floating point numbers, and would suggest using `float`
> typed variables instead, that would be totally cool with me.

So by being worried about INT_MAX occurring, you are implying that
we have to worry about a large range of values, so maybe floating points
are the best choice here.

Looking through that algorithm the costs seem to be integers only
measuring number of lines, so I would not be too worried about running
into INT_MAX problems except for the costs that are assigned INT_MAX
explicitly.

I was more asking, if floating point is the right tool for the job.

Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 03/18] branch-diff: first rudimentary implementation
  2018-05-03 21:01     ` Johannes Schindelin
@ 2018-05-03 21:19       ` Stefan Beller
  2018-05-03 22:00         ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-05-03 21:19 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

> To be honest, the main reason I spawn here is that I did not want to be
> bothered with resetting the commit flags after traversing the first commit
> range. But I guess I was just too cheap and should really do it?

Oh right, you'd have to do multiple revision walks.

> OTOH spawning here is a lot easier than not spawning, so maybe it would be
> premature optimization?

Most likely.

>
>> In addition to that patch, we'd have to buffer commit messages
>> and buffer multiple commits, as that only buffers a diff of a single
>> commit.
>
> ... and make sure that the moved-code logic (which is currently the only
> user of emitted_symbols, correct?) would never be called at the same time
> as we generate the diff.

The moved detection is all part of the flags of an emitted symbol.

By design the emitted symbol has easy access to the raw line of the output,
which made it easy for the move detection to work on the lines. AFAICT this
is also desired here as lines are put into a hashmap for comparisons.
(and having it colored differently would make finding the same line
complex using hashmaps)

I just entertain the thought of having move detection active in a
branch-diff. That would be really cool actually.

>
>> The benefit would be no invocation of new processes, letting us
>> do more in core. This would allow for tweaking revision walking
>> internally, e.g. passing of options to this command such as rename
>> detection factors, can be passed through easily without the need
>> of translating it back to the command line.
>
> On the other hand, we can simply copy those options to the command-line
> for `log`. Which might even be better, as e.g. `--format` changes global
> state :-(

ok.

Thanks for your patience,
Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-03 21:12       ` Stefan Beller
@ 2018-05-03 21:49         ` Johannes Schindelin
  2018-05-04  3:23           ` Junio C Hamano
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 21:49 UTC (permalink / raw)
  To: Stefan Beller
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Stefan,

On Thu, 3 May 2018, Stefan Beller wrote:

> On Thu, May 3, 2018 at 1:42 PM, Johannes Schindelin
> <Johannes.Schindelin@gmx.de> wrote:
> 
> >> Speaking of colors, for origin/sb/blame-color Junio hinted at re-using
> >> cyan for "uninteresting" parts to deliver a consistent color scheme for
> >> Git. Eventually he dreams of having 2 layers of indirection IIUC, with
> >>     "uninteresting" -> cyan
> >>     "repeated lines in blame" -> uninteresting
> >>
> >> Maybe we can fit the coloring of this tool in this scheme, too?
> >
> > Sure. So you mean I should use cyan for... what part of the colored
> > output? ;-)
> 
> It is just a FYI heads up, not an actionable bikeshed painting plan. ;)

Oh, I did not understand it as bike-shedding at all. I had hoped that you
ran `git branch-diff --dual-color` on something interesting and found e.g.
the yellow color inherited from DIFF_COMMIT to be the wrong color for
unchanged commit pairs.

So please: as soon as you have a concrete suggestion where to use cyan
(and preferably even a DIFF_* constant to feed to diff_get_color_opt()), I
will be more than interested.

> >> Do we need to dynamic of a floating point, or would a rather small range
> >> suffice here? (Also see rename detection settings, that take percents as
> >> integers)
> >
> > I guess you are right, and we do not need floats. It was just very, very
> > convenient to do that instead of using integers because
> >
> > - I already had the Jonker-Volgenant implementation "lying around" from my
> >   previous life as an image processing expert, using doubles (but it was
> >   in Java, not in C, so I quickly converted it for branch-diff).
> >
> > - I was actually not paying attention whether divisions are a thing in the
> >   algorithm. From a cursory glance, it would appear that we are never
> >   dividing in hungarian.c, so theoretically integers should be fine.
> >
> > - using doubles neatly side-steps the overflow problem. If I use integers
> >   instead, I always will have to worry what to do if, say, adding
> >   `INT_MAX` to `INT_MAX`.
> >
> > I am particularly worried about that last thing: it could easily lead to
> > incorrect results if we blindly, say, pretend that `INT_MAX + INT_MAX ==
> > INT_MAX` for the purpose of avoiding overflows.
> >
> > If, however, I misunderstood and you are only concerned about using
> > *double-precision* floating point numbers, and would suggest using `float`
> > typed variables instead, that would be totally cool with me.
> 
> So by being worried about INT_MAX occurring, you are implying that
> we have to worry about a large range of values, so maybe floating points
> are the best choice here.

I am not really worried about a large range of values, I am worried about
a use case where we use the maximal value as an "impossible, must avoid at
all cost" value. See this line in hungarian.c:

                        u2 = DBL_MAX;

It does not seem as if any arithmetic is done on u2 after that
(theoretically, it should not survive the loop that comes after it and
tries to replace u2 with any smaller value it finds, but what if that loop
does not even run because column_count == 1? Sure, it is a pathological
case, but even those should have correct results).

But actually, yes, there *is* arithmetic performed on u2:

                        if (u1 < u2)
                                v[j1] -= u2 - u1;

So in the pathological case where we try to find the best assignment of a
single column to an arbitrary number of rows (where simply the row with
a smallest cost should be picked), we try to subtract from v[j1] an
insanely large value. As a consequence, v[j1] will be close to INT_MIN if
we were to switch to integers, and who is to say that the next time we get
to this part, j1 will be different? If it is the same, and we hit the same
u2, then we might end up subtracting something close to INT_MAX from
INT_MIN, which will definitely overflow and the computation will be
incorrect.

*That* is what I am worried about: overflowing integer arithmetic. IIRC if
you subtract DBL_MAX from -DBL_MAX, you still end up with -DBL_MAX. So in
that respect, using floating point numbers here is safer.

> Looking through that algorithm the costs seem to be integers only
> measuring number of lines, so I would not be too worried about running
> into INT_MAX problems except for the costs that are assigned INT_MAX
> explicitly.
> 
> I was more asking, if floating point is the right tool for the job.

I think I would have to spend some real quality time with the code in
hungarian.c to turn it into using integer costs instead of floating point
numbers, to ensure that the arithmetic is done in a way that is consistent
with the algorithm, even if we cannot represent the arithmetic faithfully
with limited-range integers.

I'll think about the best way forward.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-03 18:05 ` [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Ævar Arnfjörð Bjarmason
  2018-05-03 21:07   ` Johannes Schindelin
@ 2018-05-03 21:50   ` Jacob Keller
  1 sibling, 0 replies; 387+ messages in thread
From: Jacob Keller @ 2018-05-03 21:50 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Johannes Schindelin, Git mailing list, Junio C Hamano,
	Thomas Rast, Thomas Gummerer

On Thu, May 3, 2018 at 11:05 AM, Ævar Arnfjörð Bjarmason
<avarab@gmail.com> wrote:
>
> On Thu, May 03 2018, Johannes Schindelin wrote:
>
>> 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.
>>
>> 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.
>
> I've been using branch-diff and haven't found issues with it yet, it
> works like tbdiff but better. Faster, uses the same diff as git
> (better), and spews to the pager by default.

I'm hoping to take a look at this as well, I remember looking into
tbdiff in the past, but also had trouble getting it to work. I've
tried a variety of similar things, including 4-way parent diffs, but
nothing quite gave the results I expected.

Thanks!

Regards,
Jake

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 03/18] branch-diff: first rudimentary implementation
  2018-05-03 21:19       ` Stefan Beller
@ 2018-05-03 22:00         ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-03 22:00 UTC (permalink / raw)
  To: Stefan Beller
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

[-- Attachment #1: Type: text/plain, Size: 2637 bytes --]

Hi Stefan,

On Thu, 3 May 2018, Stefan Beller wrote:

> >> In addition to that patch, we'd have to buffer commit messages and
> >> buffer multiple commits, as that only buffers a diff of a single
> >> commit.
> >
> > ... and make sure that the moved-code logic (which is currently the
> > only user of emitted_symbols, correct?) would never be called at the
> > same time as we generate the diff.
> 
> The moved detection is all part of the flags of an emitted symbol.
> 
> By design the emitted symbol has easy access to the raw line of the output,
> which made it easy for the move detection to work on the lines. AFAICT this
> is also desired here as lines are put into a hashmap for comparisons.
> (and having it colored differently would make finding the same line
> complex using hashmaps)
> 
> I just entertain the thought of having move detection active in a
> branch-diff. That would be really cool actually.

There are two separate times when we generate a diff in branch-diff: the
first time in that `git log -p <range>` call for both ranges, and later,
when displaying the changes of old/new commits.

(There is actually a third time, when the cost is calculated, but those
diffs are not shown, only their line count is used.)

It would be relatively easy to use move detection in the diff between
old/new commits. But it would be harder to do that with the `git log -p`
diffs, as we only color-code them later, not at the time they are
generated.

In fact, Ævar mentioned that he was pretty happy about the fact that `git
branch-diff` accepts all kinds of diff options, when tbdiff emulated only
two of them. Ævar mentioned specifically the use of `--color-words`...

> >> The benefit would be no invocation of new processes, letting us do
> >> more in core. This would allow for tweaking revision walking
> >> internally, e.g. passing of options to this command such as rename
> >> detection factors, can be passed through easily without the need of
> >> translating it back to the command line.
> >
> > On the other hand, we can simply copy those options to the
> > command-line for `log`. Which might even be better, as e.g. `--format`
> > changes global state :-(
> 
> ok.

I really appreciate the sanity check. It would benefit me on Windows if I
could avoid spawning... But I think in this case, it would save me 2*50ms,
which is not really worth doing the work for, so far.

> Thanks for your patience,

And thank you for yours! It *is* important to challenge beliefs during
code review, so that the choices are made for the right reasons.

Thanks,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-03 20:25     ` Johannes Schindelin
@ 2018-05-03 23:20       ` Ramsay Jones
  2018-05-04  6:40         ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Ramsay Jones @ 2018-05-03 23:20 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason



On 03/05/18 21:25, Johannes Schindelin wrote:

> On Thu, 3 May 2018, Ramsay Jones wrote:

>> On 03/05/18 16:30, Johannes Schindelin wrote:
[snip]

>>> diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
>>> new file mode 100644
>>> index 00000000000..97266cd326d
>>> --- /dev/null
>>> +++ b/builtin/branch-diff.c
>>> @@ -0,0 +1,40 @@
>>> +#include "cache.h"
>>> +#include "parse-options.h"
>>> +
>>> +static const char * const builtin_branch_diff_usage[] = {
>>> +	N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),
>>
>> s/rebase--helper/branch-diff/
> 
> Whoops!
> 
> BTW funny side note: when I saw that you replied, I instinctively thought
> "oh no, I forgot to mark a function as `static`!" ;-)

Heh, but I hadn't got around to applying the patches and building
git yet! ;-)

Sparse has two complaints:

  >     SP builtin/branch-diff.c
  > builtin/branch-diff.c:433:41: warning: Using plain integer as NULL pointer
  > builtin/branch-diff.c:431:5: warning: symbol 'cmd_branch_diff' was not declared. Should it be static?

I suppressed those warnings with the following patch (on top
of these patches):

  $ git diff
  diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
  index edf80ecb7..1373c22f4 100644
  --- a/builtin/branch-diff.c
  +++ b/builtin/branch-diff.c
  @@ -1,4 +1,5 @@
   #include "cache.h"
  +#include "builtin.h"
   #include "parse-options.h"
   #include "string-list.h"
   #include "run-command.h"
  @@ -430,7 +431,7 @@ static void output(struct string_list *a, struct string_list *b,
 
   int cmd_branch_diff(int argc, const char **argv, const char *prefix)
   {
  -       struct diff_options diffopt = { 0 };
  +       struct diff_options diffopt = { NULL };
          struct strbuf four_spaces = STRBUF_INIT;
          int dual_color = 0;
          double creation_weight = 0.6;
  $ 

The first hunk applies to patch 02/18 (ie this very patch) and
the second hunk should be applied to patch 05/18 (ie, "branch-diff:
also show the diff between patches").

ATB,
Ramsay Jones



^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 11/18] branch-diff: add tests
  2018-05-03 15:30 ` [PATCH 11/18] branch-diff: add tests Johannes Schindelin
  2018-05-03 16:56   ` Ævar Arnfjörð Bjarmason
  2018-05-03 17:11   ` Stefan Beller
@ 2018-05-03 23:27   ` Philip Oakley
  2018-05-04  6:42     ` Johannes Schindelin
  2 siblings, 1 reply; 387+ messages in thread
From: Philip Oakley @ 2018-05-03 23:27 UTC (permalink / raw)
  To: Johannes Schindelin, git
  Cc: Johannes Schindelin, Thomas Rast, Junio C Hamano,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

From: "Johannes Schindelin" <johannes.schindelin@gmx.de>
> From: Thomas Rast <tr@thomasrast.ch>
> 
> These are essentially lifted from https://github.com/trast/tbdiff, with
> light touch-ups to account for the new command name.
> 
> Apart from renaming `tbdiff` to `branch-diff`, only one test case needed
> to be adjusted: 11 - 'changed message'.
> 
> The underlying reason it had to be adjusted is that diff generation is
> sometimes ambiguous. In this case, a comment line and an empty line are
> added, but it is ambiguous whether they were added after the existing
> empty line, or whether an empty line and the comment line are added
> *before* the existing emtpy line. And apparently xdiff picks a different

s/emtpy/empty/

> option here than Python's difflib.
> 
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
[...]
Philip

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-03 15:30 ` [PATCH 02/18] Add a new builtin: branch-diff Johannes Schindelin
                     ` (2 preceding siblings ...)
  2018-05-03 16:43   ` Stefan Beller
@ 2018-05-04  2:35   ` Eric Sunshine
  2018-05-04  6:52     ` Johannes Schindelin
  3 siblings, 1 reply; 387+ messages in thread
From: Eric Sunshine @ 2018-05-04  2:35 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

On Thu, May 3, 2018 at 11:30 AM, Johannes Schindelin
<johannes.schindelin@gmx.de> wrote:
> This builtin does not do a whole lot so far, apart from showing a usage
> that is oddly similar to that of `git tbdiff`. And for a good reason:
> the next commits will turn `branch-diff` into a full-blown replacement
> for `tbdiff`.
>
> At this point, we ignore tbdiff's color options, as they will all be
> implemented later and require some patches to the diff machinery.
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
> diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
> @@ -0,0 +1,40 @@
> +static const char * const builtin_branch_diff_usage[] = {
> +       N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),
> +       NULL
> +};

The formatting of "<options>" vs. "base" confused me into thinking
that the latter was a literal keyword, but I see from reading patch
3/18 that it is not a literal at all, thus probably ought to be
specified as "<base>".

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 03/18] branch-diff: first rudimentary implementation
  2018-05-03 15:30 ` [PATCH 03/18] branch-diff: first rudimentary implementation Johannes Schindelin
  2018-05-03 16:30   ` Ramsay Jones
  2018-05-03 17:06   ` Stefan Beller
@ 2018-05-04  2:35   ` Eric Sunshine
  2018-05-04  7:03     ` Johannes Schindelin
  2018-05-04  4:56   ` Junio C Hamano
  3 siblings, 1 reply; 387+ messages in thread
From: Eric Sunshine @ 2018-05-04  2:35 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

On Thu, May 3, 2018 at 11:30 AM, Johannes Schindelin
<johannes.schindelin@gmx.de> wrote:
> At this stage, `git branch-diff` can determine corresponding commits of
> two related commit ranges. This makes use of the recently introduced
> implementation of the Hungarian algorithm.
>
> The core of this patch is a straight port of the ideas of tbdiff, the
> seemingly dormant project at https://github.com/trast/tbdiff.
>
> The output does not at all match `tbdiff`'s output yet, as this patch
> really concentrates on getting the patch matching part right.
>
> Note: due to differences in the diff algorithm (`tbdiff` uses the
> Pythong module `difflib`, Git uses its xdiff fork), the cost matrix

s/Pythong/Python/

> calculated by `branch-diff` is different (but very similar) to the one
> calculated by `tbdiff`. Therefore, it is possible that they find
> different matching commits in corner cases (e.g. when a patch was split
> into two patches of roughly equal length).
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
> diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
> @@ -19,6 +23,279 @@ static int parse_creation_weight(const struct option *opt, const char *arg,
> +static int read_patches(const char *range, struct string_list *list)
> +{
> +       [...]
> +       struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;
> +       [...]
> +                       } else if (starts_with(line.buf, "    ")) {
> +                               strbuf_addbuf(&buf, &line);
> +                               strbuf_addch(&buf, '\n');
> +                       }
> +
> +                       continue;

Unnecessary blank line above 'continue'?

> +               } else if (starts_with(line.buf, "@@ "))
> +                       strbuf_addstr(&buf, "@@");
> +               [...]
> +       }
> +       fclose(in);
> +
> +       if (util)
> +               string_list_append(list, buf.buf)->util = util;
> +       strbuf_release(&buf);

strbuf_release(&line);

> +       if (finish_command(&cp))
> +               return -1;
> +
> +       return 0;
> +}
> @@ -32,9 +309,63 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
> +       if (argc == 2) {
> +               if (!strstr(argv[0], ".."))
> +                       warning(_("no .. in range: '%s'"), argv[0]);
> +               strbuf_addstr(&range1, argv[0]);
> +
> +               if (!strstr(argv[1], ".."))
> +                       warning(_("no .. in range: '%s'"), argv[1]);
> +               strbuf_addstr(&range2, argv[1]);
> +       } else if (argc == 1) {
> +               if (!b)
> +                       die(_("single arg format requires a symmetric range"));
> +       } else {
> +               error("Need two commit ranges");

Other warning/error messages emitted by this function are not
capitalized: s/Need/need/

> +               usage_with_options(builtin_branch_diff_usage, options);
> +       }
> +
> +       if (read_patches(range1.buf, &branch1))
> +               res = error(_("could not parse log for '%s'"), range1.buf);
> +       if (!res && read_patches(range2.buf, &branch2))
> +               res = error(_("could not parse log for '%s'"), range2.buf);

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 05/18] branch-diff: also show the diff between patches
  2018-05-03 15:30 ` [PATCH 05/18] branch-diff: also show the diff between patches Johannes Schindelin
@ 2018-05-04  2:51   ` Eric Sunshine
  2018-05-04  3:15     ` Eric Sunshine
  2018-05-04  7:15     ` Johannes Schindelin
  0 siblings, 2 replies; 387+ messages in thread
From: Eric Sunshine @ 2018-05-04  2:51 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

On Thu, May 3, 2018 at 11:30 AM, Johannes Schindelin
<johannes.schindelin@gmx.de> wrote:
> Just like tbdiff, we now show the diff between matching patches. This is
> a "diff of two diffs", so it can be a bit daunting to read for the
> beginnger.

s/beginnger/beginner/

> This brings branch-diff closer to be feature-complete with regard to

s/be feature-complete/feature parity/

> tbdiff.
>
> An alternative would be to display an interdiff, i.e. the hypothetical
> diff which is the result of first reverting the old diff and then
> applying the new diff.
>
> Especially when rebasing often, an interdiff is often not feasible,
> though: if the old diff cannot be applied in reverse (due to a moving
> upstream), an interdiff can simply not be inferred.
>
> Note: while we now parse diff options such as --color, the effect is not
> yet the same as in tbdiff, where also the commit pairs would be colored.

"... tbdiff, in which the commit pairs would also be colored."

However, I don't see the --color option being parsed by this patch, so
perhaps this "Note" can be dropped?

> This is left for a later commit.
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
> diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
> @@ -319,24 +348,37 @@ static void output(struct string_list *a, struct string_list *b)
>  int cmd_branch_diff(int argc, const char **argv, const char *prefix)
>  {
> -       int no_patches = 0;
> +       struct diff_options diffopt = { 0 };
>         double creation_weight = 0.6;
>         struct option options[] = {
> -               OPT_BOOL(0, "no-patches", &no_patches,
> -                        N_("short format (no diffs)")),

This was added in 2/18 but never used...

> +               OPT_SET_INT(0, "no-patches", &diffopt.output_format,
> +                           N_("short format (no diffs)"),
> +                           DIFF_FORMAT_NO_OUTPUT),

... and is then replaced in its entirety by this. Perhaps just drop
the original --no-patches from 2/18 and let it be introduced for the
first time here?

>                 { OPTION_CALLBACK,
>                         0, "creation-weight", &creation_weight, N_("factor"),
>                         N_("Fudge factor by which creation is weighted [0.6]"),
>                         0, parse_creation_weight },
>                 OPT_END()
>         };

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 05/18] branch-diff: also show the diff between patches
  2018-05-04  2:51   ` Eric Sunshine
@ 2018-05-04  3:15     ` Eric Sunshine
  2018-05-04  7:15     ` Johannes Schindelin
  1 sibling, 0 replies; 387+ messages in thread
From: Eric Sunshine @ 2018-05-04  3:15 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

On Thu, May 3, 2018 at 10:51 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:
> On Thu, May 3, 2018 at 11:30 AM, Johannes Schindelin
> <johannes.schindelin@gmx.de> wrote:
>> Note: while we now parse diff options such as --color, the effect is not
>> yet the same as in tbdiff, where also the commit pairs would be colored.
>
> "... tbdiff, in which the commit pairs would also be colored."
>
> However, I don't see the --color option being parsed by this patch, so
> perhaps this "Note" can be dropped?

Ignore this latter comment; I missed the newly added call to diff_opt_parse().

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-03 21:49         ` Johannes Schindelin
@ 2018-05-04  3:23           ` Junio C Hamano
  0 siblings, 0 replies; 387+ messages in thread
From: Junio C Hamano @ 2018-05-04  3:23 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Stefan Beller, git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> So please: as soon as you have a concrete suggestion where to use cyan
> (and preferably even a DIFF_* constant to feed to diff_get_color_opt()), I
> will be more than interested.

I do not think Stefan's comment was that he was keen to use 'cyan'.
It was a color I suggested in a review of his change where he added
new colors to the color.[ch] palette, and I found that reusing an
existing color would have achieved the same distinction between
lines of output from his code, and it would be beneficial to make
the outcome consistent to consider why these existing colors are
used in existing places and trying to align the rationale for new
uses. "cyan" was cited as an example to illustrate that last point,
i.e. we use it to dim out relatively uninteresting part.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 17/18] branch-diff: add a man page
  2018-05-03 15:31 ` [PATCH 17/18] branch-diff: add a man page Johannes Schindelin
@ 2018-05-04  3:27   ` Eric Sunshine
  2018-05-04  7:17     ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Eric Sunshine @ 2018-05-04  3:27 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

On Thu, May 3, 2018 at 11:31 AM, Johannes Schindelin
<johannes.schindelin@gmx.de> wrote:
> This is a heavily butchered version of the README written by Thomas
> Rast and Thomas Gummerer, lifted from https://github.com/trast/tbdiff.
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
> diff --git a/Documentation/git-branch-diff.txt b/Documentation/git-branch-diff.txt
> @@ -0,0 +1,239 @@
> +Algorithm
> +---------
> +
> +The general idea is this: we generate a cost matrix between the commits
> +in both commit ranges, then solve the least-cost assignment.
> +
> +To avoid false positives (e.g. when a patch has been removed, and an
> +unrelated patch has been added between two iterations of the same patch
> +series), the cost matrix is extended to allow for that, by adding
> +fixed-cost entries for wholesale deletes/adds.
> +
> +Example: let commits `1--2` be the first iteration of a patch series and

s/let/Let/

> +`A--C` the second iteration. Let's assume that `A` is a cherry-pick of
> +`2,` and `C` is a cherry-pick of `1` but with a small modification (say,
> +a fixed typo). Visualize the commits as a bipartite graph:

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 03/18] branch-diff: first rudimentary implementation
  2018-05-03 15:30 ` [PATCH 03/18] branch-diff: first rudimentary implementation Johannes Schindelin
                     ` (2 preceding siblings ...)
  2018-05-04  2:35   ` Eric Sunshine
@ 2018-05-04  4:56   ` Junio C Hamano
  2018-05-04  7:18     ` Johannes Schindelin
  3 siblings, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-05-04  4:56 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Johannes Schindelin <johannes.schindelin@gmx.de> writes:

> Note: due to differences in the diff algorithm (`tbdiff` uses the
> Pythong module `difflib`, Git uses its xdiff fork), the cost matrix

Pythong???

> calculated by `branch-diff` is different (but very similar) to the one
> calculated by `tbdiff`. Therefore, it is possible that they find
> different matching commits in corner cases (e.g. when a patch was split
> into two patches of roughly equal length).

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-03 20:32       ` Johannes Schindelin
@ 2018-05-04  5:15         ` Duy Nguyen
  2018-05-04  7:23           ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Duy Nguyen @ 2018-05-04  5:15 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git Mailing List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

On Thu, May 3, 2018 at 10:32 PM, Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:
> Hi Duy,
>
> On Thu, 3 May 2018, Johannes Schindelin wrote:
>
>> On Thu, 3 May 2018, Duy Nguyen wrote:
>>
>> > On Thu, May 3, 2018 at 5:30 PM, Johannes Schindelin
>> > <johannes.schindelin@gmx.de> wrote:
>> > > diff --git a/command-list.txt b/command-list.txt
>> > > index a1fad28fd82..c89ac8f417f 100644
>> > > --- a/command-list.txt
>> > > +++ b/command-list.txt
>> > > @@ -19,6 +19,7 @@ git-archive                             mainporcelain
>> > >  git-bisect                              mainporcelain           info
>> > >  git-blame                               ancillaryinterrogators
>> > >  git-branch                              mainporcelain           history
>> > > +git-branch-diff                         mainporcelain           info
>> >
>> > Making it part of "git help" with the info keywords at this stage may
>> > be premature. "git help" is about _common_ commands and we don't know
>> > (yet) how popular this will be.
>>
>> Makes sense. I removed the `mainporcelain` keyword locally.
>
> On second thought, I *think* you meant to imply that I should remove that
> line altogether. Will do that now.

Actually I only suggested to remove the last word "info". That was
what made this command "common". Classifying all commands in this file
is definitely a good thing, and I think mainporcelain is the right
choice.
-- 
Duy

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
                   ` (18 preceding siblings ...)
  2018-05-03 18:05 ` [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Ævar Arnfjörð Bjarmason
@ 2018-05-04  5:24 ` Junio C Hamano
  2018-05-04  7:24   ` Johannes Schindelin
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
  2018-05-21  4:48 ` [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Junio C Hamano
  21 siblings, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-05-04  5:24 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Johannes Schindelin <johannes.schindelin@gmx.de> writes:

> Johannes Schindelin (17):
>   Add a function to solve least-cost assignment problems
>   Add a new builtin: branch-diff
>   branch-diff: first rudimentary implementation
>   branch-diff: improve the order of the shown commits
>   branch-diff: also show the diff between patches
>   branch-diff: right-trim commit messages
>   branch-diff: indent the diffs just like tbdiff
>   branch-diff: suppress the diff headers
>   branch-diff: adjust the output of the commit pairs
>   branch-diff: do not show "function names" in hunk headers
>   branch-diff: use color for the commit pairs
>   color: provide inverted colors, too
>   diff: add an internal option to dual-color diffs of diffs
>   branch-diff: offer to dual-color the diffs
>   branch-diff --dual-color: work around bogus white-space warning
>   branch-diff: add a man page
>   completion: support branch-diff
>
> Thomas Rast (1):
>   branch-diff: add tests

Lovely.  

I often have to criticize a series whose later half consists of many
follow-up patches with "don't do 'oops, the previous was wrong'",
but the follow-up patches in this series are not such corrections.
The organization of the series to outline the basic and core idea
first in the minimum form and then to build on it to improve an
aspect of the command one step at a time is very helpful to guide
the readers where the author of the series wants them to go.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-03 23:20       ` Ramsay Jones
@ 2018-05-04  6:40         ` Johannes Schindelin
  2018-05-04 15:37           ` Ramsay Jones
  2018-05-04 16:34           ` Elijah Newren
  0 siblings, 2 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04  6:40 UTC (permalink / raw)
  To: Ramsay Jones
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Ramsay,

On Fri, 4 May 2018, Ramsay Jones wrote:

> On 03/05/18 21:25, Johannes Schindelin wrote:
> 
> > On Thu, 3 May 2018, Ramsay Jones wrote:
> 
> >> On 03/05/18 16:30, Johannes Schindelin wrote:
> [snip]
> 
> >>> diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
> >>> new file mode 100644
> >>> index 00000000000..97266cd326d
> >>> --- /dev/null
> >>> +++ b/builtin/branch-diff.c
> >>> @@ -0,0 +1,40 @@
> >>> +#include "cache.h"
> >>> +#include "parse-options.h"
> >>> +
> >>> +static const char * const builtin_branch_diff_usage[] = {
> >>> +	N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),
> >>
> >> s/rebase--helper/branch-diff/
> > 
> > Whoops!
> > 
> > BTW funny side note: when I saw that you replied, I instinctively thought
> > "oh no, I forgot to mark a function as `static`!" ;-)
> 
> Heh, but I hadn't got around to applying the patches and building
> git yet! ;-)

;-)

> Sparse has two complaints:
> 
>   >     SP builtin/branch-diff.c
>   > builtin/branch-diff.c:433:41: warning: Using plain integer as NULL pointer
>   > builtin/branch-diff.c:431:5: warning: symbol 'cmd_branch_diff' was not declared. Should it be static?
> 
> I suppressed those warnings with the following patch (on top
> of these patches):
> 
>   $ git diff
>   diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
>   index edf80ecb7..1373c22f4 100644
>   --- a/builtin/branch-diff.c
>   +++ b/builtin/branch-diff.c
>   @@ -1,4 +1,5 @@
>    #include "cache.h"
>   +#include "builtin.h"
>    #include "parse-options.h"
>    #include "string-list.h"
>    #include "run-command.h"
>   @@ -430,7 +431,7 @@ static void output(struct string_list *a, struct string_list *b,
>  
>    int cmd_branch_diff(int argc, const char **argv, const char *prefix)
>    {
>   -       struct diff_options diffopt = { 0 };
>   +       struct diff_options diffopt = { NULL };
>           struct strbuf four_spaces = STRBUF_INIT;
>           int dual_color = 0;
>           double creation_weight = 0.6;
>   $ 

Thanks!

> The first hunk applies to patch 02/18 (ie this very patch) and
> the second hunk should be applied to patch 05/18 (ie, "branch-diff:
> also show the diff between patches").

I actually have a hacky script to fixup commits in a patch series. It lets
me stage part of the current changes, then figures out which of the
commits' changes overlap with the staged changed. If there is only one
commit, it automatically commits with --fixup, otherwise it lets me choose
which one I want to fixup (giving me the list of candidates).

BTW I ran `make sparse` for the first time, and it spits out tons of
stuff. And I notice that they are all non-fatal warnings, but so were the
ones you pointed out above. This is a bit sad, as I would *love* to
install a VSTS build job to run `make sparse` automatically. Examples of
warnings *after* applying your patch:

connect.c:481:40: warning: incorrect type in argument 2 (invalid types)
connect.c:481:40:    expected union __CONST_SOCKADDR_ARG [usertype] __addr
connect.c:481:40:    got struct sockaddr *ai_addr

or

pack-revindex.c:65:23: warning: memset with byte count of 262144

What gives?

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 11/18] branch-diff: add tests
  2018-05-03 23:27   ` Philip Oakley
@ 2018-05-04  6:42     ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04  6:42 UTC (permalink / raw)
  To: Philip Oakley
  Cc: git, Thomas Rast, Junio C Hamano, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Philip,

On Fri, 4 May 2018, Philip Oakley wrote:

> From: "Johannes Schindelin" <johannes.schindelin@gmx.de>
> > From: Thomas Rast <tr@thomasrast.ch>
> > 
> > These are essentially lifted from https://github.com/trast/tbdiff, with
> > light touch-ups to account for the new command name.
> > 
> > Apart from renaming `tbdiff` to `branch-diff`, only one test case needed
> > to be adjusted: 11 - 'changed message'.
> > 
> > The underlying reason it had to be adjusted is that diff generation is
> > sometimes ambiguous. In this case, a comment line and an empty line are
> > added, but it is ambiguous whether they were added after the existing
> > empty line, or whether an empty line and the comment line are added
> > *before* the existing emtpy line. And apparently xdiff picks a different
> 
> s/emtpy/empty/

Yep, that has been pointed out by others, too... ;-)

It is good that this thing gets a really good review *grins*

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-04  2:35   ` Eric Sunshine
@ 2018-05-04  6:52     ` Johannes Schindelin
  2018-05-04  7:27       ` Eric Sunshine
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04  6:52 UTC (permalink / raw)
  To: Eric Sunshine
  Cc: Git List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Eric,

On Thu, 3 May 2018, Eric Sunshine wrote:

> On Thu, May 3, 2018 at 11:30 AM, Johannes Schindelin
> <johannes.schindelin@gmx.de> wrote:
> > This builtin does not do a whole lot so far, apart from showing a usage
> > that is oddly similar to that of `git tbdiff`. And for a good reason:
> > the next commits will turn `branch-diff` into a full-blown replacement
> > for `tbdiff`.
> >
> > At this point, we ignore tbdiff's color options, as they will all be
> > implemented later and require some patches to the diff machinery.
> >
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> > diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
> > @@ -0,0 +1,40 @@
> > +static const char * const builtin_branch_diff_usage[] = {
> > +       N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),
> > +       NULL
> > +};
> 
> The formatting of "<options>" vs. "base" confused me into thinking
> that the latter was a literal keyword, but I see from reading patch
> 3/18 that it is not a literal at all, thus probably ought to be
> specified as "<base>".

Good point. Or maybe BASE?

Or I should just use the same convention as in the man page. Or not, as
the usage should be conciser.

This is what I have currently:

static const char * const builtin_branch_diff_usage[] = {
N_("git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
N_("git branch-diff [<options>] <old-tip>...<new-tip>"),
N_("git branch-diff [<options>] <base> <old-tip> <new-tip>"),
NULL
};

Thanks,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 03/18] branch-diff: first rudimentary implementation
  2018-05-04  2:35   ` Eric Sunshine
@ 2018-05-04  7:03     ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04  7:03 UTC (permalink / raw)
  To: Eric Sunshine
  Cc: Git List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Eric,

On Thu, 3 May 2018, Eric Sunshine wrote:

> On Thu, May 3, 2018 at 11:30 AM, Johannes Schindelin
> <johannes.schindelin@gmx.de> wrote:
> > At this stage, `git branch-diff` can determine corresponding commits of
> > two related commit ranges. This makes use of the recently introduced
> > implementation of the Hungarian algorithm.
> >
> > The core of this patch is a straight port of the ideas of tbdiff, the
> > seemingly dormant project at https://github.com/trast/tbdiff.
> >
> > The output does not at all match `tbdiff`'s output yet, as this patch
> > really concentrates on getting the patch matching part right.
> >
> > Note: due to differences in the diff algorithm (`tbdiff` uses the
> > Pythong module `difflib`, Git uses its xdiff fork), the cost matrix
> 
> s/Pythong/Python/

Yep!

> > calculated by `branch-diff` is different (but very similar) to the one
> > calculated by `tbdiff`. Therefore, it is possible that they find
> > different matching commits in corner cases (e.g. when a patch was split
> > into two patches of roughly equal length).
> >
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> > diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
> > @@ -19,6 +23,279 @@ static int parse_creation_weight(const struct option *opt, const char *arg,
> > +static int read_patches(const char *range, struct string_list *list)
> > +{
> > +       [...]
> > +       struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;
> > +       [...]
> > +                       } else if (starts_with(line.buf, "    ")) {
> > +                               strbuf_addbuf(&buf, &line);
> > +                               strbuf_addch(&buf, '\n');
> > +                       }
> > +
> > +                       continue;
> 
> Unnecessary blank line above 'continue'?

Sure.

> > +               } else if (starts_with(line.buf, "@@ "))
> > +                       strbuf_addstr(&buf, "@@");
> > +               [...]
> > +       }
> > +       fclose(in);
> > +
> > +       if (util)
> > +               string_list_append(list, buf.buf)->util = util;
> > +       strbuf_release(&buf);
> 
> strbuf_release(&line);

Yes!

> > +       if (finish_command(&cp))
> > +               return -1;
> > +
> > +       return 0;
> > +}
> > @@ -32,9 +309,63 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
> > +       if (argc == 2) {
> > +               if (!strstr(argv[0], ".."))
> > +                       warning(_("no .. in range: '%s'"), argv[0]);
> > +               strbuf_addstr(&range1, argv[0]);
> > +
> > +               if (!strstr(argv[1], ".."))
> > +                       warning(_("no .. in range: '%s'"), argv[1]);
> > +               strbuf_addstr(&range2, argv[1]);
> > +       } else if (argc == 1) {
> > +               if (!b)
> > +                       die(_("single arg format requires a symmetric range"));
> > +       } else {
> > +               error("Need two commit ranges");
> 
> Other warning/error messages emitted by this function are not
> capitalized: s/Need/need/

Right. And it is also not translated. Fixed both.

Thank you for helping me make this patch series better!

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 05/18] branch-diff: also show the diff between patches
  2018-05-04  2:51   ` Eric Sunshine
  2018-05-04  3:15     ` Eric Sunshine
@ 2018-05-04  7:15     ` Johannes Schindelin
  1 sibling, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04  7:15 UTC (permalink / raw)
  To: Eric Sunshine
  Cc: Git List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Eric,

On Thu, 3 May 2018, Eric Sunshine wrote:

> On Thu, May 3, 2018 at 11:30 AM, Johannes Schindelin
> <johannes.schindelin@gmx.de> wrote:
> > Just like tbdiff, we now show the diff between matching patches. This is
> > a "diff of two diffs", so it can be a bit daunting to read for the
> > beginnger.
> 
> s/beginnger/beginner/
> 
> > This brings branch-diff closer to be feature-complete with regard to
> 
> s/be feature-complete/feature parity/

Yes.

> > diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
> > @@ -319,24 +348,37 @@ static void output(struct string_list *a, struct string_list *b)
> >  int cmd_branch_diff(int argc, const char **argv, const char *prefix)
> >  {
> > -       int no_patches = 0;
> > +       struct diff_options diffopt = { 0 };
> >         double creation_weight = 0.6;
> >         struct option options[] = {
> > -               OPT_BOOL(0, "no-patches", &no_patches,
> > -                        N_("short format (no diffs)")),
> 
> This was added in 2/18 but never used...
> 
> > +               OPT_SET_INT(0, "no-patches", &diffopt.output_format,
> > +                           N_("short format (no diffs)"),
> > +                           DIFF_FORMAT_NO_OUTPUT),
> 
> ... and is then replaced in its entirety by this. Perhaps just drop
> the original --no-patches from 2/18 and let it be introduced for the
> first time here?

Sure. I actually started out by parsing even the --color option, but had
stripped that out in a rebase -i run, in favor of using diff_parse_opt()
later. I should really do the same here, too.

Thanks,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 17/18] branch-diff: add a man page
  2018-05-04  3:27   ` Eric Sunshine
@ 2018-05-04  7:17     ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04  7:17 UTC (permalink / raw)
  To: Eric Sunshine
  Cc: Git List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Eric,

On Thu, 3 May 2018, Eric Sunshine wrote:

> On Thu, May 3, 2018 at 11:31 AM, Johannes Schindelin
> <johannes.schindelin@gmx.de> wrote:
> > This is a heavily butchered version of the README written by Thomas
> > Rast and Thomas Gummerer, lifted from https://github.com/trast/tbdiff.
> >
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> > diff --git a/Documentation/git-branch-diff.txt b/Documentation/git-branch-diff.txt
> > @@ -0,0 +1,239 @@
> > +Algorithm
> > +---------
> > +
> > +The general idea is this: we generate a cost matrix between the commits
> > +in both commit ranges, then solve the least-cost assignment.
> > +
> > +To avoid false positives (e.g. when a patch has been removed, and an
> > +unrelated patch has been added between two iterations of the same patch
> > +series), the cost matrix is extended to allow for that, by adding
> > +fixed-cost entries for wholesale deletes/adds.
> > +
> > +Example: let commits `1--2` be the first iteration of a patch series and
> 
> s/let/Let/

Okay. I am always a little bit fuzzy on the question whether to continue
lower-case or upper-case after a colon or semicolon.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 03/18] branch-diff: first rudimentary implementation
  2018-05-04  4:56   ` Junio C Hamano
@ 2018-05-04  7:18     ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04  7:18 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Junio,

On Fri, 4 May 2018, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > Note: due to differences in the diff algorithm (`tbdiff` uses the
> > Pythong module `difflib`, Git uses its xdiff fork), the cost matrix
> 
> Pythong???

Yes, that's the French pronunciation.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-04  5:15         ` Duy Nguyen
@ 2018-05-04  7:23           ` Johannes Schindelin
  2018-05-04 14:44             ` Duy Nguyen
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04  7:23 UTC (permalink / raw)
  To: Duy Nguyen
  Cc: Git Mailing List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Duy,

On Fri, 4 May 2018, Duy Nguyen wrote:

> On Thu, May 3, 2018 at 10:32 PM, Johannes Schindelin
> <Johannes.Schindelin@gmx.de> wrote:
> >
> > On Thu, 3 May 2018, Johannes Schindelin wrote:
> >
> >> On Thu, 3 May 2018, Duy Nguyen wrote:
> >>
> >> > On Thu, May 3, 2018 at 5:30 PM, Johannes Schindelin
> >> > <johannes.schindelin@gmx.de> wrote:
> >> > > diff --git a/command-list.txt b/command-list.txt
> >> > > index a1fad28fd82..c89ac8f417f 100644
> >> > > --- a/command-list.txt
> >> > > +++ b/command-list.txt
> >> > > @@ -19,6 +19,7 @@ git-archive                             mainporcelain
> >> > >  git-bisect                              mainporcelain           info
> >> > >  git-blame                               ancillaryinterrogators
> >> > >  git-branch                              mainporcelain           history
> >> > > +git-branch-diff                         mainporcelain           info
> >> >
> >> > Making it part of "git help" with the info keywords at this stage may
> >> > be premature. "git help" is about _common_ commands and we don't know
> >> > (yet) how popular this will be.
> >>
> >> Makes sense. I removed the `mainporcelain` keyword locally.
> >
> > On second thought, I *think* you meant to imply that I should remove that
> > line altogether. Will do that now.
> 
> Actually I only suggested to remove the last word "info". That was
> what made this command "common". Classifying all commands in this file
> is definitely a good thing, and I think mainporcelain is the right
> choice.

Oh, okay. It was not at all clear to me what the exact format and role of
these lines are... So that's what `info` does: it influences whether/where
the command is listed in `git help`'s output... Interesting. I thought the
lines here were trying to automate parts of the tab completion or
something.

I re-added the line, this time without `info` and verified that
`branch-diff` does not show up in `git help`'s output.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-04  5:24 ` Junio C Hamano
@ 2018-05-04  7:24   ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04  7:24 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Junio,

On Fri, 4 May 2018, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > Johannes Schindelin (17):
> >   Add a function to solve least-cost assignment problems
> >   Add a new builtin: branch-diff
> >   branch-diff: first rudimentary implementation
> >   branch-diff: improve the order of the shown commits
> >   branch-diff: also show the diff between patches
> >   branch-diff: right-trim commit messages
> >   branch-diff: indent the diffs just like tbdiff
> >   branch-diff: suppress the diff headers
> >   branch-diff: adjust the output of the commit pairs
> >   branch-diff: do not show "function names" in hunk headers
> >   branch-diff: use color for the commit pairs
> >   color: provide inverted colors, too
> >   diff: add an internal option to dual-color diffs of diffs
> >   branch-diff: offer to dual-color the diffs
> >   branch-diff --dual-color: work around bogus white-space warning
> >   branch-diff: add a man page
> >   completion: support branch-diff
> >
> > Thomas Rast (1):
> >   branch-diff: add tests
> 
> Lovely.  
> 
> I often have to criticize a series whose later half consists of many
> follow-up patches with "don't do 'oops, the previous was wrong'",
> but the follow-up patches in this series are not such corrections.
> The organization of the series to outline the basic and core idea
> first in the minimum form and then to build on it to improve an
> aspect of the command one step at a time is very helpful to guide
> the readers where the author of the series wants them to go.

Thanks! *beams*

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-04  6:52     ` Johannes Schindelin
@ 2018-05-04  7:27       ` Eric Sunshine
  0 siblings, 0 replies; 387+ messages in thread
From: Eric Sunshine @ 2018-05-04  7:27 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

On Fri, May 4, 2018 at 2:52 AM, Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:
> On Thu, 3 May 2018, Eric Sunshine wrote:
>> On Thu, May 3, 2018 at 11:30 AM, Johannes Schindelin
>> <johannes.schindelin@gmx.de> wrote:
>> > +static const char * const builtin_branch_diff_usage[] = {
>> > +       N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),
>>
>> The formatting of "<options>" vs. "base" confused me into thinking
>> that the latter was a literal keyword, but I see from reading patch
>> 3/18 that it is not a literal at all, thus probably ought to be
>> specified as "<base>".
>
> Good point. Or maybe BASE?

Indeed, that's probably more consistent with 'A', 'B', etc. than <base>.

> Or I should just use the same convention as in the man page. Or not, as
> the usage should be conciser.
>
> This is what I have currently:
>
> static const char * const builtin_branch_diff_usage[] = {
> N_("git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
> N_("git branch-diff [<options>] <old-tip>...<new-tip>"),
> N_("git branch-diff [<options>] <base> <old-tip> <new-tip>"),
> NULL
> };

I can live with this. It's more verbose but more self-explanatory,
thus likely a good choice.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-04  7:23           ` Johannes Schindelin
@ 2018-05-04 14:44             ` Duy Nguyen
  2018-05-04 15:17               ` Duy Nguyen
  2018-05-04 15:23               ` Johannes Schindelin
  0 siblings, 2 replies; 387+ messages in thread
From: Duy Nguyen @ 2018-05-04 14:44 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git Mailing List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

On Fri, May 4, 2018 at 9:23 AM, Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:
> Oh, okay. It was not at all clear to me what the exact format and role of
> these lines are...

Noted. I'm making more updates in this file in another topic and will
add some explanation so the next guy will be less confused.

> So that's what `info` does: it influences whether/where
> the command is listed in `git help`'s output... Interesting. I thought the
> lines here were trying to automate parts of the tab completion or
> something.

Oh it does many things. The completion part is coming (so yeah you
don't need to update git-completion.bash at all, as long as you have a
line here and use parse_options() ;-), but I think it's mainly for
"git help" and command listing in "git help git" (this command for
example should show up under the "Main porcelain commands" in that man
page when you put a line here)
-- 
Duy

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-04 14:44             ` Duy Nguyen
@ 2018-05-04 15:17               ` Duy Nguyen
  2018-05-04 15:23               ` Johannes Schindelin
  1 sibling, 0 replies; 387+ messages in thread
From: Duy Nguyen @ 2018-05-04 15:17 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git Mailing List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

On Fri, May 04, 2018 at 04:44:49PM +0200, Duy Nguyen wrote:
> On Fri, May 4, 2018 at 9:23 AM, Johannes Schindelin
> <Johannes.Schindelin@gmx.de> wrote:
> > Oh, okay. It was not at all clear to me what the exact format and role of
> > these lines are...
> 
> Noted. I'm making more updates in this file in another topic and will
> add some explanation so the next guy will be less confused.

This is what I will include (but in a separate topic). I will not CC
you there to keep your inbox slightly less full. I hope this helps
understand what command-list.txt is for.

diff --git a/command-list.txt b/command-list.txt
index 40776b9587..929d8f0ea0 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -1,3 +1,47 @@
+# Command classification list
+# ---------------------------
+# All supported commands, builtin or external, must be described in
+# here. This info is used to list commands in various places. Each
+# command is on one line followed by one or more attributes.
+#
+# The first attribute group is mandatory and indicates the command
+# type. This group includes:
+#
+#   mainporcelain
+#   ancillarymanipulators
+#   ancillaryinterrogators
+#   foreignscminterface
+#   plumbingmanipulators
+#   plumbinginterrogators
+#   synchingrepositories
+#   synchelpers
+#   purehelpers
+#
+# the type names are self explanatory. But if you want to see what
+# command belongs to what group to get a better idea, have a look at
+# "git" man page, "GIT COMMANDS" section.
+#
+# Commands of type mainporcelain can also optionally have one of these
+# attributes:
+#
+#   init
+#   worktree
+#   info
+#   history
+#   remote
+#
+# These commands are considered "common" and will show up in "git
+# help" output in groups. Uncommon porcelain commands must not
+# specify any of these attributes.
+#
+# "complete" attribute is used to mark that the command should be
+# completable by git-completion.bash. Note that by default,
+# mainporcelain commands are completable and you don't need this
+# attribute.
+#
+# While not true commands, guides are also specified here, which can
+# only have "guide" attribute and nothing else.
+#
 ### command list (do not change this line, also do not change alignment)
 # command name                          category [category] [category]
 git-add                                 mainporcelain           worktree

^ permalink raw reply related	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-04 14:44             ` Duy Nguyen
  2018-05-04 15:17               ` Duy Nguyen
@ 2018-05-04 15:23               ` Johannes Schindelin
  2018-05-04 15:29                 ` Duy Nguyen
  1 sibling, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:23 UTC (permalink / raw)
  To: Duy Nguyen
  Cc: Git Mailing List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Duy,

On Fri, 4 May 2018, Duy Nguyen wrote:

> On Fri, May 4, 2018 at 9:23 AM, Johannes Schindelin
> <Johannes.Schindelin@gmx.de> wrote:
> > Oh, okay. It was not at all clear to me what the exact format and role of
> > these lines are...
> 
> Noted. I'm making more updates in this file in another topic and will
> add some explanation so the next guy will be less confused.

Thank you!

> > So that's what `info` does: it influences whether/where
> > the command is listed in `git help`'s output... Interesting. I thought the
> > lines here were trying to automate parts of the tab completion or
> > something.
> 
> Oh it does many things. The completion part is coming (so yeah you
> don't need to update git-completion.bash at all, as long as you have a
> line here and use parse_options() ;-), but I think it's mainly for
> "git help" and command listing in "git help git" (this command for
> example should show up under the "Main porcelain commands" in that man
> page when you put a line here)

I have a hard time believing that anything automated can infer from the
source code that branch-diff can accept the non-options in three different
formats, and what those formats look like...

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-04 15:23               ` Johannes Schindelin
@ 2018-05-04 15:29                 ` Duy Nguyen
  0 siblings, 0 replies; 387+ messages in thread
From: Duy Nguyen @ 2018-05-04 15:29 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git Mailing List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

On Fri, May 4, 2018 at 5:23 PM, Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:
>> > So that's what `info` does: it influences whether/where
>> > the command is listed in `git help`'s output... Interesting. I thought the
>> > lines here were trying to automate parts of the tab completion or
>> > something.
>>
>> Oh it does many things. The completion part is coming (so yeah you
>> don't need to update git-completion.bash at all, as long as you have a
>> line here and use parse_options() ;-), but I think it's mainly for
>> "git help" and command listing in "git help git" (this command for
>> example should show up under the "Main porcelain commands" in that man
>> page when you put a line here)
>
> I have a hard time believing that anything automated can infer from the
> source code that branch-diff can accept the non-options in three different
> formats, and what those formats look like...

Yeah. For now it can only do options which is machine readable. But I
have a big plan, so who knows :D
-- 
Duy

^ permalink raw reply	[flat|nested] 387+ messages in thread

* [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
                   ` (19 preceding siblings ...)
  2018-05-04  5:24 ` Junio C Hamano
@ 2018-05-04 15:34 ` Johannes Schindelin
  2018-05-04 15:34   ` [PATCH v2 01/18] Add a function to solve least-cost assignment problems Johannes Schindelin
                     ` (21 more replies)
  2018-05-21  4:48 ` [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Junio C Hamano
  21 siblings, 22 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:34 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

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.

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.

While at it, I forward-ported AEvar's patch to force `--decorate=no` because
`git -p tbdiff` would fail otherwise.

Side 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
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:

	base=^{/Start.the.merging-rebase}
	tag=v2.17.0.windows.1
	pre=$tag$base^2
	git branch-diff --dual-color $pre$base..$pre $tag$base..$tag

The --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.

Changes since v1:

- Fixed the usage to *not* say "rebase--helper" (oops, now everybody knows that
  I copy-edited that code... ;-))

- Removed `branch-diff` from the `git help` output for now, by removing the
  `info` keyword from the respective line in command-list.txt.

- Removed the bogus `COLOR_DUAL_MODE` constant that was introduced in one
  patch, only to be removed in the next one.

- Fixed typo "emtpy".

- Fixed `make sparse` warnings.

- Changed the usage string to avoid the confusing lower-case bare `base`.

- Fixed tyop in commit message: "Pythong".

- Removed an awkward empty line before a `continue;` statement.

- Released the `line` strbuf after use.

- Fixed a typo and some "foreigner's English" in the commit message of
  "branch-diff: also show the diff between patches".

- Avoided introducing --no-patches too early and then replacing it by the
  version that uses the diff_options.

- Fixed the man page to continue with upper-case after a colon.

The branch-diff of v1 vs 2 is best viewed with --dual-color; This helps e.g.
with identifying changed *context* lines in the diff:

git branch-diff --dual-color origin/master branch-diff-v1 branch-diff-v2

(assuming that your `origin` points to git.git and that you fetched the tags
from https://github.com/dscho/git). For example, you will see that the only
change in patch #10 is a change in the diff context (due to the change of the
usage string in patch #2):

10:  9810869ced9 ! 10:  2695a6abc46 branch-diff: do not show "function names" in hunk headers
    @@ -17,7 +17,7 @@
     +#include "userdiff.h"

      static const char * const builtin_branch_diff_usage[] = {
    -   N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),
    + N_("git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
     @@
        return data;
      }


Johannes Schindelin (17):
  Add a function to solve least-cost assignment problems
  Add a new builtin: branch-diff
  branch-diff: first rudimentary implementation
  branch-diff: improve the order of the shown commits
  branch-diff: also show the diff between patches
  branch-diff: right-trim commit messages
  branch-diff: indent the diffs just like tbdiff
  branch-diff: suppress the diff headers
  branch-diff: adjust the output of the commit pairs
  branch-diff: do not show "function names" in hunk headers
  branch-diff: use color for the commit pairs
  color: provide inverted colors, too
  diff: add an internal option to dual-color diffs of diffs
  branch-diff: offer to dual-color the diffs
  branch-diff --dual-color: work around bogus white-space warning
  branch-diff: add a man page
  completion: support branch-diff

Thomas Rast (1):
  branch-diff: add tests

 .gitignore                             |   1 +
 Documentation/git-branch-diff.txt      | 239 ++++++++++
 Makefile                               |   2 +
 builtin.h                              |   1 +
 builtin/branch-diff.c                  | 534 ++++++++++++++++++++++
 color.h                                |   6 +
 command-list.txt                       |   1 +
 contrib/completion/git-completion.bash |  18 +
 diff.c                                 |  76 +++-
 diff.h                                 |   6 +-
 git.c                                  |   1 +
 hungarian.c                            | 205 +++++++++
 hungarian.h                            |  19 +
 t/.gitattributes                       |   1 +
 t/t7910-branch-diff.sh                 | 144 ++++++
 t/t7910/history.export                 | 604 +++++++++++++++++++++++++
 16 files changed, 1846 insertions(+), 12 deletions(-)
 create mode 100644 Documentation/git-branch-diff.txt
 create mode 100644 builtin/branch-diff.c
 create mode 100644 hungarian.c
 create mode 100644 hungarian.h
 create mode 100755 t/t7910-branch-diff.sh
 create mode 100644 t/t7910/history.export


base-commit: 1f1cddd558b54bb0ce19c8ace353fd07b758510d
Published-As: https://github.com/dscho/git/releases/tag/branch-diff-v2
Fetch-It-Via: git fetch https://github.com/dscho/git branch-diff-v2

Branch-diff vs v1:
  1: 8bc517e35d4 !  1: a1ea0320b64 Add a new builtin: branch-diff
     @@ -7,8 +7,9 @@
          the next commits will turn `branch-diff` into a full-blown replacement
          for `tbdiff`.
          
     -    At this point, we ignore tbdiff's color options, as they will all be
     -    implemented later and require some patches to the diff machinery.
     +    At this point, we ignore tbdiff's color options as well as the
     +    --no-patches option, as they will all be implemented later using
     +    diff_options.
          
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     @@ -54,14 +55,15 @@
      +++ b/builtin/branch-diff.c
      @@
      +#include "cache.h"
     ++#include "builtin.h"
      +#include "parse-options.h"
      +
      +static const char * const builtin_branch_diff_usage[] = {
     -+	N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),
     -+	NULL
     ++N_("git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
     ++N_("git branch-diff [<options>] <old-tip>...<new-tip>"),
     ++N_("git branch-diff [<options>] <base> <old-tip> <new-tip>"),
     ++NULL
      +};
     -+
     -+#define COLOR_DUAL_MODE 2
      +
      +static int parse_creation_weight(const struct option *opt, const char *arg,
      +				 int unset)
     @@ -76,11 +78,8 @@
      +
      +int cmd_branch_diff(int argc, const char **argv, const char *prefix)
      +{
     -+	int no_patches = 0;
      +	double creation_weight = 0.6;
      +	struct option options[] = {
     -+		OPT_BOOL(0, "no-patches", &no_patches,
     -+			 N_("short format (no diffs)")),
      +		{ OPTION_CALLBACK,
      +			0, "creation-weight", &creation_weight, N_("factor"),
      +			N_("Fudge factor by which creation is weighted [0.6]"),
     @@ -101,7 +100,7 @@
       git-bisect                              mainporcelain           info
       git-blame                               ancillaryinterrogators
       git-branch                              mainporcelain           history
     -+git-branch-diff                         mainporcelain           info
     ++git-branch-diff                         mainporcelain
       git-bundle                              mainporcelain
       git-cat-file                            plumbinginterrogators
       git-check-attr                          purehelpers
  2: ec51c71779a !  2: e530e450ebd branch-diff: first rudimentary implementation
     @@ -13,7 +13,7 @@
          really concentrates on getting the patch matching part right.
          
          Note: due to differences in the diff algorithm (`tbdiff` uses the
     -    Pythong module `difflib`, Git uses its xdiff fork), the cost matrix
     +    Python module `difflib`, Git uses its xdiff fork), the cost matrix
          calculated by `branch-diff` is different (but very similar) to the one
          calculated by `tbdiff`. Therefore, it is possible that they find
          different matching commits in corner cases (e.g. when a patch was split
     @@ -26,6 +26,7 @@
      +++ b/builtin/branch-diff.c
      @@
       #include "cache.h"
     + #include "builtin.h"
       #include "parse-options.h"
      +#include "string-list.h"
      +#include "run-command.h"
     @@ -35,15 +36,7 @@
      +#include "hungarian.h"
       
       static const char * const builtin_branch_diff_usage[] = {
     - 	N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),
     - 	NULL
     - };
     - 
     --#define COLOR_DUAL_MODE 2
     --
     - static int parse_creation_weight(const struct option *opt, const char *arg,
     - 				 int unset)
     - {
     + N_("git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
      @@
       	return 0;
       }
     @@ -128,7 +121,6 @@
      +				strbuf_addbuf(&buf, &line);
      +				strbuf_addch(&buf, '\n');
      +			}
     -+
      +			continue;
      +		} else if (starts_with(line.buf, "@@ "))
      +			strbuf_addstr(&buf, "@@");
     @@ -148,6 +140,7 @@
      +		util->diffsize++;
      +	}
      +	fclose(in);
     ++	strbuf_release(&line);
      +
      +	if (util)
      +		string_list_append(list, buf.buf)->util = util;
     @@ -323,7 +316,7 @@
      +
       int cmd_branch_diff(int argc, const char **argv, const char *prefix)
       {
     - 	int no_patches = 0;
     + 	double creation_weight = 0.6;
      @@
       			0, parse_creation_weight },
       		OPT_END()
     @@ -366,7 +359,7 @@
      +		strbuf_addf(&range1, "%s..%.*s", b, a_len, a);
      +		strbuf_addf(&range2, "%.*s..%s", a_len, a, b);
      +	} else {
     -+		error("Need two commit ranges");
     ++		error(_("need two commit ranges"));
      +		usage_with_options(builtin_branch_diff_usage, options);
      +	}
      +
  3: 6a618d6010f =  3: 3032e2709b8 branch-diff: improve the order of the shown commits
  4: 141e5b63e45 !  4: 12d9c7977fd branch-diff: also show the diff between patches
     @@ -4,10 +4,12 @@
          
          Just like tbdiff, we now show the diff between matching patches. This is
          a "diff of two diffs", so it can be a bit daunting to read for the
     -    beginnger.
     +    beginner.
          
     -    This brings branch-diff closer to be feature-complete with regard to
     -    tbdiff.
     +    And just like tbdiff, we now also accept the `--no-patches` option
     +    (which is actually equivalent to the diff option `-s`).
     +    
     +    This brings branch-diff closer to feature parity with regard to tbdiff.
          
          An alternative would be to display an interdiff, i.e. the hypothetical
          diff which is the result of first reverting the old diff and then
     @@ -34,7 +36,7 @@
      +#include "diffcore.h"
       
       static const char * const builtin_branch_diff_usage[] = {
     - 	N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),
     + N_("git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
      @@
       	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
       }
     @@ -82,12 +84,9 @@
       
       int cmd_branch_diff(int argc, const char **argv, const char *prefix)
       {
     --	int no_patches = 0;
     -+	struct diff_options diffopt = { 0 };
     ++	struct diff_options diffopt = { NULL };
       	double creation_weight = 0.6;
       	struct option options[] = {
     --		OPT_BOOL(0, "no-patches", &no_patches,
     --			 N_("short format (no diffs)")),
      +		OPT_SET_INT(0, "no-patches", &diffopt.output_format,
      +			    N_("short format (no diffs)"),
      +			    DIFF_FORMAT_NO_OUTPUT),
  5: 303419c56c4 =  5: 53ee6ba3873 branch-diff: right-trim commit messages
  6: 218e56a69e0 !  6: c856c460a47 branch-diff: indent the diffs just like tbdiff
     @@ -26,7 +26,7 @@
      @@
       int cmd_branch_diff(int argc, const char **argv, const char *prefix)
       {
     - 	struct diff_options diffopt = { 0 };
     + 	struct diff_options diffopt = { NULL };
      +	struct strbuf four_spaces = STRBUF_INIT;
       	double creation_weight = 0.6;
       	struct option options[] = {
  7: 00ea45123ae =  7: 35a9681a192 branch-diff: suppress the diff headers
  8: 40471263d3c !  8: 0e4c8279e46 branch-diff: adjust the output of the commit pairs
     @@ -19,7 +19,7 @@
      +#include "pretty.h"
       
       static const char * const builtin_branch_diff_usage[] = {
     - 	N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),
     + N_("git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
      @@
       	return res;
       }
  9: 9810869ced9 !  9: 2695a6abc46 branch-diff: do not show "function names" in hunk headers
     @@ -17,7 +17,7 @@
      +#include "userdiff.h"
       
       static const char * const builtin_branch_diff_usage[] = {
     - 	N_("git rebase--helper [<options>] ( A..B C..D | A...B | base A B )"),
     + N_("git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
      @@
       	return data;
       }
 10: fe12b99a0b4 ! 10: 313beeed3d1 branch-diff: add tests
     @@ -12,7 +12,7 @@
          sometimes ambiguous. In this case, a comment line and an empty line are
          added, but it is ambiguous whether they were added after the existing
          empty line, or whether an empty line and the comment line are added
     -    *before* the existing emtpy line. And apparently xdiff picks a different
     +    *before* the existing empty line. And apparently xdiff picks a different
          option here than Python's difflib.
          
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
 11: c89937afc28 = 11: ba4791918c7 branch-diff: use color for the commit pairs
 12: a94e94edf65 = 12: 1ebbe359547 color: provide inverted colors, too
 13: 153425de2df = 13: ae0ea5dfca5 diff: add an internal option to dual-color diffs of diffs
 14: 2f8017c732f ! 14: b9be01705d6 branch-diff: offer to dual-color the diffs
     @@ -18,7 +18,7 @@
      +++ b/builtin/branch-diff.c
      @@
       {
     - 	struct diff_options diffopt = { 0 };
     + 	struct diff_options diffopt = { NULL };
       	struct strbuf four_spaces = STRBUF_INIT;
      +	int dual_color = 0;
       	double creation_weight = 0.6;
 15: 62f0e2cf73f = 15: b99ab186c4f branch-diff --dual-color: work around bogus white-space warning
 16: edb34bd4f8d ! 16: 950c7537701 branch-diff: add a man page
     @@ -158,7 +158,7 @@
      +series), the cost matrix is extended to allow for that, by adding
      +fixed-cost entries for wholesale deletes/adds.
      +
     -+Example: let commits `1--2` be the first iteration of a patch series and
     ++Example: Let commits `1--2` be the first iteration of a patch series and
      +`A--C` the second iteration. Let's assume that `A` is a cherry-pick of
      +`2,` and `C` is a cherry-pick of `1` but with a small modification (say,
      +a fixed typo). Visualize the commits as a bipartite graph:
 17: 5d047a830f1 = 17: 71698f11835 completion: support branch-diff
-- 
2.17.0.409.g71698f11835


^ permalink raw reply	[flat|nested] 387+ messages in thread

* [PATCH v2 01/18] Add a function to solve least-cost assignment problems
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
@ 2018-05-04 15:34   ` Johannes Schindelin
  2018-05-05 18:24     ` Jeff King
  2018-05-30 13:55     ` SZEDER Gábor
  2018-05-04 15:34   ` [PATCH v2 02/18] Add a new builtin: branch-diff Johannes Schindelin
                     ` (20 subsequent siblings)
  21 siblings, 2 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:34 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

The Jonker-Volgenant algorithm was implemented to answer questions such
as: given two different versions of a topic branch (or iterations of a
patch series), what is the best pairing of commits/patches between the
different versions?

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Makefile    |   1 +
 hungarian.c | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 hungarian.h |  19 +++++
 3 files changed, 225 insertions(+)
 create mode 100644 hungarian.c
 create mode 100644 hungarian.h

diff --git a/Makefile b/Makefile
index 50da82b0169..96f2e76a904 100644
--- a/Makefile
+++ b/Makefile
@@ -829,6 +829,7 @@ LIB_OBJS += gpg-interface.o
 LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hashmap.o
+LIB_OBJS += hungarian.o
 LIB_OBJS += help.o
 LIB_OBJS += hex.o
 LIB_OBJS += ident.o
diff --git a/hungarian.c b/hungarian.c
new file mode 100644
index 00000000000..346299a97d9
--- /dev/null
+++ b/hungarian.c
@@ -0,0 +1,205 @@
+/*
+ * Based on: Jonker, R., & Volgenant, A. (1987). <i>A shortest augmenting path
+ * algorithm for dense and sparse linear assignment problems</i>. Computing,
+ * 38(4), 325-340.
+ */
+#include "cache.h"
+#include "hungarian.h"
+#include <float.h>
+
+#define COST(column, row) cost[(column) + column_count * (row)]
+
+/*
+ * The parameter `cost` is the cost matrix: the cost to assign column j to row
+ * i is `cost[j + column_count * i].
+ */
+int compute_assignment(int column_count, int row_count, double *cost,
+		       int *column2row, int *row2column)
+{
+	double *v = xmalloc(sizeof(double) * column_count), *d;
+	int *free_row, free_count = 0, saved_free_count, *pred, *col;
+	int i, j, phase;
+
+	memset(column2row, -1, sizeof(int) * column_count);
+	memset(row2column, -1, sizeof(int) * row_count);
+
+	/* column reduction */
+	for (j = column_count - 1; j >= 0; j--) {
+		int i1 = 0;
+
+		for (i = 1; i < row_count; i++)
+			if (COST(j, i1) > COST(j, i))
+				i1 = i;
+		v[j] = COST(j, i1);
+		if (row2column[i1] == -1) {
+			/* row i1 unassigned */
+			row2column[i1] = j;
+			column2row[j] = i1;
+		} else {
+			if (row2column[i1] >= 0)
+				row2column[i1] = -2 - row2column[i1];
+			column2row[j] = -1;
+		}
+	}
+
+	/* reduction transfer */
+	free_row = xmalloc(sizeof(int) * row_count);
+	for (int i = 0; i < row_count; i++) {
+		int j1 = row2column[i];
+		if (j1 == -1)
+			free_row[free_count++] = i;
+		else if (j1 < -1)
+			row2column[i] = -2 - j1;
+		else {
+			double min = COST(!j1, i) - v[!j1];
+			for (j = 1; j < column_count; j++)
+				if (j != j1 && min > COST(j, i) - v[j])
+					min = COST(j, i) - v[j];
+			v[j1] -= min;
+		}
+	}
+
+	if (free_count ==
+	    (column_count < row_count ? row_count - column_count : 0)) {
+		free(v);
+		free(free_row);
+		return 0;
+	}
+
+	/* augmenting row reduction */
+	for (phase = 0; phase < 2; phase++) {
+		int k = 0;
+
+		saved_free_count = free_count;
+		free_count = 0;
+		while (k < saved_free_count) {
+			double u1, u2;
+			int j1 = 0, j2, i0;
+
+			i = free_row[k++];
+			u1 = COST(j1, i) - v[j1];
+			j2 = -1;
+			u2 = DBL_MAX;
+			for (j = 1; j < column_count; j++) {
+				double c = COST(j, i) - v[j];
+				if (u2 > c) {
+					if (u1 < c) {
+						u2 = c;
+						j2 = j;
+					} else {
+						u2 = u1;
+						u1 = c;
+						j2 = j1;
+						j1 = j;
+					}
+				}
+			}
+			if (j2 < 0) {
+				j2 = j1;
+				u2 = u1;
+			}
+
+			i0 = column2row[j1];
+			if (u1 < u2)
+				v[j1] -= u2 - u1;
+			else if (i0 >= 0) {
+				j1 = j2;
+				i0 = column2row[j1];
+			}
+
+			if (i0 >= 0) {
+				if (u1 < u2)
+					free_row[--k] = i0;
+				else
+					free_row[free_count++] = i0;
+			}
+			row2column[i] = j1;
+			column2row[j1] = i;
+		}
+	}
+
+	/* augmentation */
+	saved_free_count = free_count;
+	d = xmalloc(sizeof(double) * column_count);
+	pred = xmalloc(sizeof(int) * column_count);
+	col = xmalloc(sizeof(int) * column_count);
+	for (free_count = 0; free_count < saved_free_count; free_count++) {
+		int i1 = free_row[free_count], low = 0, up = 0, last, k;
+		double min, c, u1;
+
+		for (j = 0; j < column_count; j++) {
+			d[j] = COST(j, i1) - v[j];
+			pred[j] = i1;
+			col[j] = j;
+		}
+
+		j = -1;
+		do {
+			last = low;
+			min = d[col[up++]];
+			for (k = up; k < column_count; k++) {
+				j = col[k];
+				c = d[j];
+				if (c <= min) {
+					if (c < min) {
+						up = low;
+						min = c;
+					}
+					col[k] = col[up];
+					col[up++] = j;
+				}
+			}
+			for (k = low; k < up; k++)
+				if (column2row[col[k]] == -1)
+					goto update;
+
+			/* scan a row */
+			do {
+				int j1 = col[low++];
+
+				i = column2row[j1];
+				u1 = COST(j1, i) - v[j1] - min;
+				for (k = up; k < column_count; k++) {
+					j = col[k];
+					c = COST(j, i) - v[j] - u1;
+					if (c < d[j]) {
+						d[j] = c;
+						pred[j] = i;
+						if (c == min) {
+							if (column2row[j] == -1)
+								goto update;
+							col[k] = col[up];
+							col[up++] = j;
+						}
+					}
+				}
+			} while (low != up);
+		} while (low == up);
+
+update:
+		/* updating of the column pieces */
+		for (k = 0; k < last; k++) {
+			int j1 = col[k];
+			v[j1] += d[j1] - min;
+		}
+
+		/* augmentation */
+		do {
+			if (j < 0)
+				BUG("negative j: %d", j);
+			i = pred[j];
+			column2row[j] = i;
+			k = j;
+			j = row2column[i];
+			row2column[i] = k;
+		} while (i1 != i);
+	}
+
+	free(col);
+	free(pred);
+	free(d);
+	free(v);
+	free(free_row);
+
+	return 0;
+}
diff --git a/hungarian.h b/hungarian.h
new file mode 100644
index 00000000000..e7cee4bb039
--- /dev/null
+++ b/hungarian.h
@@ -0,0 +1,19 @@
+#ifndef HUNGARIAN_H
+#define HUNGARIAN_H
+
+/*
+ * Compute an assignment of columns -> rows (and vice versa) such that every
+ * column is assigned to at most one row (and vice versa) minimizing the
+ * overall cost.
+ *
+ * The parameter `cost` is the cost matrix: the cost to assign column j to row
+ * i is `cost[j + column_count * i].
+ *
+ * The arrays column2row and row2column will be populated with the respective
+ * assignments (-1 for unassigned, which can happen only if column_count !=
+ * row_count).
+ */
+int compute_assignment(int column_count, int row_count, double *cost,
+		       int *column2row, int *row2column);
+
+#endif
-- 
2.17.0.409.g71698f11835



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
  2018-05-04 15:34   ` [PATCH v2 01/18] Add a function to solve least-cost assignment problems Johannes Schindelin
@ 2018-05-04 15:34   ` Johannes Schindelin
  2018-05-05 18:26     ` Jeff King
  2018-05-04 15:34   ` [PATCH v2 03/18] branch-diff: first rudimentary implementation Johannes Schindelin
                     ` (19 subsequent siblings)
  21 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:34 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

This builtin does not do a whole lot so far, apart from showing a usage
that is oddly similar to that of `git tbdiff`. And for a good reason:
the next commits will turn `branch-diff` into a full-blown replacement
for `tbdiff`.

At this point, we ignore tbdiff's color options as well as the
--no-patches option, as they will all be implemented later using
diff_options.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitignore            |  1 +
 Makefile              |  1 +
 builtin.h             |  1 +
 builtin/branch-diff.c | 38 ++++++++++++++++++++++++++++++++++++++
 command-list.txt      |  1 +
 git.c                 |  1 +
 6 files changed, 43 insertions(+)
 create mode 100644 builtin/branch-diff.c

diff --git a/.gitignore b/.gitignore
index 833ef3b0b78..1346a64492f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,7 @@
 /git-bisect--helper
 /git-blame
 /git-branch
+/git-branch-diff
 /git-bundle
 /git-cat-file
 /git-check-attr
diff --git a/Makefile b/Makefile
index 96f2e76a904..9b1984776d8 100644
--- a/Makefile
+++ b/Makefile
@@ -953,6 +953,7 @@ BUILTIN_OBJS += builtin/archive.o
 BUILTIN_OBJS += builtin/bisect--helper.o
 BUILTIN_OBJS += builtin/blame.o
 BUILTIN_OBJS += builtin/branch.o
+BUILTIN_OBJS += builtin/branch-diff.o
 BUILTIN_OBJS += builtin/bundle.o
 BUILTIN_OBJS += builtin/cat-file.o
 BUILTIN_OBJS += builtin/check-attr.o
diff --git a/builtin.h b/builtin.h
index 42378f3aa47..e1c4d2a529a 100644
--- a/builtin.h
+++ b/builtin.h
@@ -135,6 +135,7 @@ extern int cmd_archive(int argc, const char **argv, const char *prefix);
 extern int cmd_bisect__helper(int argc, const char **argv, const char *prefix);
 extern int cmd_blame(int argc, const char **argv, const char *prefix);
 extern int cmd_branch(int argc, const char **argv, const char *prefix);
+extern int cmd_branch_diff(int argc, const char **argv, const char *prefix);
 extern int cmd_bundle(int argc, const char **argv, const char *prefix);
 extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
 extern int cmd_checkout(int argc, const char **argv, const char *prefix);
diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
new file mode 100644
index 00000000000..60a4b4fbe30
--- /dev/null
+++ b/builtin/branch-diff.c
@@ -0,0 +1,38 @@
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+
+static const char * const builtin_branch_diff_usage[] = {
+N_("git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
+N_("git branch-diff [<options>] <old-tip>...<new-tip>"),
+N_("git branch-diff [<options>] <base> <old-tip> <new-tip>"),
+NULL
+};
+
+static int parse_creation_weight(const struct option *opt, const char *arg,
+				 int unset)
+{
+	double *d = opt->value;
+	if (unset)
+		*d = 0.6;
+	else
+		*d = atof(arg);
+	return 0;
+}
+
+int cmd_branch_diff(int argc, const char **argv, const char *prefix)
+{
+	double creation_weight = 0.6;
+	struct option options[] = {
+		{ OPTION_CALLBACK,
+			0, "creation-weight", &creation_weight, N_("factor"),
+			N_("Fudge factor by which creation is weighted [0.6]"),
+			0, parse_creation_weight },
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, NULL, options,
+			builtin_branch_diff_usage, 0);
+
+	return 0;
+}
diff --git a/command-list.txt b/command-list.txt
index a1fad28fd82..d01b9063e81 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -19,6 +19,7 @@ git-archive                             mainporcelain
 git-bisect                              mainporcelain           info
 git-blame                               ancillaryinterrogators
 git-branch                              mainporcelain           history
+git-branch-diff                         mainporcelain
 git-bundle                              mainporcelain
 git-cat-file                            plumbinginterrogators
 git-check-attr                          purehelpers
diff --git a/git.c b/git.c
index f598fae7b7a..d2794fb6f5d 100644
--- a/git.c
+++ b/git.c
@@ -377,6 +377,7 @@ static struct cmd_struct commands[] = {
 	{ "bisect--helper", cmd_bisect__helper, RUN_SETUP },
 	{ "blame", cmd_blame, RUN_SETUP },
 	{ "branch", cmd_branch, RUN_SETUP | DELAY_PAGER_CONFIG },
+	{ "branch-diff", cmd_branch_diff, RUN_SETUP | USE_PAGER },
 	{ "bundle", cmd_bundle, RUN_SETUP_GENTLY | NO_PARSEOPT },
 	{ "cat-file", cmd_cat_file, RUN_SETUP },
 	{ "check-attr", cmd_check_attr, RUN_SETUP },
-- 
2.17.0.409.g71698f11835



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v2 03/18] branch-diff: first rudimentary implementation
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
  2018-05-04 15:34   ` [PATCH v2 01/18] Add a function to solve least-cost assignment problems Johannes Schindelin
  2018-05-04 15:34   ` [PATCH v2 02/18] Add a new builtin: branch-diff Johannes Schindelin
@ 2018-05-04 15:34   ` Johannes Schindelin
  2018-05-04 15:34   ` [PATCH v2 04/18] branch-diff: improve the order of the shown commits Johannes Schindelin
                     ` (18 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:34 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

At this stage, `git branch-diff` can determine corresponding commits of
two related commit ranges. This makes use of the recently introduced
implementation of the Hungarian algorithm.

The core of this patch is a straight port of the ideas of tbdiff, the
seemingly dormant project at https://github.com/trast/tbdiff.

The output does not at all match `tbdiff`'s output yet, as this patch
really concentrates on getting the patch matching part right.

Note: due to differences in the diff algorithm (`tbdiff` uses the
Python module `difflib`, Git uses its xdiff fork), the cost matrix
calculated by `branch-diff` is different (but very similar) to the one
calculated by `tbdiff`. Therefore, it is possible that they find
different matching commits in corner cases (e.g. when a patch was split
into two patches of roughly equal length).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 335 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 334 insertions(+), 1 deletion(-)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index 60a4b4fbe30..c462681067c 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -1,6 +1,12 @@
 #include "cache.h"
 #include "builtin.h"
 #include "parse-options.h"
+#include "string-list.h"
+#include "run-command.h"
+#include "argv-array.h"
+#include "hashmap.h"
+#include "xdiff-interface.h"
+#include "hungarian.h"
 
 static const char * const builtin_branch_diff_usage[] = {
 N_("git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
@@ -20,6 +26,279 @@ static int parse_creation_weight(const struct option *opt, const char *arg,
 	return 0;
 }
 
+struct patch_util {
+	/* For the search for an exact match */
+	struct hashmap_entry e;
+	const char *diff, *patch;
+
+	int i;
+	int diffsize;
+	size_t diff_offset;
+	/* the index of the matching item in the other branch, or -1 */
+	int matching;
+	struct object_id oid;
+};
+
+/*
+ * Reads the patches into a string list, with the `util` field being populated
+ * as struct object_id (will need to be free()d).
+ */
+static int read_patches(const char *range, struct string_list *list)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+	FILE *in;
+	struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;
+	struct patch_util *util = NULL;
+	int in_header = 1;
+
+	argv_array_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
+			"--reverse", "--date-order", "--decorate=no",
+			"--no-abbrev-commit", range,
+			NULL);
+	cp.out = -1;
+	cp.no_stdin = 1;
+	cp.git_cmd = 1;
+
+	if (start_command(&cp))
+		return error_errno(_("could not start `log`"));
+	in = fdopen(cp.out, "r");
+	if (!in) {
+		error_errno(_("could not read `log` output"));
+		finish_command(&cp);
+		return -1;
+	}
+
+	while (strbuf_getline(&line, in) != EOF) {
+		const char *p;
+
+		if (skip_prefix(line.buf, "commit ", &p)) {
+			if (util) {
+				string_list_append(list, buf.buf)->util = util;
+				strbuf_reset(&buf);
+			}
+			util = xcalloc(sizeof(*util), 1);
+			if (get_oid(p, &util->oid)) {
+				error(_("could not parse commit '%s'"), p);
+				free(util);
+				string_list_clear(list, 1);
+				strbuf_release(&buf);
+				strbuf_release(&line);
+				fclose(in);
+				finish_command(&cp);
+				return -1;
+			}
+			util->matching = -1;
+			in_header = 1;
+			continue;
+		}
+
+		if (starts_with(line.buf, "diff --git")) {
+			in_header = 0;
+			strbuf_addch(&buf, '\n');
+			if (!util->diff_offset)
+				util->diff_offset = buf.len;
+			strbuf_addbuf(&buf, &line);
+		} else if (in_header) {
+			if (starts_with(line.buf, "Author: ")) {
+				strbuf_addbuf(&buf, &line);
+				strbuf_addstr(&buf, "\n\n");
+			} else if (starts_with(line.buf, "    ")) {
+				strbuf_addbuf(&buf, &line);
+				strbuf_addch(&buf, '\n');
+			}
+			continue;
+		} else if (starts_with(line.buf, "@@ "))
+			strbuf_addstr(&buf, "@@");
+		else if (line.buf[0] && !starts_with(line.buf, "index "))
+			/*
+			 * A completely blank (not ' \n', which is context)
+			 * line is not valid in a diff.  We skip it
+			 * silently, because this neatly handles the blank
+			 * separator line between commits in git-log
+			 * output.
+			 */
+			strbuf_addbuf(&buf, &line);
+		else
+			continue;
+
+		strbuf_addch(&buf, '\n');
+		util->diffsize++;
+	}
+	fclose(in);
+	strbuf_release(&line);
+
+	if (util)
+		string_list_append(list, buf.buf)->util = util;
+	strbuf_release(&buf);
+
+	if (finish_command(&cp))
+		return -1;
+
+	return 0;
+}
+
+static int patch_util_cmp(const void *dummy, const struct patch_util *a,
+		     const struct patch_util *b, const char *keydata)
+{
+	return strcmp(a->diff, keydata ? keydata : b->diff);
+}
+
+static void find_exact_matches(struct string_list *a, struct string_list *b)
+{
+	struct hashmap map;
+	int i;
+
+	hashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);
+
+	/* First, add the patches of a to a hash map */
+	for (i = 0; i < a->nr; i++) {
+		struct patch_util *util = a->items[i].util;
+
+		util->i = i;
+		util->patch = a->items[i].string;
+		util->diff = util->patch + util->diff_offset;
+		hashmap_entry_init(util, strhash(util->diff));
+		hashmap_add(&map, util);
+	}
+
+	/* Now try to find exact matches in b */
+	for (i = 0; i < b->nr; i++) {
+		struct patch_util *util = b->items[i].util, *other;
+
+		util->i = i;
+		util->patch = b->items[i].string;
+		util->diff = util->patch + util->diff_offset;
+		hashmap_entry_init(util, strhash(util->diff));
+		other = hashmap_remove(&map, util, NULL);
+		if (other) {
+			if (other->matching >= 0)
+				BUG("already assigned!");
+
+			other->matching = i;
+			util->matching = other->i;
+		}
+	}
+
+	hashmap_free(&map, 0);
+}
+
+static void diffsize_consume(void *data, char *line, unsigned long len)
+{
+	(*(int *)data)++;
+}
+
+static int diffsize(const char *a, const char *b)
+{
+	xpparam_t pp = { 0 };
+	xdemitconf_t cfg = { 0 };
+	mmfile_t mf1, mf2;
+	int count = 0;
+
+	mf1.ptr = (char *)a;
+	mf1.size = strlen(a);
+	mf2.ptr = (char *)b;
+	mf2.size = strlen(b);
+
+	cfg.ctxlen = 3;
+	if (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))
+		return count;
+
+	error(_("failed to generate diff"));
+	return INT_MAX;
+}
+
+static int get_correspondences(struct string_list *a, struct string_list *b,
+			       double creation_weight)
+{
+	int n = a->nr + b->nr;
+	double *cost = xmalloc(sizeof(double) * n * n), c;
+	int *a2b = xmalloc(sizeof(int) * n), *b2a = xmalloc(sizeof(int) * n);
+	int i, j, res;
+
+	for (i = 0; i < a->nr; i++) {
+		struct patch_util *a_util = a->items[i].util;
+
+		for (j = 0; j < b->nr; j++) {
+			struct patch_util *b_util = b->items[j].util;
+
+			if (a_util->matching == j)
+				c = 0;
+			else if (a_util->matching < 0 && b_util->matching < 0)
+				c = diffsize(a_util->diff, b_util->diff);
+			else
+				c = INT_MAX;
+			cost[i + n * j] = c;
+		}
+
+		c = a_util->matching < 0 ?
+			a_util->diffsize * creation_weight : INT_MAX;
+		for (j = b->nr; j < n; j++)
+			cost[i + n * j] = c;
+	}
+
+	for (j = 0; j < b->nr; j++) {
+		struct patch_util *util = b->items[j].util;
+
+		c = util->matching < 0 ?
+			util->diffsize * creation_weight : INT_MAX;
+		for (i = a->nr; i < n; i++)
+			cost[i + n * j] = c;
+	}
+
+	for (i = a->nr; i < n; i++)
+		for (j = b->nr; j < n; j++)
+			cost[i + n * j] = 0;
+
+	res = compute_assignment(n, n, cost, a2b, b2a);
+
+	for (i = 0; i < a->nr; i++)
+		if (a2b[i] >= 0 && a2b[i] < b->nr) {
+			struct patch_util *a_util = a->items[i].util;
+			struct patch_util *b_util = b->items[a2b[i]].util;
+
+			a_util->matching = a2b[i];
+			b_util->matching = i;
+		}
+
+	free(cost);
+	free(a2b);
+	free(b2a);
+
+	return res;
+}
+
+static const char *short_oid(struct patch_util *util)
+{
+	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
+}
+
+static void output(struct string_list *a, struct string_list *b)
+{
+	int i;
+
+	for (i = 0; i < b->nr; i++) {
+		struct patch_util *util = b->items[i].util, *prev;
+
+		if (util->matching < 0)
+			printf("-: -------- > %d: %s\n",
+					i + 1, short_oid(util));
+		else {
+			prev = a->items[util->matching].util;
+			printf("%d: %s ! %d: %s\n",
+			       util->matching + 1, short_oid(prev),
+			       i + 1, short_oid(util));
+		}
+	}
+
+	for (i = 0; i < a->nr; i++) {
+		struct patch_util *util = a->items[i].util;
+
+		if (util->matching < 0)
+			printf("%d: %s < -: --------\n",
+			       i + 1, short_oid(util));
+	}
+}
+
 int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 {
 	double creation_weight = 0.6;
@@ -30,9 +309,63 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 			0, parse_creation_weight },
 		OPT_END()
 	};
+	int res = 0;
+	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
+	struct string_list branch1 = STRING_LIST_INIT_DUP;
+	struct string_list branch2 = STRING_LIST_INIT_DUP;
 
 	argc = parse_options(argc, argv, NULL, options,
 			builtin_branch_diff_usage, 0);
 
-	return 0;
+	if (argc == 2) {
+		if (!strstr(argv[0], ".."))
+			warning(_("no .. in range: '%s'"), argv[0]);
+		strbuf_addstr(&range1, argv[0]);
+
+		if (!strstr(argv[1], ".."))
+			warning(_("no .. in range: '%s'"), argv[1]);
+		strbuf_addstr(&range2, argv[1]);
+	} else if (argc == 3) {
+		strbuf_addf(&range1, "%s..%s", argv[0], argv[1]);
+		strbuf_addf(&range2, "%s..%s", argv[0], argv[2]);
+	} else if (argc == 1) {
+		const char *b = strstr(argv[0], "..."), *a = argv[0];
+		int a_len;
+
+		if (!b)
+			die(_("single arg format requires a symmetric range"));
+
+		a_len = (int)(b - a);
+		if (!a_len) {
+			a = "HEAD";
+			a_len = strlen(a);
+		}
+		b += 3;
+		if (!*b)
+			b = "HEAD";
+		strbuf_addf(&range1, "%s..%.*s", b, a_len, a);
+		strbuf_addf(&range2, "%.*s..%s", a_len, a, b);
+	} else {
+		error(_("need two commit ranges"));
+		usage_with_options(builtin_branch_diff_usage, options);
+	}
+
+	if (read_patches(range1.buf, &branch1))
+		res = error(_("could not parse log for '%s'"), range1.buf);
+	if (!res && read_patches(range2.buf, &branch2))
+		res = error(_("could not parse log for '%s'"), range2.buf);
+
+	if (!res) {
+		find_exact_matches(&branch1, &branch2);
+		res = get_correspondences(&branch1, &branch2, creation_weight);
+		if (!res)
+			output(&branch1, &branch2);
+	}
+
+	strbuf_release(&range1);
+	strbuf_release(&range2);
+	string_list_clear(&branch1, 1);
+	string_list_clear(&branch2, 1);
+
+	return !!res;
 }
-- 
2.17.0.409.g71698f11835



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v2 04/18] branch-diff: improve the order of the shown commits
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
                     ` (2 preceding siblings ...)
  2018-05-04 15:34   ` [PATCH v2 03/18] branch-diff: first rudimentary implementation Johannes Schindelin
@ 2018-05-04 15:34   ` Johannes Schindelin
  2018-05-04 15:34   ` [PATCH v2 05/18] branch-diff: also show the diff between patches Johannes Schindelin
                     ` (17 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:34 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

This patch lets branch-diff use the same order as tbdiff.

The idea is simple: for left-to-right readers, it is natural to assume
that the branch-diff is performed between an older vs a newer version of
the branch. As such, the user is probably more interested in the
question "where did this come from?" rather than "where did that one
go?".

To that end, we list the commits in the order of the second commit range
("the newer version"), inserting the unmatched commits of the first
commit range as soon as all their predecessors have been shown.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 59 +++++++++++++++++++++++++++++--------------
 1 file changed, 40 insertions(+), 19 deletions(-)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index c462681067c..92302b1c339 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -31,7 +31,7 @@ struct patch_util {
 	struct hashmap_entry e;
 	const char *diff, *patch;
 
-	int i;
+	int i, shown;
 	int diffsize;
 	size_t diff_offset;
 	/* the index of the matching item in the other branch, or -1 */
@@ -274,28 +274,49 @@ static const char *short_oid(struct patch_util *util)
 
 static void output(struct string_list *a, struct string_list *b)
 {
-	int i;
-
-	for (i = 0; i < b->nr; i++) {
-		struct patch_util *util = b->items[i].util, *prev;
+	int i = 0, j = 0;
+
+	/*
+	 * We assume the user is really more interested in the second argument
+	 * ("newer" version). To that end, we print the output in the order of
+	 * the RHS (the `b` parameter). To put the LHS (the `a` parameter)
+	 * commits that are no longer in the RHS into a good place, we place
+	 * them once we have shown all of their predecessors in the LHS.
+	 */
+
+	while (i < a->nr || j < b->nr) {
+		struct patch_util *a_util, *b_util;
+		a_util = i < a->nr ? a->items[i].util : NULL;
+		b_util = j < b->nr ? b->items[j].util : NULL;
+
+		/* Skip all the already-shown commits from the LHS. */
+		while (i < a->nr && a_util->shown)
+			a_util = ++i < a->nr ? a->items[i].util : NULL;
+
+		/* Show unmatched LHS commit whose predecessors were shown. */
+		if (i < a->nr && a_util->matching < 0) {
+			printf("%d: %s < -: --------\n",
+			       i + 1, short_oid(a_util));
+			i++;
+			continue;
+		}
 
-		if (util->matching < 0)
+		/* Show unmatched RHS commits. */
+		while (j < b->nr && b_util->matching < 0) {
 			printf("-: -------- > %d: %s\n",
-					i + 1, short_oid(util));
-		else {
-			prev = a->items[util->matching].util;
-			printf("%d: %s ! %d: %s\n",
-			       util->matching + 1, short_oid(prev),
-			       i + 1, short_oid(util));
+			       j + 1, short_oid(b_util));
+			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
-	}
-
-	for (i = 0; i < a->nr; i++) {
-		struct patch_util *util = a->items[i].util;
 
-		if (util->matching < 0)
-			printf("%d: %s < -: --------\n",
-			       i + 1, short_oid(util));
+		/* Show matching LHS/RHS pair. */
+		if (j < b->nr) {
+			a_util = a->items[b_util->matching].util;
+			printf("%d: %s ! %d: %s\n",
+			       b_util->matching + 1, short_oid(a_util),
+			       j + 1, short_oid(b_util));
+			a_util->shown = 1;
+			j++;
+		}
 	}
 }
 
-- 
2.17.0.409.g71698f11835



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v2 05/18] branch-diff: also show the diff between patches
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
                     ` (3 preceding siblings ...)
  2018-05-04 15:34   ` [PATCH v2 04/18] branch-diff: improve the order of the shown commits Johannes Schindelin
@ 2018-05-04 15:34   ` Johannes Schindelin
  2018-05-06  1:14     ` Igor Djordjevic
  2018-05-04 15:34   ` [PATCH v2 06/18] branch-diff: right-trim commit messages Johannes Schindelin
                     ` (16 subsequent siblings)
  21 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:34 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

Just like tbdiff, we now show the diff between matching patches. This is
a "diff of two diffs", so it can be a bit daunting to read for the
beginner.

And just like tbdiff, we now also accept the `--no-patches` option
(which is actually equivalent to the diff option `-s`).

This brings branch-diff closer to feature parity with regard to tbdiff.

An alternative would be to display an interdiff, i.e. the hypothetical
diff which is the result of first reverting the old diff and then
applying the new diff.

Especially when rebasing often, an interdiff is often not feasible,
though: if the old diff cannot be applied in reverse (due to a moving
upstream), an interdiff can simply not be inferred.

Note: while we now parse diff options such as --color, the effect is not
yet the same as in tbdiff, where also the commit pairs would be colored.
This is left for a later commit.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 53 +++++++++++++++++++++++++++++++++++++++----
 1 file changed, 49 insertions(+), 4 deletions(-)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index 92302b1c339..b23d66a3b1c 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -7,6 +7,8 @@
 #include "hashmap.h"
 #include "xdiff-interface.h"
 #include "hungarian.h"
+#include "diff.h"
+#include "diffcore.h"
 
 static const char * const builtin_branch_diff_usage[] = {
 N_("git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
@@ -272,7 +274,31 @@ static const char *short_oid(struct patch_util *util)
 	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
 }
 
-static void output(struct string_list *a, struct string_list *b)
+static struct diff_filespec *get_filespec(const char *name, const char *p)
+{
+	struct diff_filespec *spec = alloc_filespec(name);
+
+	fill_filespec(spec, &null_oid, 0, 0644);
+	spec->data = (char *)p;
+	spec->size = strlen(p);
+	spec->should_munmap = 0;
+	spec->is_stdin = 1;
+
+	return spec;
+}
+
+static void patch_diff(const char *a, const char *b,
+			      struct diff_options *diffopt)
+{
+	diff_queue(&diff_queued_diff,
+		   get_filespec("a", a), get_filespec("b", b));
+
+	diffcore_std(diffopt);
+	diff_flush(diffopt);
+}
+
+static void output(struct string_list *a, struct string_list *b,
+		   struct diff_options *diffopt)
 {
 	int i = 0, j = 0;
 
@@ -314,6 +340,9 @@ static void output(struct string_list *a, struct string_list *b)
 			printf("%d: %s ! %d: %s\n",
 			       b_util->matching + 1, short_oid(a_util),
 			       j + 1, short_oid(b_util));
+			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
+				patch_diff(a->items[b_util->matching].string,
+					   b->items[j].string, diffopt);
 			a_util->shown = 1;
 			j++;
 		}
@@ -322,21 +351,37 @@ static void output(struct string_list *a, struct string_list *b)
 
 int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 {
+	struct diff_options diffopt = { NULL };
 	double creation_weight = 0.6;
 	struct option options[] = {
+		OPT_SET_INT(0, "no-patches", &diffopt.output_format,
+			    N_("short format (no diffs)"),
+			    DIFF_FORMAT_NO_OUTPUT),
 		{ OPTION_CALLBACK,
 			0, "creation-weight", &creation_weight, N_("factor"),
 			N_("Fudge factor by which creation is weighted [0.6]"),
 			0, parse_creation_weight },
 		OPT_END()
 	};
-	int res = 0;
+	int i, j, res = 0;
 	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
 	struct string_list branch1 = STRING_LIST_INIT_DUP;
 	struct string_list branch2 = STRING_LIST_INIT_DUP;
 
+	diff_setup(&diffopt);
+	diffopt.output_format = DIFF_FORMAT_PATCH;
+
 	argc = parse_options(argc, argv, NULL, options,
-			builtin_branch_diff_usage, 0);
+			builtin_branch_diff_usage, PARSE_OPT_KEEP_UNKNOWN);
+
+	for (i = j = 0; i < argc; i++) {
+		int c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);
+
+		if (!c)
+			argv[j++] = argv[i];
+	}
+	argc = j;
+	diff_setup_done(&diffopt);
 
 	if (argc == 2) {
 		if (!strstr(argv[0], ".."))
@@ -380,7 +425,7 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 		find_exact_matches(&branch1, &branch2);
 		res = get_correspondences(&branch1, &branch2, creation_weight);
 		if (!res)
-			output(&branch1, &branch2);
+			output(&branch1, &branch2, &diffopt);
 	}
 
 	strbuf_release(&range1);
-- 
2.17.0.409.g71698f11835



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v2 06/18] branch-diff: right-trim commit messages
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
                     ` (4 preceding siblings ...)
  2018-05-04 15:34   ` [PATCH v2 05/18] branch-diff: also show the diff between patches Johannes Schindelin
@ 2018-05-04 15:34   ` Johannes Schindelin
  2018-05-04 15:34   ` [PATCH v2 07/18] branch-diff: indent the diffs just like tbdiff Johannes Schindelin
                     ` (15 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:34 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

When comparing commit messages, we need to keep in mind that they are
indented by four spaces. That is, empty lines are no longer empty, but
have "trailing whitespace". When displaying them in color, that results
in those nagging red lines.

Let's just right-trim the lines in the commit message, it's not like
trailing white-space in the commit messages are important enough to care
about in branch-diff.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index b23d66a3b1c..e2337b905b1 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -105,6 +105,7 @@ static int read_patches(const char *range, struct string_list *list)
 				strbuf_addbuf(&buf, &line);
 				strbuf_addstr(&buf, "\n\n");
 			} else if (starts_with(line.buf, "    ")) {
+				strbuf_rtrim(&line);
 				strbuf_addbuf(&buf, &line);
 				strbuf_addch(&buf, '\n');
 			}
-- 
2.17.0.409.g71698f11835



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v2 07/18] branch-diff: indent the diffs just like tbdiff
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
                     ` (5 preceding siblings ...)
  2018-05-04 15:34   ` [PATCH v2 06/18] branch-diff: right-trim commit messages Johannes Schindelin
@ 2018-05-04 15:34   ` Johannes Schindelin
  2018-05-06 14:15     ` Martin Ågren
  2018-05-04 15:34   ` [PATCH v2 08/18] branch-diff: suppress the diff headers Johannes Schindelin
                     ` (14 subsequent siblings)
  21 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:34 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

The main information in the branch-diff view comes from the list of
matching and non-matching commits, the diffs are additional information.
Indenting them helps with the reading flow.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index e2337b905b1..4fc9fd74531 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -275,6 +275,11 @@ static const char *short_oid(struct patch_util *util)
 	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
 }
 
+static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)
+{
+	return data;
+}
+
 static struct diff_filespec *get_filespec(const char *name, const char *p)
 {
 	struct diff_filespec *spec = alloc_filespec(name);
@@ -353,6 +358,7 @@ static void output(struct string_list *a, struct string_list *b,
 int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 {
 	struct diff_options diffopt = { NULL };
+	struct strbuf four_spaces = STRBUF_INIT;
 	double creation_weight = 0.6;
 	struct option options[] = {
 		OPT_SET_INT(0, "no-patches", &diffopt.output_format,
@@ -371,6 +377,9 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 
 	diff_setup(&diffopt);
 	diffopt.output_format = DIFF_FORMAT_PATCH;
+	diffopt.output_prefix = output_prefix_cb;
+	strbuf_addstr(&four_spaces, "    ");
+	diffopt.output_prefix_data = &four_spaces;
 
 	argc = parse_options(argc, argv, NULL, options,
 			builtin_branch_diff_usage, PARSE_OPT_KEEP_UNKNOWN);
-- 
2.17.0.409.g71698f11835



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v2 08/18] branch-diff: suppress the diff headers
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
                     ` (6 preceding siblings ...)
  2018-05-04 15:34   ` [PATCH v2 07/18] branch-diff: indent the diffs just like tbdiff Johannes Schindelin
@ 2018-05-04 15:34   ` Johannes Schindelin
  2018-05-04 15:34   ` [PATCH v2 09/18] branch-diff: adjust the output of the commit pairs Johannes Schindelin
                     ` (13 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:34 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

When showing the diff between corresponding patches of the two branch
versions, we have to make up a fake filename to run the diff machinery.

That filename does not carry any meaningful information, hence tbdiff
suppresses it. So we should, too.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 1 +
 diff.c                | 5 ++++-
 diff.h                | 1 +
 3 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index 4fc9fd74531..ed520d6229d 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -377,6 +377,7 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 
 	diff_setup(&diffopt);
 	diffopt.output_format = DIFF_FORMAT_PATCH;
+	diffopt.flags.suppress_diff_headers = 1;
 	diffopt.output_prefix = output_prefix_cb;
 	strbuf_addstr(&four_spaces, "    ");
 	diffopt.output_prefix_data = &four_spaces;
diff --git a/diff.c b/diff.c
index 1289df4b1f9..f1bda0db3f5 100644
--- a/diff.c
+++ b/diff.c
@@ -3197,13 +3197,16 @@ static void builtin_diff(const char *name_a,
 		memset(&xpp, 0, sizeof(xpp));
 		memset(&xecfg, 0, sizeof(xecfg));
 		memset(&ecbdata, 0, sizeof(ecbdata));
+		if (o->flags.suppress_diff_headers)
+			lbl[0] = NULL;
 		ecbdata.label_path = lbl;
 		ecbdata.color_diff = want_color(o->use_color);
 		ecbdata.ws_rule = whitespace_rule(name_b);
 		if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
 			check_blank_at_eof(&mf1, &mf2, &ecbdata);
 		ecbdata.opt = o;
-		ecbdata.header = header.len ? &header : NULL;
+		if (header.len && !o->flags.suppress_diff_headers)
+			ecbdata.header = &header;
 		xpp.flags = o->xdl_opts;
 		xpp.anchors = o->anchors;
 		xpp.anchors_nr = o->anchors_nr;
diff --git a/diff.h b/diff.h
index d29560f822c..0dd6a71af60 100644
--- a/diff.h
+++ b/diff.h
@@ -94,6 +94,7 @@ struct diff_flags {
 	unsigned funccontext:1;
 	unsigned default_follow_renames:1;
 	unsigned stat_with_summary:1;
+	unsigned suppress_diff_headers:1;
 };
 
 static inline void diff_flags_or(struct diff_flags *a,
-- 
2.17.0.409.g71698f11835



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v2 09/18] branch-diff: adjust the output of the commit pairs
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
                     ` (7 preceding siblings ...)
  2018-05-04 15:34   ` [PATCH v2 08/18] branch-diff: suppress the diff headers Johannes Schindelin
@ 2018-05-04 15:34   ` Johannes Schindelin
  2018-05-04 16:25     ` Elijah Newren
  2018-05-04 15:34   ` [PATCH v2 10/18] branch-diff: do not show "function names" in hunk headers Johannes Schindelin
                     ` (12 subsequent siblings)
  21 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:34 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

This change brings branch-diff yet another step closer to feature parity
with tbdiff: it now shows the oneline, too, and indicates with `=` when
the commits have identical diffs.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 67 +++++++++++++++++++++++++++++++++++++------
 1 file changed, 58 insertions(+), 9 deletions(-)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index ed520d6229d..5b187890bdf 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -9,6 +9,8 @@
 #include "hungarian.h"
 #include "diff.h"
 #include "diffcore.h"
+#include "commit.h"
+#include "pretty.h"
 
 static const char * const builtin_branch_diff_usage[] = {
 N_("git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
@@ -270,9 +272,57 @@ static int get_correspondences(struct string_list *a, struct string_list *b,
 	return res;
 }
 
-static const char *short_oid(struct patch_util *util)
+static void output_pair_header(struct strbuf *buf,
+			       int i, struct patch_util *a_util,
+			       int j, struct patch_util *b_util)
 {
-	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
+	static char *dashes;
+	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
+	struct commit *commit;
+
+	if (!dashes) {
+		char *p;
+
+		dashes = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV));
+		for (p = dashes; *p; p++)
+			*p = '-';
+	}
+
+	strbuf_reset(buf);
+	if (i < 0)
+		strbuf_addf(buf, "-:  %s ", dashes);
+	else
+		strbuf_addf(buf, "%d:  %s ", i + 1,
+			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
+
+	if (i < 0)
+		strbuf_addch(buf, '>');
+	else if (j < 0)
+		strbuf_addch(buf, '<');
+	else if (strcmp(a_util->patch, b_util->patch))
+		strbuf_addch(buf, '!');
+	else
+		strbuf_addch(buf, '=');
+
+	if (j < 0)
+		strbuf_addf(buf, " -:  %s", dashes);
+	else
+		strbuf_addf(buf, " %d:  %s", j + 1,
+			    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));
+
+	commit = lookup_commit_reference(oid);
+	if (commit) {
+		const char *commit_buffer = get_commit_buffer(commit, NULL);
+		const char *subject;
+
+		find_commit_subject(commit_buffer, &subject);
+		strbuf_addch(buf, ' ');
+		format_subject(buf, subject, " ");
+		unuse_commit_buffer(commit, commit_buffer);
+	}
+	strbuf_addch(buf, '\n');
+
+	fwrite(buf->buf, buf->len, 1, stdout);
 }
 
 static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)
@@ -306,6 +356,7 @@ static void patch_diff(const char *a, const char *b,
 static void output(struct string_list *a, struct string_list *b,
 		   struct diff_options *diffopt)
 {
+	struct strbuf buf = STRBUF_INIT;
 	int i = 0, j = 0;
 
 	/*
@@ -327,25 +378,22 @@ static void output(struct string_list *a, struct string_list *b,
 
 		/* Show unmatched LHS commit whose predecessors were shown. */
 		if (i < a->nr && a_util->matching < 0) {
-			printf("%d: %s < -: --------\n",
-			       i + 1, short_oid(a_util));
+			output_pair_header(&buf, i, a_util, -1, NULL);
 			i++;
 			continue;
 		}
 
 		/* Show unmatched RHS commits. */
 		while (j < b->nr && b_util->matching < 0) {
-			printf("-: -------- > %d: %s\n",
-			       j + 1, short_oid(b_util));
+			output_pair_header(&buf, -1, NULL, j, b_util);
 			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
 
 		/* Show matching LHS/RHS pair. */
 		if (j < b->nr) {
 			a_util = a->items[b_util->matching].util;
-			printf("%d: %s ! %d: %s\n",
-			       b_util->matching + 1, short_oid(a_util),
-			       j + 1, short_oid(b_util));
+			output_pair_header(&buf,
+					   b_util->matching, a_util, j, b_util);
 			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
 				patch_diff(a->items[b_util->matching].string,
 					   b->items[j].string, diffopt);
@@ -353,6 +401,7 @@ static void output(struct string_list *a, struct string_list *b,
 			j++;
 		}
 	}
+	strbuf_release(&buf);
 }
 
 int cmd_branch_diff(int argc, const char **argv, const char *prefix)
-- 
2.17.0.409.g71698f11835



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v2 10/18] branch-diff: do not show "function names" in hunk headers
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
                     ` (8 preceding siblings ...)
  2018-05-04 15:34   ` [PATCH v2 09/18] branch-diff: adjust the output of the commit pairs Johannes Schindelin
@ 2018-05-04 15:34   ` Johannes Schindelin
  2018-05-04 15:34   ` [PATCH v2 11/18] branch-diff: add tests Johannes Schindelin
                     ` (11 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:34 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

We are comparing complete, formatted commit messages with patches. There
are no function names here, so stop looking for them.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index 5b187890bdf..89d75c93115 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -11,6 +11,7 @@
 #include "diffcore.h"
 #include "commit.h"
 #include "pretty.h"
+#include "userdiff.h"
 
 static const char * const builtin_branch_diff_usage[] = {
 N_("git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
@@ -330,6 +331,10 @@ static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)
 	return data;
 }
 
+static struct userdiff_driver no_func_name = {
+	.funcname = { "$^", 0 }
+};
+
 static struct diff_filespec *get_filespec(const char *name, const char *p)
 {
 	struct diff_filespec *spec = alloc_filespec(name);
@@ -339,6 +344,7 @@ static struct diff_filespec *get_filespec(const char *name, const char *p)
 	spec->size = strlen(p);
 	spec->should_munmap = 0;
 	spec->is_stdin = 1;
+	spec->driver = &no_func_name;
 
 	return spec;
 }
-- 
2.17.0.409.g71698f11835



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v2 11/18] branch-diff: add tests
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
                     ` (9 preceding siblings ...)
  2018-05-04 15:34   ` [PATCH v2 10/18] branch-diff: do not show "function names" in hunk headers Johannes Schindelin
@ 2018-05-04 15:34   ` Johannes Schindelin
  2018-05-04 15:34   ` [PATCH v2 12/18] branch-diff: use color for the commit pairs Johannes Schindelin
                     ` (10 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:34 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Thomas Rast, Junio C Hamano,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

From: Thomas Rast <tr@thomasrast.ch>

These are essentially lifted from https://github.com/trast/tbdiff, with
light touch-ups to account for the new command name.

Apart from renaming `tbdiff` to `branch-diff`, only one test case needed
to be adjusted: 11 - 'changed message'.

The underlying reason it had to be adjusted is that diff generation is
sometimes ambiguous. In this case, a comment line and an empty line are
added, but it is ambiguous whether they were added after the existing
empty line, or whether an empty line and the comment line are added
*before* the existing empty line. And apparently xdiff picks a different
option here than Python's difflib.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/.gitattributes       |   1 +
 t/t7910-branch-diff.sh | 144 ++++++++++
 t/t7910/history.export | 604 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 749 insertions(+)
 create mode 100755 t/t7910-branch-diff.sh
 create mode 100644 t/t7910/history.export

diff --git a/t/.gitattributes b/t/.gitattributes
index 3bd959ae523..af15d5aeedd 100644
--- a/t/.gitattributes
+++ b/t/.gitattributes
@@ -18,5 +18,6 @@ t[0-9][0-9][0-9][0-9]/* -whitespace
 /t5515/* eol=lf
 /t556x_common eol=lf
 /t7500/* eol=lf
+/t7910/* eol=lf
 /t8005/*.txt eol=lf
 /t9*/*.dump eol=lf
diff --git a/t/t7910-branch-diff.sh b/t/t7910-branch-diff.sh
new file mode 100755
index 00000000000..a7fece88045
--- /dev/null
+++ b/t/t7910-branch-diff.sh
@@ -0,0 +1,144 @@
+#!/bin/sh
+
+test_description='branch-diff tests'
+
+. ./test-lib.sh
+
+# Note that because of git-branch-diff's heuristics, test_commit does more
+# harm than good.  We need some real history.
+
+test_expect_success 'setup' '
+	git fast-import < "$TEST_DIRECTORY"/t7910/history.export
+'
+
+test_expect_success 'simple A..B A..C (unmodified)' '
+	git branch-diff --no-color master..topic master..unmodified >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  35b9b25 s/5/A/
+	2:  fccce22 = 2:  de345ab s/4/A/
+	3:  147e64e = 3:  9af6654 s/11/B/
+	4:  a63e992 = 4:  2901f77 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'simple B...C (unmodified)' '
+	git branch-diff --no-color topic...unmodified >actual &&
+	# same "expected" as above
+	test_cmp expected actual
+'
+
+test_expect_success 'simple A B C (unmodified)' '
+	git branch-diff --no-color master topic unmodified >actual &&
+	# same "expected" as above
+	test_cmp expected actual
+'
+
+test_expect_success 'trivial reordering' '
+	git branch-diff --no-color master topic reordered >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  aca177a s/5/A/
+	3:  147e64e = 2:  14ad629 s/11/B/
+	4:  a63e992 = 3:  ee58208 s/12/B/
+	2:  fccce22 = 4:  307b27a s/4/A/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'removed a commit' '
+	git branch-diff --no-color master topic removed >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  7657159 s/5/A/
+	2:  fccce22 < -:  ------- s/4/A/
+	3:  147e64e = 2:  43d84d3 s/11/B/
+	4:  a63e992 = 3:  a740396 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'added a commit' '
+	git branch-diff --no-color master topic added >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  2716022 s/5/A/
+	2:  fccce22 = 2:  b62accd s/4/A/
+	-:  ------- > 3:  df46cfa s/6/A/
+	3:  147e64e = 4:  3e64548 s/11/B/
+	4:  a63e992 = 5:  12b4063 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'new base, A B C' '
+	git branch-diff --no-color master topic rebased >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  cc9c443 s/5/A/
+	2:  fccce22 = 2:  c5d9641 s/4/A/
+	3:  147e64e = 3:  28cc2b6 s/11/B/
+	4:  a63e992 = 4:  5628ab7 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'new base, B...C' '
+	# this syntax includes the commits from master!
+	git branch-diff --no-color topic...rebased >actual &&
+	cat >expected <<-EOF &&
+	-:  ------- > 1:  a31b12e unrelated
+	1:  4de457d = 2:  cc9c443 s/5/A/
+	2:  fccce22 = 3:  c5d9641 s/4/A/
+	3:  147e64e = 4:  28cc2b6 s/11/B/
+	4:  a63e992 = 5:  5628ab7 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'changed commit' '
+	git branch-diff --no-color topic...changed >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  a4b3333 s/5/A/
+	2:  fccce22 = 2:  f51d370 s/4/A/
+	3:  147e64e ! 3:  0559556 s/11/B/
+	    @@ -10,7 +10,7 @@
+	      9
+	      10
+	     -11
+	    -+B
+	    ++BB
+	      12
+	      13
+	      14
+	4:  a63e992 ! 4:  d966c5c s/12/B/
+	    @@ -8,7 +8,7 @@
+	     @@
+	      9
+	      10
+	    - B
+	    + BB
+	     -12
+	     +B
+	      13
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'changed message' '
+	git branch-diff --no-color topic...changed-message >actual &&
+	sed s/Z/\ /g >expected <<-EOF &&
+	1:  4de457d = 1:  f686024 s/5/A/
+	2:  fccce22 ! 2:  4ab067d s/4/A/
+	    @@ -2,6 +2,8 @@
+	    Z
+	    Z    s/4/A/
+	    Z
+	    +    Also a silly comment here!
+	    +
+	    Zdiff --git a/file b/file
+	    Z--- a/file
+	    Z+++ b/file
+	3:  147e64e = 3:  b9cb956 s/11/B/
+	4:  a63e992 = 4:  8add5f1 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_done
diff --git a/t/t7910/history.export b/t/t7910/history.export
new file mode 100644
index 00000000000..b8ffff0940d
--- /dev/null
+++ b/t/t7910/history.export
@@ -0,0 +1,604 @@
+blob
+mark :1
+data 51
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+reset refs/heads/removed
+commit refs/heads/removed
+mark :2
+author Thomas Rast <trast@inf.ethz.ch> 1374424921 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374484724 +0200
+data 8
+initial
+M 100644 :1 file
+
+blob
+mark :3
+data 51
+1
+2
+3
+4
+A
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :4
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+blob
+mark :5
+data 51
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :6
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+data 7
+s/4/A/
+from :4
+M 100644 :5 file
+
+blob
+mark :7
+data 50
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+B
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :8
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+data 8
+s/11/B/
+from :6
+M 100644 :7 file
+
+blob
+mark :9
+data 49
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+B
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :10
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+data 8
+s/12/B/
+from :8
+M 100644 :9 file
+
+blob
+mark :11
+data 10
+unrelated
+
+commit refs/heads/master
+mark :12
+author Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200
+data 10
+unrelated
+from :2
+M 100644 :11 otherfile
+
+commit refs/heads/rebased
+mark :13
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485137 +0200
+data 7
+s/5/A/
+from :12
+M 100644 :3 file
+
+commit refs/heads/rebased
+mark :14
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
+data 7
+s/4/A/
+from :13
+M 100644 :5 file
+
+commit refs/heads/rebased
+mark :15
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
+data 8
+s/11/B/
+from :14
+M 100644 :7 file
+
+commit refs/heads/rebased
+mark :16
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
+data 8
+s/12/B/
+from :15
+M 100644 :9 file
+
+commit refs/heads/added
+mark :17
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/added
+mark :18
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 7
+s/4/A/
+from :17
+M 100644 :5 file
+
+blob
+mark :19
+data 51
+1
+2
+3
+A
+A
+A
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/added
+mark :20
+author Thomas Rast <trast@inf.ethz.ch> 1374485186 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 7
+s/6/A/
+from :18
+M 100644 :19 file
+
+blob
+mark :21
+data 50
+1
+2
+3
+A
+A
+A
+7
+8
+9
+10
+B
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/added
+mark :22
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 8
+s/11/B/
+from :20
+M 100644 :21 file
+
+blob
+mark :23
+data 49
+1
+2
+3
+A
+A
+A
+7
+8
+9
+10
+B
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/added
+mark :24
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 8
+s/12/B/
+from :22
+M 100644 :23 file
+
+commit refs/heads/reordered
+mark :25
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+blob
+mark :26
+data 50
+1
+2
+3
+4
+A
+6
+7
+8
+9
+10
+B
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/reordered
+mark :27
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 8
+s/11/B/
+from :25
+M 100644 :26 file
+
+blob
+mark :28
+data 49
+1
+2
+3
+4
+A
+6
+7
+8
+9
+10
+B
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/reordered
+mark :29
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 8
+s/12/B/
+from :27
+M 100644 :28 file
+
+commit refs/heads/reordered
+mark :30
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 7
+s/4/A/
+from :29
+M 100644 :9 file
+
+commit refs/heads/changed
+mark :31
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/changed
+mark :32
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 7
+s/4/A/
+from :31
+M 100644 :5 file
+
+blob
+mark :33
+data 51
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+BB
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/changed
+mark :34
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 8
+s/11/B/
+from :32
+M 100644 :33 file
+
+blob
+mark :35
+data 50
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+BB
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/changed
+mark :36
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 8
+s/12/B/
+from :34
+M 100644 :35 file
+
+commit refs/heads/changed-message
+mark :37
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/changed-message
+mark :38
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200
+data 35
+s/4/A/
+
+Also a silly comment here!
+from :37
+M 100644 :5 file
+
+commit refs/heads/changed-message
+mark :39
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200
+data 8
+s/11/B/
+from :38
+M 100644 :7 file
+
+commit refs/heads/changed-message
+mark :40
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200
+data 8
+s/12/B/
+from :39
+M 100644 :9 file
+
+commit refs/heads/unmodified
+mark :41
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/unmodified
+mark :42
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200
+data 7
+s/4/A/
+from :41
+M 100644 :5 file
+
+commit refs/heads/unmodified
+mark :43
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200
+data 8
+s/11/B/
+from :42
+M 100644 :7 file
+
+commit refs/heads/unmodified
+mark :44
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200
+data 8
+s/12/B/
+from :43
+M 100644 :9 file
+
+commit refs/heads/removed
+mark :45
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/removed
+mark :46
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
+data 8
+s/11/B/
+from :45
+M 100644 :26 file
+
+commit refs/heads/removed
+mark :47
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
+data 8
+s/12/B/
+from :46
+M 100644 :28 file
+
+reset refs/heads/removed
+from :47
+
-- 
2.17.0.409.g71698f11835



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v2 12/18] branch-diff: use color for the commit pairs
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
                     ` (10 preceding siblings ...)
  2018-05-04 15:34   ` [PATCH v2 11/18] branch-diff: add tests Johannes Schindelin
@ 2018-05-04 15:34   ` Johannes Schindelin
  2018-05-05 23:48     ` Todd Zullinger
  2018-05-04 15:34   ` [PATCH v2 13/18] color: provide inverted colors, too Johannes Schindelin
                     ` (9 subsequent siblings)
  21 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:34 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

Arguably the most important part of branch-diff's output is the list of
commits in the two branches, together with their relationships.

For that reason, tbdiff introduced color-coding that is pretty
intuitive, especially for unchanged patches (all dim yellow, like the
first line in `git show`'s output) vs modified patches (old commit is
red, new commit is green). Let's imitate that color scheme.

While at it, also copy tbdiff's change of the fragment color to magenta.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 49 +++++++++++++++++++++++++++++++------------
 1 file changed, 36 insertions(+), 13 deletions(-)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index 89d75c93115..04efd30f0f6 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -273,13 +273,19 @@ static int get_correspondences(struct string_list *a, struct string_list *b,
 	return res;
 }
 
-static void output_pair_header(struct strbuf *buf,
+static void output_pair_header(struct diff_options *diffopt, struct strbuf *buf,
 			       int i, struct patch_util *a_util,
 			       int j, struct patch_util *b_util)
 {
 	static char *dashes;
 	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
 	struct commit *commit;
+	char status;
+	const char *color_reset = diff_get_color_opt(diffopt, DIFF_RESET);
+	const char *color_old = diff_get_color_opt(diffopt, DIFF_FILE_OLD);
+	const char *color_new = diff_get_color_opt(diffopt, DIFF_FILE_NEW);
+	const char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT);
+	const char *color;
 
 	if (!dashes) {
 		char *p;
@@ -289,21 +295,33 @@ static void output_pair_header(struct strbuf *buf,
 			*p = '-';
 	}
 
+	if (j < 0) {
+		color = color_old;
+		status = '<';
+	} else if (i < 0) {
+		color = color_new;
+		status = '>';
+	} else if (strcmp(a_util->patch, b_util->patch)) {
+		color = color_commit;
+		status = '!';
+	} else {
+		color = color_commit;
+		status = '=';
+	}
+
 	strbuf_reset(buf);
+	strbuf_addstr(buf, status == '!' ? color_old : color);
 	if (i < 0)
 		strbuf_addf(buf, "-:  %s ", dashes);
 	else
 		strbuf_addf(buf, "%d:  %s ", i + 1,
 			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
 
-	if (i < 0)
-		strbuf_addch(buf, '>');
-	else if (j < 0)
-		strbuf_addch(buf, '<');
-	else if (strcmp(a_util->patch, b_util->patch))
-		strbuf_addch(buf, '!');
-	else
-		strbuf_addch(buf, '=');
+	if (status == '!')
+		strbuf_addf(buf, "%s%s", color_reset, color);
+	strbuf_addch(buf, status);
+	if (status == '!')
+		strbuf_addf(buf, "%s%s", color_reset, color_new);
 
 	if (j < 0)
 		strbuf_addf(buf, " -:  %s", dashes);
@@ -316,12 +334,15 @@ static void output_pair_header(struct strbuf *buf,
 		const char *commit_buffer = get_commit_buffer(commit, NULL);
 		const char *subject;
 
+		if (status == '!')
+			strbuf_addf(buf, "%s%s", color_reset, color);
+
 		find_commit_subject(commit_buffer, &subject);
 		strbuf_addch(buf, ' ');
 		format_subject(buf, subject, " ");
 		unuse_commit_buffer(commit, commit_buffer);
 	}
-	strbuf_addch(buf, '\n');
+	strbuf_addf(buf, "%s\n", color_reset);
 
 	fwrite(buf->buf, buf->len, 1, stdout);
 }
@@ -384,21 +405,21 @@ static void output(struct string_list *a, struct string_list *b,
 
 		/* Show unmatched LHS commit whose predecessors were shown. */
 		if (i < a->nr && a_util->matching < 0) {
-			output_pair_header(&buf, i, a_util, -1, NULL);
+			output_pair_header(diffopt, &buf, i, a_util, -1, NULL);
 			i++;
 			continue;
 		}
 
 		/* Show unmatched RHS commits. */
 		while (j < b->nr && b_util->matching < 0) {
-			output_pair_header(&buf, -1, NULL, j, b_util);
+			output_pair_header(diffopt, &buf, -1, NULL, j, b_util);
 			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
 
 		/* Show matching LHS/RHS pair. */
 		if (j < b->nr) {
 			a_util = a->items[b_util->matching].util;
-			output_pair_header(&buf,
+			output_pair_header(diffopt, &buf,
 					   b_util->matching, a_util, j, b_util);
 			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
 				patch_diff(a->items[b_util->matching].string,
@@ -430,6 +451,8 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 	struct string_list branch1 = STRING_LIST_INIT_DUP;
 	struct string_list branch2 = STRING_LIST_INIT_DUP;
 
+	git_diff_basic_config("diff.color.frag", "magenta", NULL);
+
 	diff_setup(&diffopt);
 	diffopt.output_format = DIFF_FORMAT_PATCH;
 	diffopt.flags.suppress_diff_headers = 1;
-- 
2.17.0.409.g71698f11835



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v2 13/18] color: provide inverted colors, too
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
                     ` (11 preceding siblings ...)
  2018-05-04 15:34   ` [PATCH v2 12/18] branch-diff: use color for the commit pairs Johannes Schindelin
@ 2018-05-04 15:34   ` Johannes Schindelin
  2018-05-05 18:29     ` Jeff King
  2018-05-04 15:34   ` [PATCH v2 14/18] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin
                     ` (8 subsequent siblings)
  21 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:34 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

For every regular color, there exists the inverted equivalent where
background and foreground colors are exchanged.

We will use this in the next commit to allow inverting *just* the +/-
signs in a diff.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 color.h | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/color.h b/color.h
index cd0bcedd084..f0984b09583 100644
--- a/color.h
+++ b/color.h
@@ -36,6 +36,12 @@ struct strbuf;
 #define GIT_COLOR_BOLD_BLUE	"\033[1;34m"
 #define GIT_COLOR_BOLD_MAGENTA	"\033[1;35m"
 #define GIT_COLOR_BOLD_CYAN	"\033[1;36m"
+#define GIT_COLOR_INV_RED	"\033[7;31m"
+#define GIT_COLOR_INV_GREEN	"\033[7;32m"
+#define GIT_COLOR_INV_YELLOW	"\033[7;33m"
+#define GIT_COLOR_INV_BLUE	"\033[7;34m"
+#define GIT_COLOR_INV_MAGENTA	"\033[7;35m"
+#define GIT_COLOR_INV_CYAN	"\033[7;36m"
 #define GIT_COLOR_BG_RED	"\033[41m"
 #define GIT_COLOR_BG_GREEN	"\033[42m"
 #define GIT_COLOR_BG_YELLOW	"\033[43m"
-- 
2.17.0.409.g71698f11835



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v2 14/18] diff: add an internal option to dual-color diffs of diffs
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
                     ` (12 preceding siblings ...)
  2018-05-04 15:34   ` [PATCH v2 13/18] color: provide inverted colors, too Johannes Schindelin
@ 2018-05-04 15:34   ` Johannes Schindelin
  2018-05-04 15:35   ` [PATCH v2 15/18] branch-diff: offer to dual-color the diffs Johannes Schindelin
                     ` (7 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:34 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

When diffing diffs, it can be quite daunting to figure out what the heck
is going on, as there are nested +/- signs.

Let's make this easier by adding a flag in diff_options that allows
color-coding the outer diff sign with inverted colors, so that the
preimage and postimage is colored like the diff it is.

Of course, this really only makes sense when the preimage and postimage
*are* diffs. So let's not expose this flag via a command-line option for
now.

This is a feature that was invented by git-tbdiff, and it will be used
in `branch-diff` in the next commit.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 diff.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++---------
 diff.h |  5 ++++-
 2 files changed, 59 insertions(+), 11 deletions(-)

diff --git a/diff.c b/diff.c
index f1bda0db3f5..98a41e88620 100644
--- a/diff.c
+++ b/diff.c
@@ -67,6 +67,8 @@ static char diff_colors[][COLOR_MAXLEN] = {
 	GIT_COLOR_BOLD_YELLOW,	/* NEW_MOVED ALTERNATIVE */
 	GIT_COLOR_FAINT,	/* NEW_MOVED_DIM */
 	GIT_COLOR_FAINT_ITALIC,	/* NEW_MOVED_ALTERNATIVE_DIM */
+	GIT_COLOR_INV_RED,	/* OLD_INV */
+	GIT_COLOR_INV_GREEN,	/* NEW_INV */
 };
 
 static NORETURN void die_want_option(const char *option_name)
@@ -108,6 +110,10 @@ static int parse_diff_color_slot(const char *var)
 		return DIFF_FILE_NEW_MOVED_DIM;
 	if (!strcasecmp(var, "newmovedalternativedimmed"))
 		return DIFF_FILE_NEW_MOVED_ALT_DIM;
+	if (!strcasecmp(var, "oldinv"))
+		return DIFF_FILE_OLD_INV;
+	if (!strcasecmp(var, "newinv"))
+		return DIFF_FILE_NEW_INV;
 	return -1;
 }
 
@@ -577,7 +583,10 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res
 	int nofirst;
 	FILE *file = o->file;
 
-	fputs(diff_line_prefix(o), file);
+	if (first)
+		fputs(diff_line_prefix(o), file);
+	else if (!len)
+		return;
 
 	if (len == 0) {
 		has_trailing_newline = (first == '\n');
@@ -596,7 +605,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res
 
 	if (len || !nofirst) {
 		fputs(set, file);
-		if (!nofirst)
+		if (first && !nofirst)
 			fputc(first, file);
 		fwrite(line, len, 1, file);
 		fputs(reset, file);
@@ -970,7 +979,8 @@ static void dim_moved_lines(struct diff_options *o)
 
 static void emit_line_ws_markup(struct diff_options *o,
 				const char *set, const char *reset,
-				const char *line, int len, char sign,
+				const char *line, int len,
+				const char *set_sign, char sign,
 				unsigned ws_rule, int blank_at_eof)
 {
 	const char *ws = NULL;
@@ -981,14 +991,18 @@ static void emit_line_ws_markup(struct diff_options *o,
 			ws = NULL;
 	}
 
-	if (!ws)
+	if (!ws && set_sign == set)
 		emit_line_0(o, set, reset, sign, line, len);
-	else if (blank_at_eof)
+	else if (!ws) {
+		/* Emit just the prefix, then the rest. */
+		emit_line_0(o, set_sign, reset, sign, "", 0);
+		emit_line_0(o, set, reset, 0, line, len);
+	} else if (blank_at_eof)
 		/* Blank line at EOF - paint '+' as well */
 		emit_line_0(o, ws, reset, sign, line, len);
 	else {
 		/* Emit just the prefix, then the rest. */
-		emit_line_0(o, set, reset, sign, "", 0);
+		emit_line_0(o, set_sign, reset, sign, "", 0);
 		ws_check_emit(line, len, ws_rule,
 			      o->file, set, reset, ws);
 	}
@@ -998,7 +1012,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 					 struct emitted_diff_symbol *eds)
 {
 	static const char *nneof = " No newline at end of file\n";
-	const char *context, *reset, *set, *meta, *fraginfo;
+	const char *context, *reset, *set, *set_sign, *meta, *fraginfo;
 	struct strbuf sb = STRBUF_INIT;
 
 	enum diff_symbol s = eds->s;
@@ -1038,7 +1052,16 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 	case DIFF_SYMBOL_CONTEXT:
 		set = diff_get_color_opt(o, DIFF_CONTEXT);
 		reset = diff_get_color_opt(o, DIFF_RESET);
-		emit_line_ws_markup(o, set, reset, line, len, ' ',
+		set_sign = set;
+		if (o->flags.dual_color_diffed_diffs) {
+			char c = !len ? 0 : line[0];
+
+			if (c == '+')
+				set = diff_get_color_opt(o, DIFF_FILE_NEW);
+			else if (c == '-')
+				set = diff_get_color_opt(o, DIFF_FILE_OLD);
+		}
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',
 				    flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);
 		break;
 	case DIFF_SYMBOL_PLUS:
@@ -1065,7 +1088,18 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 			set = diff_get_color_opt(o, DIFF_FILE_NEW);
 		}
 		reset = diff_get_color_opt(o, DIFF_RESET);
-		emit_line_ws_markup(o, set, reset, line, len, '+',
+		if (!o->flags.dual_color_diffed_diffs)
+			set_sign = set;
+		else {
+			char c = !len ? 0 : line[0];
+
+			set_sign = diff_get_color_opt(o, DIFF_FILE_NEW_INV);
+			if (c == '-')
+				set = diff_get_color_opt(o, DIFF_FILE_OLD);
+			else if (c != '+')
+				set = diff_get_color_opt(o, DIFF_CONTEXT);
+		}
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK,
 				    flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);
 		break;
@@ -1093,7 +1127,18 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 			set = diff_get_color_opt(o, DIFF_FILE_OLD);
 		}
 		reset = diff_get_color_opt(o, DIFF_RESET);
-		emit_line_ws_markup(o, set, reset, line, len, '-',
+		if (!o->flags.dual_color_diffed_diffs)
+			set_sign = set;
+		else {
+			char c = !len ? 0 : line[0];
+
+			set_sign = diff_get_color_opt(o, DIFF_FILE_OLD_INV);
+			if (c == '+')
+				set = diff_get_color_opt(o, DIFF_FILE_NEW);
+			else if (c != '-')
+				set = diff_get_color_opt(o, DIFF_CONTEXT);
+		}
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, '-',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);
 		break;
 	case DIFF_SYMBOL_WORDS_PORCELAIN:
diff --git a/diff.h b/diff.h
index 0dd6a71af60..c3e5d27967c 100644
--- a/diff.h
+++ b/diff.h
@@ -95,6 +95,7 @@ struct diff_flags {
 	unsigned default_follow_renames:1;
 	unsigned stat_with_summary:1;
 	unsigned suppress_diff_headers:1;
+	unsigned dual_color_diffed_diffs:1;
 };
 
 static inline void diff_flags_or(struct diff_flags *a,
@@ -242,7 +243,9 @@ enum color_diff {
 	DIFF_FILE_NEW_MOVED = 13,
 	DIFF_FILE_NEW_MOVED_ALT = 14,
 	DIFF_FILE_NEW_MOVED_DIM = 15,
-	DIFF_FILE_NEW_MOVED_ALT_DIM = 16
+	DIFF_FILE_NEW_MOVED_ALT_DIM = 16,
+	DIFF_FILE_OLD_INV = 17,
+	DIFF_FILE_NEW_INV = 18
 };
 const char *diff_get_color(int diff_use_color, enum color_diff ix);
 #define diff_get_color_opt(o, ix) \
-- 
2.17.0.409.g71698f11835



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v2 15/18] branch-diff: offer to dual-color the diffs
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
                     ` (13 preceding siblings ...)
  2018-05-04 15:34   ` [PATCH v2 14/18] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin
@ 2018-05-04 15:35   ` Johannes Schindelin
  2018-05-04 15:35   ` [PATCH v2 16/18] branch-diff --dual-color: work around bogus white-space warning Johannes Schindelin
                     ` (6 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:35 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

When showing what changed between old and new commits, we show a diff of
the patches. This diff is a diff between diffs, therefore there are
nested +/- signs, and it can be relatively hard to understand what is
going on.

With the --dual-color option, the preimage and the postimage are colored
like the diffs they are, and the *outer* +/- sign is inverted for
clarity.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/branch-diff.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
index 04efd30f0f6..8a16352e3a1 100644
--- a/builtin/branch-diff.c
+++ b/builtin/branch-diff.c
@@ -435,8 +435,11 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 {
 	struct diff_options diffopt = { NULL };
 	struct strbuf four_spaces = STRBUF_INIT;
+	int dual_color = 0;
 	double creation_weight = 0.6;
 	struct option options[] = {
+		OPT_BOOL(0, "dual-color", &dual_color,
+			    N_("color both diff and diff-between-diffs")),
 		OPT_SET_INT(0, "no-patches", &diffopt.output_format,
 			    N_("short format (no diffs)"),
 			    DIFF_FORMAT_NO_OUTPUT),
@@ -472,6 +475,11 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
 	argc = j;
 	diff_setup_done(&diffopt);
 
+	if (dual_color) {
+		diffopt.use_color = 1;
+		diffopt.flags.dual_color_diffed_diffs = 1;
+	}
+
 	if (argc == 2) {
 		if (!strstr(argv[0], ".."))
 			warning(_("no .. in range: '%s'"), argv[0]);
-- 
2.17.0.409.g71698f11835



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v2 16/18] branch-diff --dual-color: work around bogus white-space warning
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
                     ` (14 preceding siblings ...)
  2018-05-04 15:35   ` [PATCH v2 15/18] branch-diff: offer to dual-color the diffs Johannes Schindelin
@ 2018-05-04 15:35   ` Johannes Schindelin
  2018-05-04 15:35   ` [PATCH v2 17/18] branch-diff: add a man page Johannes Schindelin
                     ` (5 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:35 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

When displaying a diff of diffs, it is possible that there is an outer
`+` before a context line. That happens when the context changed between
old and new commit. When that context line starts with a tab (after the
space that marks it as context line), our diff machinery spits out a
white-space error (space before tab), but in this case, that is
incorrect.

Work around this by detecting that situation and simply *not* printing
the space in that case.

This is slightly improper a fix because it is conceivable that an
output_prefix might be configured with *just* the right length to let
that tab jump to a different tab stop depending whether we emit that
space or not.

However, the proper fix would be relatively ugly and intrusive because
it would have to *weaken* the WS_SPACE_BEFORE_TAB option in ws.c.
Besides, we do not expose the --dual-color option in cases other than
the `branch-diff` command, which only uses a hard-coded output_prefix of
four spaces (which misses the problem by one column ;-)).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 diff.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/diff.c b/diff.c
index 98a41e88620..b98a18fe014 100644
--- a/diff.c
+++ b/diff.c
@@ -1098,6 +1098,12 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 				set = diff_get_color_opt(o, DIFF_FILE_OLD);
 			else if (c != '+')
 				set = diff_get_color_opt(o, DIFF_CONTEXT);
+			/* Avoid space-before-tab warning */
+			if (c == ' ' && (len < 2 || line[1] == '\t' ||
+					 line[1] == '\r' || line[1] == '\n')) {
+				line++;
+				len--;
+			}
 		}
 		emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK,
-- 
2.17.0.409.g71698f11835



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v2 17/18] branch-diff: add a man page
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
                     ` (15 preceding siblings ...)
  2018-05-04 15:35   ` [PATCH v2 16/18] branch-diff --dual-color: work around bogus white-space warning Johannes Schindelin
@ 2018-05-04 15:35   ` Johannes Schindelin
  2018-05-04 15:35   ` [PATCH v2 18/18] completion: support branch-diff Johannes Schindelin
                     ` (4 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:35 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

This is a heavily butchered version of the README written by Thomas
Rast and Thomas Gummerer, lifted from https://github.com/trast/tbdiff.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/git-branch-diff.txt | 239 ++++++++++++++++++++++++++++++
 1 file changed, 239 insertions(+)
 create mode 100644 Documentation/git-branch-diff.txt

diff --git a/Documentation/git-branch-diff.txt b/Documentation/git-branch-diff.txt
new file mode 100644
index 00000000000..f9e23eaf721
--- /dev/null
+++ b/Documentation/git-branch-diff.txt
@@ -0,0 +1,239 @@
+git-branch-diff(1)
+==================
+
+NAME
+----
+git-branch-diff - Compare two versions of a branch
+
+SYNOPSIS
+--------
+[verse]
+'git branch-diff' [--color=[<when>]] [--no-color] [<diff-options>]
+	[--dual-color] [--no-patches] [--creation-weight=<weight>]
+	( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
+
+DESCRIPTION
+-----------
+
+This command shows the differences between two versions of a patch
+series, or more generally, two commit ranges (ignoring merges).
+
+To that end, it first finds pairs of commits from both commit ranges
+that correspond with each other. Two commits are said to correspond when
+the diff between their patches (i.e. the author information, the commit
+message and the commit diff) is reasonably small compared to the
+patches' size. See ``Algorithm` below for details.
+
+Finally, the list of matching commits is shown in the order of the
+second commit range, with unmatched commits being inserted just after
+all of their ancestors have been shown.
+
+
+OPTIONS
+-------
+--no-patches::
+	Suppress the diffs between commit pairs that were deemed to
+	correspond; only show the pairings.
+
+--dual-color::
+	When the commit diffs differ, recreate the original diffs'
+	coloring, and add outer -/+ diff markers with the *background*
+	being red/green to make it easier to see e.g. when there was a
+	change in what exact lines were added.
+
+--creation-weight=<factor>::
+	Set the creation/deletion cost fudge factor to `<factor>`.
+	Defaults to 0.6. Try a larger value if `git branch-diff`
+	erroneously considers a large change a total rewrite (deletion
+	of one commit and addition of another), and a smaller one in
+	the reverse case. See the ``Algorithm`` section below for an
+	explanation why this is needed.
+
+<range1> <range2>::
+	Compare the commits specified by the two ranges, where
+	`<range1>` is considered an older version of `<range2>`.
+
+<rev1>...<rev2>::
+	Equivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.
+
+<base> <rev1> <rev2>::
+	Equivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.
+	Note that `<base>` does not need to be the exact branch point
+	of the branches. Example: after rebasing a branch `my-topic`,
+	`git branch-diff my-topic@{u} my-topic@{1} my-topic` would
+	show the differences introduced by the rebase.
+
+`git branch-diff` also accepts the regular diff options (see
+linkgit:git-diff[1]), most notably the `--color=[<when>]` and
+`--no-color` options. These options are used when generating the "diff
+between patches", i.e. to compare the author, commit message and diff of
+corresponding old/new commits. There is currently no means to tweak the
+diff options passed to `git log` when generating those patches.
+
+
+CONFIGURATION
+-------------
+This command uses the `diff.color.*` and `pager.branch-diff` settings
+(the latter is on by default).
+See linkgit:git-config[1].
+
+
+Examples
+--------
+
+When a rebase required merge conflicts to be resolved, compare the changes
+introduced by the rebase directly afterwards using:
+
+------------
+$ git branch-diff @{u} @{1} @
+------------
+
+
+A typical output of `git branch-diff` would look like this:
+
+------------
+-:  ------- > 1:  0ddba11 Prepare for the inevitable!
+1:  c0debee = 2:  cab005e Add a helpful message at the start
+2:  f00dbal ! 3:  decafe1 Describe a bug
+    @@ -1,3 +1,3 @@
+     Author: A U Thor <author@example.com>
+
+    -TODO: Describe a bug
+    +Describe a bug
+    @@ -324,5 +324,6
+      This is expected.
+
+    -+What is unexpected is that it will also crash.
+    ++Unexpectedly, it also crashes. This is a bug, and the jury is
+    ++still out there how to fix it best. See ticket #314 for details.
+
+      Contact
+3:  bedead < -:  ------- TO-UNDO
+------------
+
+In this example, there are 3 old and 3 new commits, where the developer
+removed the 3rd, added a new one before the first two, and modified the
+commit message of the 2nd commit as well its diff.
+
+When the output goes to a terminal, it is color-coded by default, just
+like regular `git diff`'s output. In addition, the first line (adding a
+commit) is green, the last line (deleting a commit) is red, the second
+line (with a perfect match) is yellow like the commit header of `git
+show`'s output, and the third line colors the old commit red, the new
+one green and the rest like `git show`'s commit header.
+
+The color-coded diff is actually a bit hard to read, though, as it
+colors the entire lines red or green. The line that added "What is
+unexpected" in the old commit, for example, is completely red, even if
+the intent of the old commit was to add something.
+
+To help with that, use the `--dual-color` mode. In this mode, the diff
+of diffs will retain the original diff colors, and prefix the lines with
+-/+ markers that have their *background* red or green, to make it more
+obvious that they describe how the diff itself changed.
+
+
+Algorithm
+---------
+
+The general idea is this: we generate a cost matrix between the commits
+in both commit ranges, then solve the least-cost assignment.
+
+To avoid false positives (e.g. when a patch has been removed, and an
+unrelated patch has been added between two iterations of the same patch
+series), the cost matrix is extended to allow for that, by adding
+fixed-cost entries for wholesale deletes/adds.
+
+Example: Let commits `1--2` be the first iteration of a patch series and
+`A--C` the second iteration. Let's assume that `A` is a cherry-pick of
+`2,` and `C` is a cherry-pick of `1` but with a small modification (say,
+a fixed typo). Visualize the commits as a bipartite graph:
+
+------------
+    1            A
+
+    2            B
+
+		 C
+------------
+
+We are looking for a "best" explanation of the new series in terms of
+the old one. We can represent an "explanation" as an edge in the graph:
+
+
+------------
+    1            A
+	       /
+    2 --------'  B
+
+		 C
+------------
+
+This explanation comes for "free" because there was no change. Similarly
+`C` could be explained using `1`, but that comes at some cost c>0
+because of the modification:
+
+------------
+    1 ----.      A
+	  |    /
+    2 ----+---'  B
+	  |
+	  `----- C
+	  c>0
+------------
+
+In mathematical terms, what we are looking for is some sort of a minimum
+cost bipartite matching; `1` is matched to `C` at some cost, etc. The
+underlying graph is in fact a complete bipartite graph; the cost we
+associate with every edge is the size of the diff between the two
+commits' patches. To explain also new commits, we introduce dummy nodes
+on both sides:
+
+------------
+    1 ----.      A
+	  |    /
+    2 ----+---'  B
+	  |
+    o     `----- C
+	  c>0
+    o            o
+
+    o            o
+------------
+
+The cost of an edge `o--C` is the size of `C`'s diff, modified by a
+fudge factor that should be smaller than 1.0. The cost of an edge `o--o`
+is free. The fudge factor is necessary because even if `1` and `C` have
+nothing in common, they may still share a few empty lines and such,
+possibly making the assignment `1--C`, `o--o` slightly cheaper than
+`1--o`, `o--C` even if `1` and `C` have nothing in common. With the
+fudge factor we require a much larger common part to consider patches as
+corresponding.
+
+The overall time needed to compute this algorithm is the time needed to
+compute n+m commit diffs and then n*m diffs of patches, plus the time
+needed to compute the least-cost assigment between n and m diffs. Git
+uses an implementation of the Jonker-Volgenant algorithm to solve the
+assignment problem, which has cubic runtime complexity. The matching
+found in this case will look like this:
+
+------------
+    1 ----.      A
+	  |    /
+    2 ----+---'  B
+       .--+-----'
+    o -'  `----- C
+	  c>0
+    o ---------- o
+
+    o ---------- o
+------------
+
+
+SEE ALSO
+--------
+linkgit:git-log[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
-- 
2.17.0.409.g71698f11835



^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v2 18/18] completion: support branch-diff
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
                     ` (16 preceding siblings ...)
  2018-05-04 15:35   ` [PATCH v2 17/18] branch-diff: add a man page Johannes Schindelin
@ 2018-05-04 15:35   ` Johannes Schindelin
  2018-05-06  8:24     ` Duy Nguyen
  2018-05-04 16:21   ` [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike Elijah Newren
                     ` (3 subsequent siblings)
  21 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-04 15:35 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

Tab completion of `branch-diff` is very convenient, especially given
that the revision arguments that need to be passed to `git branch-diff`
are typically more complex than, say, your grandfather's `git log`
arguments.

Without this patch, we would only complete the `branch-diff` part but
not the options and other arguments.

This of itself may already be slightly disruptive for well-trained
fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to
`git branch origin/master`, as we now no longer automatically append a
space after completing `git branch`: this is now ambiguous.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 contrib/completion/git-completion.bash | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 01dd9ff07a2..45addd525ac 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1496,6 +1496,24 @@ _git_format_patch ()
 	__git_complete_revlist
 }
 
+__git_branch_diff_options="
+	--no-patches --creation-weight= --dual-color
+"
+
+_git_branch_diff ()
+{
+	case "$cur" in
+	--*)
+		__gitcomp "
+			$__git_branch_diff_options
+			$__git_diff_common_options
+			"
+		return
+		;;
+	esac
+	__git_complete_revlist
+}
+
 _git_fsck ()
 {
 	case "$cur" in
-- 
2.17.0.409.g71698f11835

^ permalink raw reply related	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-04  6:40         ` Johannes Schindelin
@ 2018-05-04 15:37           ` Ramsay Jones
  2018-05-05 19:41             ` Johannes Schindelin
  2018-05-04 16:34           ` Elijah Newren
  1 sibling, 1 reply; 387+ messages in thread
From: Ramsay Jones @ 2018-05-04 15:37 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason



On 04/05/18 07:40, Johannes Schindelin wrote:
[snip] 
> BTW I ran `make sparse` for the first time, and it spits out tons of
> stuff. And I notice that they are all non-fatal warnings, but so were the
> ones you pointed out above. This is a bit sad, as I would *love* to
> install a VSTS build job to run `make sparse` automatically. Examples of
> warnings *after* applying your patch:
> 
> connect.c:481:40: warning: incorrect type in argument 2 (invalid types)
> connect.c:481:40:    expected union __CONST_SOCKADDR_ARG [usertype] __addr
> connect.c:481:40:    got struct sockaddr *ai_addr
> 
> or
> 
> pack-revindex.c:65:23: warning: memset with byte count of 262144
> 
> What gives?

My stock answer, until recently, was that you are using a very
old version of sparse. Which is probably still true here - but
I recently noticed that more up-to-date platforms/gcc versions
also have many problems. (The main sparse contributors tend to
stick with conservative distros and/or don't use sparse on any
software that uses system headers - thus they tend not to notice
the problems caused by new gcc/glibc versions! ;-) )

Since I am on Linux Mint 18.3 (based on the last Ubuntu LTS) and
build sparse from source, the current 'master', 'next' and 'pu'
branches are all 'sparse-clean' for me. (On cygwin, which is
built with NO_REGEX, I have a single sparse warning).

I was just about to say that, unusually for me, I was using a
sparse built from a release tag, but then remembered that I have
some additional patches which fixes a problem on fedora 27!
Using sparse on fedora 27 is otherwise useless. (There are still
many warnings spewed on f27 - but they are caused by incorrect
system headers :( ).

The current release of sparse is v0.5.2, which probably hasn't
been included in any distro yet (I think the previous release
v0.5.1, which should also work for you, is in Debian unstable).
If you wanted to try building a more up-to-date sparse, the repo
is at: git://git.kernel.org/pub/scm/devel/sparse/sparse.git.

Linux Mint 19, which will be released in about a month, will be
using the Ubuntu 18.04 LTS as a base, so I guess it is possible
that I will need to debug sparse again ...

BTW, I spent some time last night playing with 'git-branch-diff'.

First of all - Good Job! This tool will be very useful (thanks
also go to Thomas, of course).

I noticed that there seemed to be an occasional 'whitespace error'
indicator (red background) directly after an +/- change character
which I suspect is an error (I haven't actually checked). However,
this indicator disappears if you add the --dual-color option.

Thanks!

ATB,
Ramsay Jones

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
                     ` (17 preceding siblings ...)
  2018-05-04 15:35   ` [PATCH v2 18/18] completion: support branch-diff Johannes Schindelin
@ 2018-05-04 16:21   ` Elijah Newren
  2018-05-04 16:30     ` Elijah Newren
  2018-05-05 20:03     ` Johannes Schindelin
  2018-05-06  5:22   ` Junio C Hamano
                     ` (2 subsequent siblings)
  21 siblings, 2 replies; 387+ messages in thread
From: Elijah Newren @ 2018-05-04 16:21 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git Mailing List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

On Fri, May 4, 2018 at 8:34 AM, Johannes Schindelin
<johannes.schindelin@gmx.de> wrote:
> 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.

tbdiff is awesome; thanks for bringing it in as a builtin to git.

I've run through a few cases, comparing output of tbdiff and
branch-diff.  So far, what I've noted is that they produce largely the
same output except that:

- tbdiff seems to shorten shas to 7 characters, branch-diff is using
10, in git.git at least.  (Probably a good change)
- tbdiff aligned output columns better when there were more than 9
patches (I'll comment more on patch 09/18)
- As noted elsewhere in the review of round 1, tbdiff uses difflib
while branch-diff uses xdiff.  I found some cases where that mattered,
and in all of them, I either felt like the difference was irrelevant
or that difflib was suboptimal, so this is definitely an improvement
for me.
- branch-diff produces it's output faster, and it is automatically
paged.  This is really cool.

Also, I don't have bash-completion for either tbdiff or branch-diff.
:-(  But I saw some discussion on the v1 patches about how this gets
handled...  :-)


Elijah

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 09/18] branch-diff: adjust the output of the commit pairs
  2018-05-04 15:34   ` [PATCH v2 09/18] branch-diff: adjust the output of the commit pairs Johannes Schindelin
@ 2018-05-04 16:25     ` Elijah Newren
  0 siblings, 0 replies; 387+ messages in thread
From: Elijah Newren @ 2018-05-04 16:25 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git Mailing List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Dscho,

On Fri, May 4, 2018 at 8:34 AM, Johannes Schindelin
<johannes.schindelin@gmx.de> wrote:
> This change brings branch-diff yet another step closer to feature parity
> with tbdiff: it now shows the oneline, too, and indicates with `=` when
> the commits have identical diffs.
>
<snip>
> @@ -270,9 +272,57 @@ static int get_correspondences(struct string_list *a, struct string_list *b,
>         return res;
>  }
>
> -static const char *short_oid(struct patch_util *util)
> +static void output_pair_header(struct strbuf *buf,
> +                              int i, struct patch_util *a_util,
> +                              int j, struct patch_util *b_util)
>  {
> -       return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
> +       static char *dashes;
> +       struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
> +       struct commit *commit;
> +
> +       if (!dashes) {
> +               char *p;
> +
> +               dashes = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV));
> +               for (p = dashes; *p; p++)
> +                       *p = '-';
> +       }
> +
> +       strbuf_reset(buf);
> +       if (i < 0)
> +               strbuf_addf(buf, "-:  %s ", dashes);
> +       else
> +               strbuf_addf(buf, "%d:  %s ", i + 1,


One nice thing tbdiff did was to right align patch numbers (which also
helped align other columns in the output).  So, for example when there
are more than 9 patches I would see output like:

...
 8: a980de43fd =  8: 362ab315ac directory rename detection: testcases
exploring possibly suboptimal merges
 9: 3633e79ed9 =  9: 792e1371d9 directory rename detection:
miscellaneous testcases to complete coverage
10: e10d07ef40 = 10: a0b0a15103 directory rename detection: tests for
handling overwriting untracked files
11: f6d84b503e = 11: a7a436042a directory rename detection: tests for
handling overwriting dirty files
...

whereas branch-diff here is instead giving output of the form

...
8:  a980de43fd = 8:  362ab315ac directory rename detection: testcases
exploring possibly suboptimal merges
9:  3633e79ed9 = 9:  792e1371d9 directory rename detection:
miscellaneous testcases to complete coverage
10:  e10d07ef40 = 10:  a0b0a15103 directory rename detection: tests
for handling overwriting untracked files
11:  f6d84b503e = 11:  a7a436042a directory rename detection: tests
for handling overwriting dirty files
...


Not a critical difference, but it'd be nice to match tbdiff here all the same.

> +                           find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
> +
> +       if (i < 0)
> +               strbuf_addch(buf, '>');
> +       else if (j < 0)
> +               strbuf_addch(buf, '<');
> +       else if (strcmp(a_util->patch, b_util->patch))
> +               strbuf_addch(buf, '!');
> +       else
> +               strbuf_addch(buf, '=');
> +
> +       if (j < 0)
> +               strbuf_addf(buf, " -:  %s", dashes);
> +       else
> +               strbuf_addf(buf, " %d:  %s", j + 1,

Same comment on these last two strbuf_addf's about alignment.



Elijah

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-04 16:21   ` [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike Elijah Newren
@ 2018-05-04 16:30     ` Elijah Newren
  2018-05-05 20:03     ` Johannes Schindelin
  1 sibling, 0 replies; 387+ messages in thread
From: Elijah Newren @ 2018-05-04 16:30 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git Mailing List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi,

On Fri, May 4, 2018 at 9:21 AM, Elijah Newren <newren@gmail.com> wrote:
> On Fri, May 4, 2018 at 8:34 AM, Johannes Schindelin
> <johannes.schindelin@gmx.de> wrote:
>> 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.
>
> tbdiff is awesome; thanks for bringing it in as a builtin to git.
>
> I've run through a few cases, comparing output of tbdiff and
> branch-diff.  So far, what I've noted is that they produce largely the
> same output except that:
>
> - tbdiff seems to shorten shas to 7 characters, branch-diff is using
> 10, in git.git at least.  (Probably a good change)

Sorry, a quick self-correction here:

tbdiff, when using an actual shortened sha, uses 10 characters.  But
when a patch doesn't have a match, tbdiff seems to use seven dashes on
one side in lieu of a shortened sha, whereas branch-diff will use 10
characters whether it has an actual shortened sha or is just putting a
bunch of dashes there.  So, this is definitely a good change.

> - tbdiff aligned output columns better when there were more than 9
> patches (I'll comment more on patch 09/18)
> - As noted elsewhere in the review of round 1, tbdiff uses difflib
> while branch-diff uses xdiff.  I found some cases where that mattered,
> and in all of them, I either felt like the difference was irrelevant
> or that difflib was suboptimal, so this is definitely an improvement
> for me.
> - branch-diff produces it's output faster, and it is automatically
> paged.  This is really cool.
>
> Also, I don't have bash-completion for either tbdiff or branch-diff.
> :-(  But I saw some discussion on the v1 patches about how this gets
> handled...  :-)

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-04  6:40         ` Johannes Schindelin
  2018-05-04 15:37           ` Ramsay Jones
@ 2018-05-04 16:34           ` Elijah Newren
  2018-05-05 20:24             ` Johannes Schindelin
  1 sibling, 1 reply; 387+ messages in thread
From: Elijah Newren @ 2018-05-04 16:34 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Ramsay Jones, Git Mailing List, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

On Thu, May 3, 2018 at 11:40 PM, Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:
> I actually have a hacky script to fixup commits in a patch series. It lets
> me stage part of the current changes, then figures out which of the
> commits' changes overlap with the staged changed. If there is only one
> commit, it automatically commits with --fixup, otherwise it lets me choose
> which one I want to fixup (giving me the list of candidates).

Ooh, interesting.  Are you willing to share said hacky script by chance?

(And as a total aside, I found your apply-from-public-inbox.sh script
and really like it.  Thanks for making it public.)

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 01/18] Add a function to solve least-cost assignment problems
  2018-05-04 15:34   ` [PATCH v2 01/18] Add a function to solve least-cost assignment problems Johannes Schindelin
@ 2018-05-05 18:24     ` Jeff King
  2018-05-05 21:55       ` Johannes Schindelin
  2018-05-30 13:55     ` SZEDER Gábor
  1 sibling, 1 reply; 387+ messages in thread
From: Jeff King @ 2018-05-05 18:24 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

On Fri, May 04, 2018 at 05:34:29PM +0200, Johannes Schindelin wrote:

> The Jonker-Volgenant algorithm was implemented to answer questions such
> as: given two different versions of a topic branch (or iterations of a
> patch series), what is the best pairing of commits/patches between the
> different versions?

I love git-tbdiff, so I'm excited to see it getting more exposure (and a
speedup). Thanks for working on this!

Two minor nits on this patch:

> +/*
> + * The parameter `cost` is the cost matrix: the cost to assign column j to row
> + * i is `cost[j + column_count * i].
> + */
> +int compute_assignment(int column_count, int row_count, double *cost,
> +		       int *column2row, int *row2column)
> +{
> +	double *v = xmalloc(sizeof(double) * column_count), *d;

Please use st_mult, xcalloc, or ALLOC_ARRAY here to avoid unchecked
multiplication in an allocation. This is probably hard to exploit in
practice (since you'd need sizeof(size_t)/8 columns, which presumably
requires allocating some heavier-weight struct per item). But it makes
auditing easier if we avoid the pattern altogether.

> +/*
> + * Compute an assignment of columns -> rows (and vice versa) such that every
> + * column is assigned to at most one row (and vice versa) minimizing the
> + * overall cost.
> + *
> + * The parameter `cost` is the cost matrix: the cost to assign column j to row
> + * i is `cost[j + column_count * i].
> + *
> + * The arrays column2row and row2column will be populated with the respective
> + * assignments (-1 for unassigned, which can happen only if column_count !=
> + * row_count).
> + */
> +int compute_assignment(int column_count, int row_count, double *cost,
> +		       int *column2row, int *row2column);

It looks like this always returns zero. Is there a ever a case where we
would return an error if this? If not, should it just be void?

-Peff

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-04 15:34   ` [PATCH v2 02/18] Add a new builtin: branch-diff Johannes Schindelin
@ 2018-05-05 18:26     ` Jeff King
  2018-05-05 21:57       ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Jeff King @ 2018-05-05 18:26 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

On Fri, May 04, 2018 at 05:34:32PM +0200, Johannes Schindelin wrote:

> This builtin does not do a whole lot so far, apart from showing a usage
> that is oddly similar to that of `git tbdiff`. And for a good reason:
> the next commits will turn `branch-diff` into a full-blown replacement
> for `tbdiff`.

One minor point about the name: will it become annoying as a tab
completion conflict with git-branch?

It feels really petty complaining about the name, but I just want to
raise the point, since it will never be easier to change than right now.

(And no, I don't really have another name in mind; I'm just wondering if
"subset" names like this might be a mild annoyance in the long run).

-Peff

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 13/18] color: provide inverted colors, too
  2018-05-04 15:34   ` [PATCH v2 13/18] color: provide inverted colors, too Johannes Schindelin
@ 2018-05-05 18:29     ` Jeff King
  2018-05-05 22:03       ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Jeff King @ 2018-05-05 18:29 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

On Fri, May 04, 2018 at 05:34:58PM +0200, Johannes Schindelin wrote:

> For every regular color, there exists the inverted equivalent where
> background and foreground colors are exchanged.
> 
> We will use this in the next commit to allow inverting *just* the +/-
> signs in a diff.

There's a "reverse" attribute (which we already parse and support) that
can do this without having to repeat the colors. AFAIK it's well
supported everywhere, but I could be wrong.

I wonder if that would make configuring this slightly more pleasant,
since it saves the user having to define "oldinv" whenever they change
"old".

-Peff

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-04 15:37           ` Ramsay Jones
@ 2018-05-05 19:41             ` Johannes Schindelin
  2018-05-09 16:24               ` Ramsay Jones
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-05 19:41 UTC (permalink / raw)
  To: Ramsay Jones
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Ramsay,

On Fri, 4 May 2018, Ramsay Jones wrote:

> On 04/05/18 07:40, Johannes Schindelin wrote:
> [snip] 
> > BTW I ran `make sparse` for the first time, and it spits out tons of
> > stuff. And I notice that they are all non-fatal warnings, but so were the
> > ones you pointed out above. This is a bit sad, as I would *love* to
> > install a VSTS build job to run `make sparse` automatically. Examples of
> > warnings *after* applying your patch:
> > 
> > connect.c:481:40: warning: incorrect type in argument 2 (invalid types)
> > connect.c:481:40:    expected union __CONST_SOCKADDR_ARG [usertype] __addr
> > connect.c:481:40:    got struct sockaddr *ai_addr
> > 
> > or
> > 
> > pack-revindex.c:65:23: warning: memset with byte count of 262144
> > 
> > What gives?
> 
> My stock answer, until recently, was that you are using a very
> old version of sparse.

Sure. I did this in an Ubuntu 16.04 LTS VM, via `sudo apt-get install
sparse`.

> Which is probably still true here - but I recently noticed that more
> up-to-date platforms/gcc versions also have many problems. (The main
> sparse contributors tend to stick with conservative distros and/or don't
> use sparse on any software that uses system headers - thus they tend not
> to notice the problems caused by new gcc/glibc versions! ;-) )
> 
> Since I am on Linux Mint 18.3 (based on the last Ubuntu LTS) and build
> sparse from source, the current 'master', 'next' and 'pu' branches are
> all 'sparse-clean' for me. (On cygwin, which is built with NO_REGEX, I
> have a single sparse warning).
> 
> I was just about to say that, unusually for me, I was using a sparse
> built from a release tag, but then remembered that I have some
> additional patches which fixes a problem on fedora 27!  Using sparse on
> fedora 27 is otherwise useless. (There are still many warnings spewed on
> f27 - but they are caused by incorrect system headers :( ).
> 
> The current release of sparse is v0.5.2, which probably hasn't been
> included in any distro yet (I think the previous release v0.5.1, which
> should also work for you, is in Debian unstable).  If you wanted to try
> building a more up-to-date sparse, the repo is at:
> git://git.kernel.org/pub/scm/devel/sparse/sparse.git.

Well, what I would want to do is let the cloud work for me. By adding an
automated build to my Visual Studio Team Services (VSTS) account, of
course, as I have "cloud privilege" (i.e. I work in the organization
providing the service, so I get to play with all of it for free).

So I really don't want to build sparse every time a new revision needs to
be tested (whether that be from one of my branches, an internal PR for
pre-review of patches to be sent to the mailing list, or maybe even `pu`
or the personalized branches on https://github.com/gitster/git).

I really would need a ready-to-install sparse, preferably as light-weight
as possible (by not requiring any dependencies outside what is available
in VSTS' hosted Linux build agents.

Maybe there is a specific apt source for sparse?

> Linux Mint 19, which will be released in about a month, will be
> using the Ubuntu 18.04 LTS as a base, so I guess it is possible
> that I will need to debug sparse again ...

:-)

> BTW, I spent some time last night playing with 'git-branch-diff'.

Great!

> First of all - Good Job! This tool will be very useful (thanks
> also go to Thomas, of course).

Both Thomases. Thomas Rast and Thomas Gummerer.

> I noticed that there seemed to be an occasional 'whitespace error'
> indicator (red background) directly after an +/- change character
> which I suspect is an error (I haven't actually checked). However,
> this indicator disappears if you add the --dual-color option.

Indeed. This is a quirk of the whitespace error paired with diffing diffs:
the whitespace error correctly treats the leading space as marker for a
context line, but if you diff diffs, the next character may still be a
marker for a context line (but this time the "inner" diff). And our
whitespace error detection mechanism cannot guess that it looks at a diff
of diffs.

However, in dual-color mode, we know that we will almost certainly look at
diffs of diffs (except if the change is in the commit message, in which
case I don't care about whitespace errors at all, anyway).

So I have this hack in 16/18:
https://public-inbox.org/git/b99ab186c4f11239a10950b9902d9c87d0e60513.1525448066.git.johannes.schindelin@gmx.de/T/#u

Essentially, I extend the dual-color mode to detect where such a bogus
whitespace error would be detected, and simply *skip the space*! I can get
away with that because dual-color is meant for human consumption, and if a
horizontal tab follows, it would not matter whether there was a space: it
would find the same tab stop. Likewise, if the space comes before a CR or
LF, or even just before the final NUL, the space can be safely omitted
from the output, too.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* [PATCH v3 19/20] range-diff: left-pad patch numbers
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
                       ` (15 preceding siblings ...)
  2018-05-03 14:44     ` [PATCH v3 18/20] completion: support `git range-diff` Johannes Schindelin via GitGitGadget
@ 2018-05-05 19:52     ` Johannes Schindelin via GitGitGadget
  2018-05-06 15:26     ` [PATCH v3 05/20] range-diff: also show the diff between patches Johannes Schindelin via GitGitGadget
                       ` (3 subsequent siblings)
  20 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-05-05 19:52 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

As pointed out by Elijah Newren, tbdiff has this neat little alignment
trick where it outputs the commit pairs with patch numbers that are
padded to the maximal patch number's width:

	  1: cafedead =   1: acefade first patch
	[...]
	314: beefeada < 314: facecab up to PI!

Let's do the same in range-diff, too.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 21 +++++++++++++--------
 1 file changed, 13 insertions(+), 8 deletions(-)

diff --git a/range-diff.c b/range-diff.c
index 870c3680c..d3e51bf36 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -254,7 +254,8 @@ static void get_correspondences(struct string_list *a, struct string_list *b,
 	free(b2a);
 }
 
-static void output_pair_header(struct diff_options *diffopt, struct strbuf *buf,
+static void output_pair_header(struct diff_options *diffopt, int patch_no_width,
+			       struct strbuf *buf,
 			       struct patch_util *a_util,
 			       struct patch_util *b_util)
 {
@@ -293,9 +294,9 @@ static void output_pair_header(struct diff_options *diffopt, struct strbuf *buf,
 	strbuf_reset(buf);
 	strbuf_addstr(buf, status == '!' ? color_old : color);
 	if (!a_util)
-		strbuf_addf(buf, "-:  %s ", dashes);
+		strbuf_addf(buf, "%*s:  %s ", patch_no_width, "-", dashes);
 	else
-		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
+		strbuf_addf(buf, "%*d:  %s ", patch_no_width, a_util->i + 1,
 			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
 
 	if (status == '!')
@@ -305,9 +306,9 @@ static void output_pair_header(struct diff_options *diffopt, struct strbuf *buf,
 		strbuf_addf(buf, "%s%s", color_reset, color_new);
 
 	if (!b_util)
-		strbuf_addf(buf, " -:  %s", dashes);
+		strbuf_addf(buf, " %*s:  %s", patch_no_width, "-", dashes);
 	else
-		strbuf_addf(buf, " %d:  %s", b_util->i + 1,
+		strbuf_addf(buf, " %*d:  %s", patch_no_width, b_util->i + 1,
 			    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));
 
 	commit = lookup_commit_reference(oid);
@@ -360,6 +361,7 @@ static void output(struct string_list *a, struct string_list *b,
 		   struct diff_options *diffopt)
 {
 	struct strbuf buf = STRBUF_INIT;
+	int patch_no_width = decimal_width(1 + (a->nr > b->nr ? a->nr : b->nr));
 	int i = 0, j = 0;
 
 	/*
@@ -381,21 +383,24 @@ static void output(struct string_list *a, struct string_list *b,
 
 		/* Show unmatched LHS commit whose predecessors were shown. */
 		if (i < a->nr && a_util->matching < 0) {
-			output_pair_header(diffopt, &buf, a_util, NULL);
+			output_pair_header(diffopt, patch_no_width, &buf,
+					   a_util, NULL);
 			i++;
 			continue;
 		}
 
 		/* Show unmatched RHS commits. */
 		while (j < b->nr && b_util->matching < 0) {
-			output_pair_header(diffopt, &buf, NULL, b_util);
+			output_pair_header(diffopt, patch_no_width, &buf,
+					   NULL, b_util);
 			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
 
 		/* Show matching LHS/RHS pair. */
 		if (j < b->nr) {
 			a_util = a->items[b_util->matching].util;
-			output_pair_header(diffopt, &buf, a_util, b_util);
+			output_pair_header(diffopt, patch_no_width, &buf,
+					   a_util, b_util);
 			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
 				patch_diff(a->items[b_util->matching].string,
 					   b->items[j].string, diffopt);
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-04 16:21   ` [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike Elijah Newren
  2018-05-04 16:30     ` Elijah Newren
@ 2018-05-05 20:03     ` Johannes Schindelin
  2018-05-07 17:07       ` Elijah Newren
  1 sibling, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-05 20:03 UTC (permalink / raw)
  To: Elijah Newren
  Cc: Git Mailing List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Elijah,

On Fri, 4 May 2018, Elijah Newren wrote:

> On Fri, May 4, 2018 at 8:34 AM, Johannes Schindelin
> <johannes.schindelin@gmx.de> wrote:
> > 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.
> 
> tbdiff is awesome; thanks for bringing it in as a builtin to git.

You're welcome.

> I've run through a few cases, comparing output of tbdiff and
> branch-diff.  So far, what I've noted is that they produce largely the
> same output except that:
> 
> - tbdiff seems to shorten shas to 7 characters, branch-diff is using
> 10, in git.git at least.  (Probably a good change)

Yes, it is a good change ;-)

> - tbdiff aligned output columns better when there were more than 9
> patches (I'll comment more on patch 09/18)

I added a new patch to align the patch numbers specifically. I considered
squashing it into 9/18, but decided against it: it will make it easier to
read through the rationale when calling `git annotate` on those lines.

> - As noted elsewhere in the review of round 1, tbdiff uses difflib
> while branch-diff uses xdiff.  I found some cases where that mattered,
> and in all of them, I either felt like the difference was irrelevant
> or that difflib was suboptimal, so this is definitely an improvement
> for me.

Indeed. It is more or less ambiguities that get resolved differently.

> - branch-diff produces it's output faster, and it is automatically
> paged.  This is really cool.

:-)

It was actually the paging that made the most difference for me. The `git
tbdiff` command broke for me as soon as I switched on the pager, as tbdiff
got confused by the decoration (AEvar had put up a PR to fix that, but
that PR has not even so much as been answered in the meantime, so I
thought it'd be a good time to rewrite the entire shebang in C, also
because I could not use tbdiff *at all* on Windows due to its hefty
dependencies).

> Also, I don't have bash-completion for either tbdiff or branch-diff.
> :-(  But I saw some discussion on the v1 patches about how this gets
> handled...  :-)

Oh? Does 18/18 not work for you?
https://public-inbox.org/git/71698f11835311c103aae565a2a761d10f4676b9.1525448066.git.johannes.schindelin@gmx.de/

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-04 16:34           ` Elijah Newren
@ 2018-05-05 20:24             ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-05 20:24 UTC (permalink / raw)
  To: Elijah Newren
  Cc: Ramsay Jones, Git Mailing List, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason

Hi Elijah,

On Fri, 4 May 2018, Elijah Newren wrote:

> On Thu, May 3, 2018 at 11:40 PM, Johannes Schindelin
> <Johannes.Schindelin@gmx.de> wrote:
> > I actually have a hacky script to fixup commits in a patch series. It lets
> > me stage part of the current changes, then figures out which of the
> > commits' changes overlap with the staged changed. If there is only one
> > commit, it automatically commits with --fixup, otherwise it lets me choose
> > which one I want to fixup (giving me the list of candidates).
> 
> Ooh, interesting.  Are you willing to share said hacky script by chance?

It is part of a real huge hacky script of pretty much all things I add as
aliases, so I extracted the relevant part for you:

-- snip --
#!/bin/sh

fixup () { # [--upstream=<branch>] [--not=<tip-to-skip>]
	upstream=
	not=
	while case "$1" in
	--upstream) shift; upstream="$1";;
	--upstream=*) upstream="${1#*=}";;
	--not) shift; not="$not $1";;
	--not=*) not="$not ${1#*=}";;
	-*) die "Unknown option: $1";;
	*) break;;
	esac; do shift; done

	test $# -le 1 ||
	die "Need 0 or 1 commit"

	! git diff-index --cached --quiet --ignore-submodules HEAD -- || {
		git update-index --ignore-submodules --refresh
	        ! git diff-files --quiet --ignore-submodules -- ||
		die "No changes"

		git add -p ||
		exit
	}
	! git diff-index --cached --quiet --ignore-submodules HEAD -- ||
	die "No staged changes"

	test $# = 1 || {
		if test -z "$upstream"
		then
			upstream="$(git rev-parse --symbolic-full-name \
				HEAD@{upstream} 2> /dev/null)" &&
			test "$(git rev-parse HEAD)" != \
				"$(git rev-parse $upstream)" ||
			upstream=origin/master
		fi

		revs="$(git rev-list $upstream.. --not $not --)" ||
		die "Could not get commits between $upstream and HEAD"

		test -n "$revs" ||
		die "No commits between $upstream and HEAD"

		while count=$(test -z "$revs" && echo 0 || echo "$revs" | wc -l | tr -dc 0-9) &&
			test $count -gt 1
		do
			printf '\nMultiple candidates:\n'
			echo $revs | xargs git log --no-walk --oneline | cat -n

			read input
			case "$input" in
			[0-9]|[0-9][0-9]|[0-9][0-9][0-9])
				revs="$(echo "$revs" | sed -n "${input}p")"
				count=1
				break
				;;
			h|hh)
				revs=$(history_of_staged_changes $upstream..)
				continue
				;;
			hhhh)
				history_of_staged_changes -p $upstream..
				continue
				;;
			p)
				git log -p --no-walk $revs
				continue
				;;
			[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])
				revs=$input
				continue
			esac
			revs="$(git rev-list --no-walk --grep="$input" $revs)"
		done

		test $count = 1 ||
		die "No commit given"

		set $revs
	}

	git commit --fixup "$1"
	message="$(git show -s --format=%s%n%n%b)"
	case "$message" in
	'fixup! fixup! '*)
		message="${message#fixup! }"
		message="${message#fixup! }"
		message="${message#fixup! }"
		git commit --amend -m "fixup! $message"
		;;
	esac
}

history_of_staged_changes () { # <commit-range>
	pretty=
	if test "a-p" = "a$1"
	then
		pretty=-p
		shift
	fi

	test $# -le 1 ||
	die "Usage: $0 <commit-range>"

	test $# = 1 || {
		upstream=$(git rev-parse --verify HEAD@{u} 2>/dev/null) ||
		upstream=origin/master
		set $upstream..
	}

	args=$(for file in $(git diff --no-color --cached --name-only)
		do
			for hunk in $(get_hunks --cached -- "$file")
			do
				hunk1=${hunk%:*}
				start1=${hunk1%,*}
				end1=$(($start1+${hunk1#*,}-1))
				echo "'$file:$start1-$end1'"
			done
		done)

	test -n "$args" ||
	die "No staged files!"

	eval hunk_history $pretty "$1" -- $args
}

hunk_history () { # <commit-range> -- <file>:<start>[-<end>]...
	pretty=
	if test "a-p" = "a$1"
	then
		pretty=t
		shift
	fi

	test $# -ge 3 && test a-- = "a$2" ||
	die "Usage: $0 <commit-range> -- <file>:<start>[-<end>]..."

	range="$1"
	shift; shift

	files="$(for arg
		do
			echo "'${arg%:*}'"
		done)"

	for commit in $(eval git rev-list --topo-order "$range" -- $files)
	do
		if test -z "$lines"
		then
			lines="$(echo "$*" |
				tr ' ' '\n' |
				sed "s/^/$commit /")"
		fi

		touched=
		for line in $(echo "$lines" |
				sed -n "s|^$commit ||p")
		do
			file="${line%:*}"
			curstart="${line#$file:}"
			curend="${curstart#*-}"
			curstart="${curstart%%-*}"

			diff_output=
			parentstart=$curstart
			parentend=$curend
			parents=$(git rev-list --no-walk --parents \
					$commit -- "$file" |
				cut -c 41-)
			if test -z "$parents"
			then
				touched=t
			fi

			for parent in $parents
			do
				for hunk in $(get_hunks ^$parent $commit -- \
					"$file")
				do
					hunk1=${hunk%:*}
					start1=${hunk1%,*}
					end1=$(($start1+${hunk1#*,}-1))

					hunk2=${hunk#*:}
					start2=${hunk2%,*}
					end2=$(($start2+${hunk2#*,}-1))

					if test $start2 -le $curend &&
						test $end2 -ge $curstart
					then
						touched=t
					fi

					if test $end2 -le $curstart
					then
						diff=$(($end1-$end2))
						parentstart=$(($curstart+$diff))
					elif test $start2 -le $curstart
					then
						parentstart=$start1
					fi

					if test $end2 -le $curend
					then
						diff=$(($end1-$end2))
						parentend=$(($curend+$diff))
					elif test $start2 -le $curend
					then
						parentend=$end1
					fi
				done

				if test -n "$pretty" &&
					test $curstart != $parentstart ||
					test $curend != $parentend
				then
					test -n "$(echo "$diff_output" |
						sed -n "s|^\([^ -]\[[^m]*m\)*diff --git a/$file b/||p")" ||
					diff_output="$(printf '%s%s\n' "$diff_output" \
						"$(git diff --color \
							^$parent $commit -- $file |
						sed '/^\([^ -]\[[^m]*m\)*@@ /,$d')")"
					prefix="$(git rev-parse --git-dir)"
					oldfile="${prefix}${prefix:+/}.old"
					git show $parent:$file 2>/dev/null |
					sed -n "$parentstart,${parentend}p" >$oldfile
					newfile="${prefix}${prefix:+/}.new"
					git show $commit:$file |
					sed -n "$curstart,${curend}p" >$newfile
					diff1="$(git diff --no-index --color $oldfile $newfile |
						sed '1,4d')"
					diff_output="$(printf '%s%s\n' "$diff_output" \
						"$diff1")"
				fi

				# TODO: support renames here
				prefix="$parent $file:"
				lines="$(printf '%s\n%s%s' "$lines" "$prefix" \
					"$parentstart-$parentend")"
			done
		done
		test -z "$touched" || {
			if test -z "$pretty"
			then
				echo $commit
			else
				git show --color -s $commit --
				echo "$diff_output"
			fi
		}
	done
}

# takes a commit range and a file name
# returns a list of <offset>,<count>:<offset>,<count>
get_hunks () {
	# TODO: support renames here
	git diff --no-color -U0 "$@" |
	sed -n -e 's/\([-+][0-9][0-9]*\) /\1,1 /g' \
		-e 's/^@@ -\([0-9]*,[0-9]*\) +\([0-9]*,[0-9]*\) .*/\1 \2/p' |
	fix_hunks
}

fix_hunks () {
	while read hunk1 hunk2
	do
		case $hunk1 in
		*,0)
			printf '%d,0:' $((${hunk1%,0}+1))
			;;
		*)
			printf '%s:' $hunk1
		esac
		case $hunk2 in
		*,0)
			printf '%d,0\n' $((${hunk2%,0}+1))
			;;
		*)
			printf '%s\n' $hunk2
		esac
	done
}

fixup "$@"
-- snap --

Quite a handful to read, eh? And no code comments. I always meant to
annotate it with some helpful remarks for future me, but never got around
to do that, either. These days, I would probably

1. write the whole thing based on `git log -L <line-range>:<file>`, and

2. either implement it in node.js for speed, or directly in C.

> (And as a total aside, I found your apply-from-public-inbox.sh script
> and really like it.  Thanks for making it public.)

You're welcome! I am glad it is useful to you.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 01/18] Add a function to solve least-cost assignment problems
  2018-05-05 18:24     ` Jeff King
@ 2018-05-05 21:55       ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-05 21:55 UTC (permalink / raw)
  To: Jeff King
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Peff,

On Sat, 5 May 2018, Jeff King wrote:

> On Fri, May 04, 2018 at 05:34:29PM +0200, Johannes Schindelin wrote:
> 
> > The Jonker-Volgenant algorithm was implemented to answer questions such
> > as: given two different versions of a topic branch (or iterations of a
> > patch series), what is the best pairing of commits/patches between the
> > different versions?
> 
> I love git-tbdiff, so I'm excited to see it getting more exposure (and a
> speedup). Thanks for working on this!

:-)

> Two minor nits on this patch:
> 
> > +/*
> > + * The parameter `cost` is the cost matrix: the cost to assign column j to row
> > + * i is `cost[j + column_count * i].
> > + */
> > +int compute_assignment(int column_count, int row_count, double *cost,
> > +		       int *column2row, int *row2column)
> > +{
> > +	double *v = xmalloc(sizeof(double) * column_count), *d;
> 
> Please use st_mult, xcalloc, or ALLOC_ARRAY here to avoid unchecked
> multiplication in an allocation. This is probably hard to exploit in
> practice (since you'd need sizeof(size_t)/8 columns, which presumably
> requires allocating some heavier-weight struct per item). But it makes
> auditing easier if we avoid the pattern altogether.

Sure. I did mean to return errors in those case, but I guess it is not
worth the trouble (what would we do in case of out-of-memory?).

> > +/*
> > + * Compute an assignment of columns -> rows (and vice versa) such that every
> > + * column is assigned to at most one row (and vice versa) minimizing the
> > + * overall cost.
> > + *
> > + * The parameter `cost` is the cost matrix: the cost to assign column j to row
> > + * i is `cost[j + column_count * i].
> > + *
> > + * The arrays column2row and row2column will be populated with the respective
> > + * assignments (-1 for unassigned, which can happen only if column_count !=
> > + * row_count).
> > + */
> > +int compute_assignment(int column_count, int row_count, double *cost,
> > +		       int *column2row, int *row2column);
> 
> It looks like this always returns zero. Is there a ever a case where we
> would return an error if this? If not, should it just be void?

Fixed.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-05 18:26     ` Jeff King
@ 2018-05-05 21:57       ` Johannes Schindelin
  2018-05-06  0:25         ` Todd Zullinger
                           ` (3 more replies)
  0 siblings, 4 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-05 21:57 UTC (permalink / raw)
  To: Jeff King
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Peff,

On Sat, 5 May 2018, Jeff King wrote:

> On Fri, May 04, 2018 at 05:34:32PM +0200, Johannes Schindelin wrote:
> 
> > This builtin does not do a whole lot so far, apart from showing a usage
> > that is oddly similar to that of `git tbdiff`. And for a good reason:
> > the next commits will turn `branch-diff` into a full-blown replacement
> > for `tbdiff`.
> 
> One minor point about the name: will it become annoying as a tab
> completion conflict with git-branch?

I did mention this in the commit message of 18/18:

    Without this patch, we would only complete the `branch-diff` part but
    not the options and other arguments.

    This of itself may already be slightly disruptive for well-trained
    fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to
    `git branch origin/master`, as we now no longer automatically append a
    space after completing `git branch`: this is now ambiguous.

> It feels really petty complaining about the name, but I just want to
> raise the point, since it will never be easier to change than right now.

I do hear you. Especially since I hate `git cherry` every single time that
I try to tab-complete `git cherry-pick`.

> (And no, I don't really have another name in mind; I'm just wondering if
> "subset" names like this might be a mild annoyance in the long run).

They totally are, and if you can come up with a better name, I am really
interested in changing it before this hits `next`, even.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 13/18] color: provide inverted colors, too
  2018-05-05 18:29     ` Jeff King
@ 2018-05-05 22:03       ` Johannes Schindelin
  2018-05-06  6:35         ` Jeff King
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-05 22:03 UTC (permalink / raw)
  To: Jeff King
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Peff,

On Sat, 5 May 2018, Jeff King wrote:

> On Fri, May 04, 2018 at 05:34:58PM +0200, Johannes Schindelin wrote:
> 
> > For every regular color, there exists the inverted equivalent where
> > background and foreground colors are exchanged.
> > 
> > We will use this in the next commit to allow inverting *just* the +/-
> > signs in a diff.
> 
> There's a "reverse" attribute (which we already parse and support) that
> can do this without having to repeat the colors. AFAIK it's well
> supported everywhere, but I could be wrong.

How would I use that here, though? I need to get the thing via
diff_get_color_opt() which takes a parameter of type `enum color_diff`.
There is no way I can specify `reverse` here, can I?

> I wonder if that would make configuring this slightly more pleasant,
> since it saves the user having to define "oldinv" whenever they change
> "old".

I am all for making the configuration more pleasant. So I hope I can make
use of the `reverse` thing here, without having to introduce a new enum
value.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 12/18] branch-diff: use color for the commit pairs
  2018-05-04 15:34   ` [PATCH v2 12/18] branch-diff: use color for the commit pairs Johannes Schindelin
@ 2018-05-05 23:48     ` Todd Zullinger
  2018-05-07  1:52       ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Todd Zullinger @ 2018-05-05 23:48 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Johannes,

As many others have already said, thanks for this series!
I've used tbdiff a bit over the years, but having a builtin
will make it much more convenient (and the speed boost from
a C implementation will be a very nice bonus).

Johannes Schindelin wrote:
> @@ -430,6 +451,8 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
>  	struct string_list branch1 = STRING_LIST_INIT_DUP;
>  	struct string_list branch2 = STRING_LIST_INIT_DUP;
>  
> +	git_diff_basic_config("diff.color.frag", "magenta", NULL);
> +
>  	diff_setup(&diffopt);
>  	diffopt.output_format = DIFF_FORMAT_PATCH;
>  	diffopt.flags.suppress_diff_headers = 1;

Should this also (or only) check color.diff.frag?  I thought
that color.diff.* was preferred over diff.color.*, though
that doesn't seem to be entirely true in all parts of the
current codebase.

In testing this series it seems that setting color.diff
options to change the various colors read earlier in this
patch via diff_get_color_opt, as well as the 'frag' slot,
are ignored.  Setting them via diff.color.<slot> does work.

The later patch adding a man page documents branch-diff as
using `diff.color.*` and points to git-config(1), but the
config docs only list color.diff.

Is this a bug in the diff_get_color{,_opt}() tooling?
It's certainly not anything you've introduced here, of
course.  I just noticed that some custom color.diff settings
I've used weren't picked up by branch-diff, despite your
clear intention to respect colors from the config.

-- 
Todd
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Abandon the search for Truth; settle for a good fantasy.


^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-05 21:57       ` Johannes Schindelin
@ 2018-05-06  0:25         ` Todd Zullinger
  2018-05-06  0:38           ` Todd Zullinger
  2018-05-06  1:05         ` Igor Djordjevic
                           ` (2 subsequent siblings)
  3 siblings, 1 reply; 387+ messages in thread
From: Todd Zullinger @ 2018-05-06  0:25 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Jeff King, git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Johannes,

Johannes Schindelin wrote:
> On Sat, 5 May 2018, Jeff King wrote:
>> One minor point about the name: will it become annoying as a tab
>> completion conflict with git-branch?
> 
> I did mention this in the commit message of 18/18:
> 
>     Without this patch, we would only complete the `branch-diff` part but
>     not the options and other arguments.
> 
>     This of itself may already be slightly disruptive for well-trained
>     fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to
>     `git branch origin/master`, as we now no longer automatically append a
>     space after completing `git branch`: this is now ambiguous.
> 
>> It feels really petty complaining about the name, but I just want to
>> raise the point, since it will never be easier to change than right now.
> 
> I do hear you. Especially since I hate `git cherry` every single time that
> I try to tab-complete `git cherry-pick`.
> 
>> (And no, I don't really have another name in mind; I'm just wondering if
>> "subset" names like this might be a mild annoyance in the long run).
> 
> They totally are, and if you can come up with a better name, I am really
> interested in changing it before this hits `next`, even.

Would it be possible and reasonable to teach 'git branch' to
call this as a subcommand, i.e. as 'git branch diff'?  Then
the completion wouldn't offer git branch-diff.

Users could still call it directly if they wanted, though
I'd tend to think that should be discouraged and have it
treated as an implementation detail that it's a separate
binary.

We have a number of commands which take subcommands this way
(bundle, bisect, notes, submodule, and stash come to mind).
I don't know if any are used with and without a subcommand,
but it doesn't seem too strange from a UI point of view, to
me.

(I don't know if it's coincidental that of the existing
commands I noted above, 3 of the 5 are currently implemented
as shell scripts.  But they've all seen at least some work
toward converting them to C, I believe).

The idea might be gross and/or unreasonable from an
implementation or UI view.  I'm not sure, but I thought I
would toss the idea out.

This wouldn't work for git cherry{,-pick} where you wouldn't
consider 'git cherry pick' as related to 'git cherry'
though.

We also have this with git show{,-branch} and some others.
It's a mild annoyance, but muscle memory adapts eventually.

-- 
Todd
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A budget is just a method of worrying before you spend money, as well
as afterward.


^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-06  0:25         ` Todd Zullinger
@ 2018-05-06  0:38           ` Todd Zullinger
  2018-05-06 12:04             ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Todd Zullinger @ 2018-05-06  0:38 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Jeff King, git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

I wrote:
> Would it be possible and reasonable to teach 'git branch' to
> call this as a subcommand, i.e. as 'git branch diff'?  Then
> the completion wouldn't offer git branch-diff.

Of course right after I sent this, it occurred to me that
'git branch diff' would make mask the ability to create a
branch named diff.  Using 'git branch --diff ...' wouldn't
suffer that problem.

It does add a bit more overhead to the 'git branch' command,
in terms of documentation and usage.  I'm not sure it's too
much though.  The git-branch summary wouldn't change much:

-git-branch - List, create, or delete branches
+git-branch - List, create, delete, or diff branches

I hesitate to hit send again, in case I'm once again
overlooking a glaringly obvious problem with this idea. ;)

-- 
Todd
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Quick to judge, quick to anger, slow to understand.
Ignorance and prejudice and fear walk hand in hand.


^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-05 21:57       ` Johannes Schindelin
  2018-05-06  0:25         ` Todd Zullinger
@ 2018-05-06  1:05         ` Igor Djordjevic
  2018-05-06  4:53           ` Jacob Keller
  2018-05-06 12:10           ` Johannes Schindelin
  2018-05-06  2:33         ` Junio C Hamano
  2018-05-07  7:50         ` Jeff King
  3 siblings, 2 replies; 387+ messages in thread
From: Igor Djordjevic @ 2018-05-06  1:05 UTC (permalink / raw)
  To: Johannes Schindelin, Jeff King
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Dscho,

On 05/05/2018 23:57, Johannes Schindelin wrote:
> 
> > > This builtin does not do a whole lot so far, apart from showing a
> > > usage that is oddly similar to that of `git tbdiff`. And for a
> > > good reason: the next commits will turn `branch-diff` into a
> > > full-blown replacement for `tbdiff`.
> >
> > One minor point about the name: will it become annoying as a tab
> > completion conflict with git-branch?
> 
> I did mention this in the commit message of 18/18:
> 
>     Without this patch, we would only complete the `branch-diff` part but
>     not the options and other arguments.
> 
>     This of itself may already be slightly disruptive for well-trained
>     fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to
>     `git branch origin/master`, as we now no longer automatically append a
>     space after completing `git branch`: this is now ambiguous.
> 
> > It feels really petty complaining about the name, but I just want
> > to raise the point, since it will never be easier to change than
> > right now.
> 
> I do hear you. Especially since I hate `git cherry` every single
> time that I try to tab-complete `git cherry-pick`.
> 
> > (And no, I don't really have another name in mind; I'm just
> > wondering if "subset" names like this might be a mild annoyance in
> > the long run).
> 
> They totally are, and if you can come up with a better name, I am
> really interested in changing it before this hits `next`, even.

I gave this just a quick glance so might be I`m missing something 
obvious or otherwise well-known here, bur why not `diff-branch` instead?

From user interface perspective, I would (personally) rather expect a 
command that does "diff of branches" to belong to "diff family" of 
commands (just operating on branches, instead of "branch" command 
knowing to "diff itself"), and I see we already have `diff-files`, 
`diff-index` and `diff-tree`, for what that`s worth.

Heck, I might even expect something like `git diff --branch ...` to work, 
but I guess that is yet a different matter :)

Thanks, Buga

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 05/18] branch-diff: also show the diff between patches
  2018-05-04 15:34   ` [PATCH v2 05/18] branch-diff: also show the diff between patches Johannes Schindelin
@ 2018-05-06  1:14     ` Igor Djordjevic
  2018-05-06 12:18       ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Igor Djordjevic @ 2018-05-06  1:14 UTC (permalink / raw)
  To: Johannes Schindelin, git
  Cc: Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Johannes,

On 04/05/2018 17:34, Johannes Schindelin wrote:
> Just like tbdiff, we now show the diff between matching patches. This is
> a "diff of two diffs", so it can be a bit daunting to read for the
> beginner.
> 
> And just like tbdiff, we now also accept the `--no-patches` option
> (which is actually equivalent to the diff option `-s`).

A quick nit - would `--no-patch` (singular form) option name be more 
aligned with diff `-s` option it resembles?

Thanks, Buga

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-05 21:57       ` Johannes Schindelin
  2018-05-06  0:25         ` Todd Zullinger
  2018-05-06  1:05         ` Igor Djordjevic
@ 2018-05-06  2:33         ` Junio C Hamano
  2018-05-06 12:21           ` Johannes Schindelin
  2018-05-07  7:50         ` Jeff King
  3 siblings, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-05-06  2:33 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Jeff King, git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> Hi Peff,
>
> On Sat, 5 May 2018, Jeff King wrote:
>
>> On Fri, May 04, 2018 at 05:34:32PM +0200, Johannes Schindelin wrote:
>> 
>> > This builtin does not do a whole lot so far, apart from showing a usage
>> > that is oddly similar to that of `git tbdiff`. And for a good reason:
>> > the next commits will turn `branch-diff` into a full-blown replacement
>> > for `tbdiff`.
>> 
>> One minor point about the name: will it become annoying as a tab
>> completion conflict with git-branch?

If tbdiff were "Thomas's branch diff", I would call this jbdiff ;-)
but I think the 't' in there stands for "topic", not "Thomas's".

How about "git topic-diff"?

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-06  1:05         ` Igor Djordjevic
@ 2018-05-06  4:53           ` Jacob Keller
  2018-05-06  8:32             ` Duy Nguyen
  2018-05-06 12:10           ` Johannes Schindelin
  1 sibling, 1 reply; 387+ messages in thread
From: Jacob Keller @ 2018-05-06  4:53 UTC (permalink / raw)
  To: Igor Djordjevic
  Cc: Johannes Schindelin, Jeff King, Git mailing list, Junio C Hamano,
	Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Eric Sunshine

On Sat, May 5, 2018 at 6:05 PM, Igor Djordjevic
<igor.d.djordjevic@gmail.com> wrote:
> Hi Dscho,
>
> On 05/05/2018 23:57, Johannes Schindelin wrote:
>>
>> > > This builtin does not do a whole lot so far, apart from showing a
>> > > usage that is oddly similar to that of `git tbdiff`. And for a
>> > > good reason: the next commits will turn `branch-diff` into a
>> > > full-blown replacement for `tbdiff`.
>> >
>> > One minor point about the name: will it become annoying as a tab
>> > completion conflict with git-branch?
>>
>> I did mention this in the commit message of 18/18:
>>
>>     Without this patch, we would only complete the `branch-diff` part but
>>     not the options and other arguments.
>>
>>     This of itself may already be slightly disruptive for well-trained
>>     fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to
>>     `git branch origin/master`, as we now no longer automatically append a
>>     space after completing `git branch`: this is now ambiguous.
>>
>> > It feels really petty complaining about the name, but I just want
>> > to raise the point, since it will never be easier to change than
>> > right now.
>>
>> I do hear you. Especially since I hate `git cherry` every single
>> time that I try to tab-complete `git cherry-pick`.
>>
>> > (And no, I don't really have another name in mind; I'm just
>> > wondering if "subset" names like this might be a mild annoyance in
>> > the long run).
>>
>> They totally are, and if you can come up with a better name, I am
>> really interested in changing it before this hits `next`, even.
>
> I gave this just a quick glance so might be I`m missing something
> obvious or otherwise well-known here, bur why not `diff-branch` instead?
>
> From user interface perspective, I would (personally) rather expect a
> command that does "diff of branches" to belong to "diff family" of
> commands (just operating on branches, instead of "branch" command
> knowing to "diff itself"), and I see we already have `diff-files`,
> `diff-index` and `diff-tree`, for what that`s worth.
>
> Heck, I might even expect something like `git diff --branch ...` to work,
> but I guess that is yet a different matter :)
>
> Thanks, Buga

I like diff-branch, though I suppose that also conflicts with diff too.

Thanks,
Jake

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
                     ` (18 preceding siblings ...)
  2018-05-04 16:21   ` [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike Elijah Newren
@ 2018-05-06  5:22   ` Junio C Hamano
  2018-05-06 12:23     ` Johannes Schindelin
  2018-05-06 22:56   ` brian m. carlson
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
  21 siblings, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-05-06  5:22 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Johannes Schindelin <johannes.schindelin@gmx.de> writes:

> Johannes Schindelin (17):
>   Add a function to solve least-cost assignment problems
>   Add a new builtin: branch-diff

Perhaps retitling these to

    hungarian: a function to solve least-cost assignment problems
    branch-diff: a new builtin to compare iterations of a topic

may serve as good precedents to changes other people may later make
to these files.  Especially the second one is already consistent
with the several changes that are listed below ;-)

>   branch-diff: first rudimentary implementation
>   branch-diff: improve the order of the shown commits
>   branch-diff: also show the diff between patches
>...

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 13/18] color: provide inverted colors, too
  2018-05-05 22:03       ` Johannes Schindelin
@ 2018-05-06  6:35         ` Jeff King
  2018-05-06  6:41           ` Jeff King
  0 siblings, 1 reply; 387+ messages in thread
From: Jeff King @ 2018-05-06  6:35 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

On Sun, May 06, 2018 at 12:03:50AM +0200, Johannes Schindelin wrote:

> > There's a "reverse" attribute (which we already parse and support) that
> > can do this without having to repeat the colors. AFAIK it's well
> > supported everywhere, but I could be wrong.
> 
> How would I use that here, though? I need to get the thing via
> diff_get_color_opt() which takes a parameter of type `enum color_diff`.
> There is no way I can specify `reverse` here, can I?

My thinking was that the code would know that coloring the initial "+"
should combine color.diff.new, along with a new tbdiff-specific config
option. So the C equivalent of something like this:

  new=$(git config --get-color color.diff.new green)
  tbdiff=$(git config --get-color color.tbdiff.new reverse)
  reset=$(git config --get-color color.diff.reset reset)

  echo "${new}${tbdiff}+${reset}${new}+actual diff content${reset}"

Then if you set color.diff.new to blue, you'll get a reverse-blue "+"
without having to configure anything else.

You can still override the tbdiff coloring with a totally unrelated
color, since it comes after ${new} (so you could set it to purple or
something if you wanted, though obviously a background or attribute from
${new} can still leak through if you have one set). The only downside in
such a case is that the color sequence is slightly longer ("green, no
blue!").

You could also have tbdiff.new and tbdiff.old to allow setting them
independently (but they'd both default to "reverse").

> > I wonder if that would make configuring this slightly more pleasant,
> > since it saves the user having to define "oldinv" whenever they change
> > "old".
> 
> I am all for making the configuration more pleasant. So I hope I can make
> use of the `reverse` thing here, without having to introduce a new enum
> value.

I think the new enum (and matching config) has some value in case people
want to override it.  But if you don't want to, diff_get_color() is
really just checking want_color() as a convenience. You could do that,
too:

  const char *reverse = want_color(opt->use_color) ? GIT_COLOR_REVERSE : "";

You'd have to introduce GIT_COLOR_REVERSE. I don't think we have a
constant for it yet, but it's \x[7m.

-Peff

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 13/18] color: provide inverted colors, too
  2018-05-06  6:35         ` Jeff King
@ 2018-05-06  6:41           ` Jeff King
  2018-05-07  1:20             ` Johannes Schindelin
  2018-05-07  1:35             ` Junio C Hamano
  0 siblings, 2 replies; 387+ messages in thread
From: Jeff King @ 2018-05-06  6:41 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

On Sun, May 06, 2018 at 02:35:44AM -0400, Jeff King wrote:

> You'd have to introduce GIT_COLOR_REVERSE. I don't think we have a
> constant for it yet, but it's \x[7m.

Heh, of course you knew that already, as I just noticed your patch is
using the reverse attribute internally (I had thought at first glance
you were just specifying the background independently).

So really, I guess all I am arguing for is having GIT_COLOR_INV (or
REVERSE) as a constant, and then teaching the code to combine it with
the existing "new" color. It's perfectly OK to have:

  \x1b[7m\x1b[36m

instead of:

  \x1b[7;36m

It's two extra bytes, but I doubt anybody cares.

-Peff

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 18/18] completion: support branch-diff
  2018-05-04 15:35   ` [PATCH v2 18/18] completion: support branch-diff Johannes Schindelin
@ 2018-05-06  8:24     ` Duy Nguyen
  2018-05-07  1:23       ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Duy Nguyen @ 2018-05-06  8:24 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

On Fri, May 04, 2018 at 05:35:11PM +0200, Johannes Schindelin wrote:
> Tab completion of `branch-diff` is very convenient, especially given
> that the revision arguments that need to be passed to `git branch-diff`
> are typically more complex than, say, your grandfather's `git log`
> arguments.
> 
> Without this patch, we would only complete the `branch-diff` part but
> not the options and other arguments.
> 
> This of itself may already be slightly disruptive for well-trained
> fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to
> `git branch origin/master`, as we now no longer automatically append a
> space after completing `git branch`: this is now ambiguous.
> 
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  contrib/completion/git-completion.bash | 18 ++++++++++++++++++
>  1 file changed, 18 insertions(+)
> 
> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
> index 01dd9ff07a2..45addd525ac 100644
> --- a/contrib/completion/git-completion.bash
> +++ b/contrib/completion/git-completion.bash
> @@ -1496,6 +1496,24 @@ _git_format_patch ()
>  	__git_complete_revlist
>  }
>  
> +__git_branch_diff_options="
> +	--no-patches --creation-weight= --dual-color
> +"
> +
> +_git_branch_diff ()
> +{
> +	case "$cur" in
> +	--*)
> +		__gitcomp "

You should use __gitcomp_builtin so you don't have to maintain
$__git_branch_diff_options here. Something like this

-- 8< --
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 45addd525a..4745631daf 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1496,18 +1496,11 @@ _git_format_patch ()
 	__git_complete_revlist
 }
 
-__git_branch_diff_options="
-	--no-patches --creation-weight= --dual-color
-"
-
 _git_branch_diff ()
 {
 	case "$cur" in
 	--*)
-		__gitcomp "
-			$__git_branch_diff_options
-			$__git_diff_common_options
-			"
+		__gitcomp_builtin branch-diff "$__git_diff_common_options"
 		return
 		;;
 	esac
-- 8< --


> +			$__git_branch_diff_options
> +			$__git_diff_common_options
> +			"
> +		return
> +		;;
> +	esac
> +	__git_complete_revlist
> +}
> +
>  _git_fsck ()
>  {
>  	case "$cur" in
> -- 
> 2.17.0.409.g71698f11835

^ permalink raw reply related	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-06  4:53           ` Jacob Keller
@ 2018-05-06  8:32             ` Duy Nguyen
  2018-05-06 12:08               ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Duy Nguyen @ 2018-05-06  8:32 UTC (permalink / raw)
  To: Jacob Keller
  Cc: Igor Djordjevic, Johannes Schindelin, Jeff King,
	Git mailing list, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Eric Sunshine

On Sun, May 6, 2018 at 6:53 AM, Jacob Keller <jacob.keller@gmail.com> wrote:
> On Sat, May 5, 2018 at 6:05 PM, Igor Djordjevic
> <igor.d.djordjevic@gmail.com> wrote:
>> Hi Dscho,
>>
>> On 05/05/2018 23:57, Johannes Schindelin wrote:
>>>
>>> > > This builtin does not do a whole lot so far, apart from showing a
>>> > > usage that is oddly similar to that of `git tbdiff`. And for a
>>> > > good reason: the next commits will turn `branch-diff` into a
>>> > > full-blown replacement for `tbdiff`.
>>> >
>>> > One minor point about the name: will it become annoying as a tab
>>> > completion conflict with git-branch?
>>>
>>> I did mention this in the commit message of 18/18:
>>>
>>>     Without this patch, we would only complete the `branch-diff` part but
>>>     not the options and other arguments.
>>>
>>>     This of itself may already be slightly disruptive for well-trained
>>>     fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to
>>>     `git branch origin/master`, as we now no longer automatically append a
>>>     space after completing `git branch`: this is now ambiguous.
>>>
>>> > It feels really petty complaining about the name, but I just want
>>> > to raise the point, since it will never be easier to change than
>>> > right now.
>>>
>>> I do hear you. Especially since I hate `git cherry` every single
>>> time that I try to tab-complete `git cherry-pick`.
>>>
>>> > (And no, I don't really have another name in mind; I'm just
>>> > wondering if "subset" names like this might be a mild annoyance in
>>> > the long run).
>>>
>>> They totally are, and if you can come up with a better name, I am
>>> really interested in changing it before this hits `next`, even.
>>
>> I gave this just a quick glance so might be I`m missing something
>> obvious or otherwise well-known here, bur why not `diff-branch` instead?
>>
>> From user interface perspective, I would (personally) rather expect a
>> command that does "diff of branches" to belong to "diff family" of
>> commands (just operating on branches, instead of "branch" command
>> knowing to "diff itself"), and I see we already have `diff-files`,
>> `diff-index` and `diff-tree`, for what that`s worth.
>>
>> Heck, I might even expect something like `git diff --branch ...` to work,
>> but I guess that is yet a different matter :)
>>
>> Thanks, Buga
>
> I like diff-branch, though I suppose that also conflicts with diff too.

How about interdiff?

-- 
Duy

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-06  0:38           ` Todd Zullinger
@ 2018-05-06 12:04             ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-06 12:04 UTC (permalink / raw)
  To: Todd Zullinger
  Cc: Jeff King, git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Todd,

On Sat, 5 May 2018, Todd Zullinger wrote:

> I wrote:
> > Would it be possible and reasonable to teach 'git branch' to
> > call this as a subcommand, i.e. as 'git branch diff'?  Then
> > the completion wouldn't offer git branch-diff.
> 
> Of course right after I sent this, it occurred to me that
> 'git branch diff' would make mask the ability to create a
> branch named diff.  Using 'git branch --diff ...' wouldn't
> suffer that problem.

Yep, I immediately thought of --diff instead of diff when I read your
previous mail on that matter. And I like this idea!

Of course, it will complicate the code to set up the pager a bit (for
`branch-diff`, I could default to "on" all the time). But IIRC we recently
changed the --list cmdmode to set the pager to "auto", so I'll just copy
that.

> It does add a bit more overhead to the 'git branch' command,
> in terms of documentation and usage.  I'm not sure it's too
> much though.  The git-branch summary wouldn't change much:
> 
> -git-branch - List, create, or delete branches
> +git-branch - List, create, delete, or diff branches

Indeed.

Unless I hear objections, I will work on moving to `git branch --diff` (it
might take a while, though, I will be traveling for work this week).

Ciao,
Johannes

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-06  8:32             ` Duy Nguyen
@ 2018-05-06 12:08               ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-06 12:08 UTC (permalink / raw)
  To: Duy Nguyen
  Cc: Jacob Keller, Igor Djordjevic, Jeff King, Git mailing list,
	Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Eric Sunshine

Hi Duy,

On Sun, 6 May 2018, Duy Nguyen wrote:

> On Sun, May 6, 2018 at 6:53 AM, Jacob Keller <jacob.keller@gmail.com> wrote:
> > On Sat, May 5, 2018 at 6:05 PM, Igor Djordjevic
> > <igor.d.djordjevic@gmail.com> wrote:
> >>
> >> On 05/05/2018 23:57, Johannes Schindelin wrote:
> >>>
> >>> > > This builtin does not do a whole lot so far, apart from showing a
> >>> > > usage that is oddly similar to that of `git tbdiff`. And for a
> >>> > > good reason: the next commits will turn `branch-diff` into a
> >>> > > full-blown replacement for `tbdiff`.
> >>> >
> >>> > One minor point about the name: will it become annoying as a tab
> >>> > completion conflict with git-branch?
> >>>
> >>> I did mention this in the commit message of 18/18:
> >>>
> >>>     Without this patch, we would only complete the `branch-diff` part but
> >>>     not the options and other arguments.
> >>>
> >>>     This of itself may already be slightly disruptive for well-trained
> >>>     fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to
> >>>     `git branch origin/master`, as we now no longer automatically append a
> >>>     space after completing `git branch`: this is now ambiguous.
> >>>
> >>> > It feels really petty complaining about the name, but I just want
> >>> > to raise the point, since it will never be easier to change than
> >>> > right now.
> >>>
> >>> I do hear you. Especially since I hate `git cherry` every single
> >>> time that I try to tab-complete `git cherry-pick`.
> >>>
> >>> > (And no, I don't really have another name in mind; I'm just
> >>> > wondering if "subset" names like this might be a mild annoyance in
> >>> > the long run).
> >>>
> >>> They totally are, and if you can come up with a better name, I am
> >>> really interested in changing it before this hits `next`, even.
> >>
> >> I gave this just a quick glance so might be I`m missing something
> >> obvious or otherwise well-known here, bur why not `diff-branch` instead?
> >>
> >> From user interface perspective, I would (personally) rather expect a
> >> command that does "diff of branches" to belong to "diff family" of
> >> commands (just operating on branches, instead of "branch" command
> >> knowing to "diff itself"), and I see we already have `diff-files`,
> >> `diff-index` and `diff-tree`, for what that`s worth.
> >>
> >> Heck, I might even expect something like `git diff --branch ...` to work,
> >> but I guess that is yet a different matter :)
> >>
> >> Thanks, Buga
> >
> > I like diff-branch, though I suppose that also conflicts with diff too.
> 
> How about interdiff?

No. An interdiff is well defined as the diff you would get by first
applying the first of two patches in reverse and then the second patch
forward. In other words, it turns two revisions of a patch into the diff
between the result of applying both revisions.

I tried very hard to avoid using that term in my patch series (tbdiff used
the term incorrectly: what it called an interdiff is a diff of two
patches, where a patch is an author line followed by the commit message
followed by the commit diff).

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-06  1:05         ` Igor Djordjevic
  2018-05-06  4:53           ` Jacob Keller
@ 2018-05-06 12:10           ` Johannes Schindelin
  2018-05-06 13:37             ` Igor Djordjevic
  1 sibling, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-06 12:10 UTC (permalink / raw)
  To: Igor Djordjevic
  Cc: Jeff King, git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Buga,

On Sun, 6 May 2018, Igor Djordjevic wrote:

> On 05/05/2018 23:57, Johannes Schindelin wrote:
> > 
> > > > This builtin does not do a whole lot so far, apart from showing a
> > > > usage that is oddly similar to that of `git tbdiff`. And for a
> > > > good reason: the next commits will turn `branch-diff` into a
> > > > full-blown replacement for `tbdiff`.
> > >
> > > One minor point about the name: will it become annoying as a tab
> > > completion conflict with git-branch?
> > 
> > I did mention this in the commit message of 18/18:
> > 
> >     Without this patch, we would only complete the `branch-diff` part but
> >     not the options and other arguments.
> > 
> >     This of itself may already be slightly disruptive for well-trained
> >     fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to
> >     `git branch origin/master`, as we now no longer automatically append a
> >     space after completing `git branch`: this is now ambiguous.
> > 
> > > It feels really petty complaining about the name, but I just want
> > > to raise the point, since it will never be easier to change than
> > > right now.
> > 
> > I do hear you. Especially since I hate `git cherry` every single
> > time that I try to tab-complete `git cherry-pick`.
> > 
> > > (And no, I don't really have another name in mind; I'm just
> > > wondering if "subset" names like this might be a mild annoyance in
> > > the long run).
> > 
> > They totally are, and if you can come up with a better name, I am
> > really interested in changing it before this hits `next`, even.
> 
> I gave this just a quick glance so might be I`m missing something 
> obvious or otherwise well-known here, bur why not `diff-branch` instead?

I think that is just turning the problem from `branch` to `diff`.

Of course, we have precedent with diff-index and diff-files. Except that
they don't auto-complete (because they are low-level commands) and I
*would* like the subcommand discussed in this here patch series to
auto-complete.

I think Todd's idea to shift it from a full-blown builtin to a cmdmode
of `branch` makes tons of sense.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 05/18] branch-diff: also show the diff between patches
  2018-05-06  1:14     ` Igor Djordjevic
@ 2018-05-06 12:18       ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-06 12:18 UTC (permalink / raw)
  To: Igor Djordjevic
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Buga,

On Sun, 6 May 2018, Igor Djordjevic wrote:

> On 04/05/2018 17:34, Johannes Schindelin wrote:
> > Just like tbdiff, we now show the diff between matching patches. This is
> > a "diff of two diffs", so it can be a bit daunting to read for the
> > beginner.
> > 
> > And just like tbdiff, we now also accept the `--no-patches` option
> > (which is actually equivalent to the diff option `-s`).
> 
> A quick nit - would `--no-patch` (singular form) option name be more 
> aligned with diff `-s` option it resembles?

The reason I used `--no-patches` is that tbdiff called it that way.

But you're right, the functionality is already available via -s, and we
*do* make this a distinct thing from tbdiff. So I'll simply drop support
for --no-patches.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-06  2:33         ` Junio C Hamano
@ 2018-05-06 12:21           ` Johannes Schindelin
  2018-05-06 20:51             ` Eric Sunshine
  2018-05-07  1:45             ` Junio C Hamano
  0 siblings, 2 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-06 12:21 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Jeff King, git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Junio,

On Sun, 6 May 2018, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > On Sat, 5 May 2018, Jeff King wrote:
> >
> >> On Fri, May 04, 2018 at 05:34:32PM +0200, Johannes Schindelin wrote:
> >> 
> >> > This builtin does not do a whole lot so far, apart from showing a usage
> >> > that is oddly similar to that of `git tbdiff`. And for a good reason:
> >> > the next commits will turn `branch-diff` into a full-blown replacement
> >> > for `tbdiff`.
> >> 
> >> One minor point about the name: will it become annoying as a tab
> >> completion conflict with git-branch?
> 
> If tbdiff were "Thomas's branch diff", I would call this jbdiff ;-)
> but I think the 't' in there stands for "topic", not "Thomas's".
> 
> How about "git topic-diff"?

Or `git topic-branch-diff`?

But then, we do not really use the term `topic branch` a lot in Git, *and*
the operation in question is not really about showing differences between
topic branches, but between revisions of topic branches.

So far, the solution I like best is to use `git branch --diff <...>`,
which also neatly side-steps the problem of cluttering the top-level
command list (because tab completion).

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-06  5:22   ` Junio C Hamano
@ 2018-05-06 12:23     ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-06 12:23 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Junio,

On Sun, 6 May 2018, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > Johannes Schindelin (17):
> >   Add a function to solve least-cost assignment problems
> >   Add a new builtin: branch-diff
> 
> Perhaps retitling these to
> 
>     hungarian: a function to solve least-cost assignment problems
>     branch-diff: a new builtin to compare iterations of a topic
> 
> may serve as good precedents to changes other people may later make
> to these files.  Especially the second one is already consistent
> with the several changes that are listed below ;-)

I like it! They are retitled locally, in preparation for whenever I send
out the next iteration.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-06 12:10           ` Johannes Schindelin
@ 2018-05-06 13:37             ` Igor Djordjevic
  2018-05-07  1:34               ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Igor Djordjevic @ 2018-05-06 13:37 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Jeff King, git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Dscho,

On 06/05/2018 14:10, Johannes Schindelin wrote:
> 
> > > > > This builtin does not do a whole lot so far, apart from showing a
> > > > > usage that is oddly similar to that of `git tbdiff`. And for a
> > > > > good reason: the next commits will turn `branch-diff` into a
> > > > > full-blown replacement for `tbdiff`.
> > > >
> > > > One minor point about the name: will it become annoying as a tab
> > > > completion conflict with git-branch?
> > >
> > > I did mention this in the commit message of 18/18:
> > >
> > >     Without this patch, we would only complete the `branch-diff` part but
> > >     not the options and other arguments.
> > >
> > >     This of itself may already be slightly disruptive for well-trained
> > >     fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to
> > >     `git branch origin/master`, as we now no longer automatically append a
> > >     space after completing `git branch`: this is now ambiguous.
> > >
> > > > It feels really petty complaining about the name, but I just want
> > > > to raise the point, since it will never be easier to change than
> > > > right now.
> > >
> > > I do hear you. Especially since I hate `git cherry` every single
> > > time that I try to tab-complete `git cherry-pick`.
> > >
> > > > (And no, I don't really have another name in mind; I'm just
> > > > wondering if "subset" names like this might be a mild annoyance in
> > > > the long run).
> > >
> > > They totally are, and if you can come up with a better name, I am
> > > really interested in changing it before this hits `next`, even.
> >
> > I gave this just a quick glance so might be I`m missing something 
> > obvious or otherwise well-known here, bur why not `diff-branch` instead?
> 
> I think that is just turning the problem from `branch` to `diff`.
> 
> Of course, we have precedent with diff-index and diff-files. Except that
> they don't auto-complete (because they are low-level commands) and I
> *would* like the subcommand discussed in this here patch series to
> auto-complete.

Yeah, I did suspect it might be something like this (those other ones 
not auto-completing, where we do want it here), thanks for elaborating.

> I think Todd's idea to shift it from a full-blown builtin to a cmdmode
> of `branch` makes tons of sense.

I don`t know, I still find it a bit strange that in order to "diff 
something", you go to "something" and tell it to "diff itself" - not 
because it`s a weird concept (OOP, anyone? :]), but because we 
already have "diff" command that can accept different things, thus 
just teaching it to accept additional "something" (branch, in this 
case), seems more natural (to me) - "branch diff" being just another 
"diff" mode of operation.

What about that side thought you left out from my original message, 
making it `git diff --branch` instead?

But if "branch diff" is considered to be too special-cased mode of 
"diff" so that supporting it from `diff` itself would make it feel 
awkward in both usage and maintenance (in terms of many other regular 
`diff` specific options being unsupported), I guess I would understand 
having it outside `diff` altogether (and implemented as proposed `git 
branch --diff`, or something)... for the time being, at least :)

Regards, Buga

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 07/18] branch-diff: indent the diffs just like tbdiff
  2018-05-04 15:34   ` [PATCH v2 07/18] branch-diff: indent the diffs just like tbdiff Johannes Schindelin
@ 2018-05-06 14:15     ` Martin Ågren
  2018-05-07  1:54       ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Martin Ågren @ 2018-05-06 14:15 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git Mailing List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

On 4 May 2018 at 17:34, Johannes Schindelin <johannes.schindelin@gmx.de> wrote:
> @@ -353,6 +358,7 @@ static void output(struct string_list *a, struct string_list *b,
>  int cmd_branch_diff(int argc, const char **argv, const char *prefix)
>  {
>         struct diff_options diffopt = { NULL };
> +       struct strbuf four_spaces = STRBUF_INIT;
>         double creation_weight = 0.6;
>         struct option options[] = {
>                 OPT_SET_INT(0, "no-patches", &diffopt.output_format,
> @@ -371,6 +377,9 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
>
>         diff_setup(&diffopt);
>         diffopt.output_format = DIFF_FORMAT_PATCH;
> +       diffopt.output_prefix = output_prefix_cb;
> +       strbuf_addstr(&four_spaces, "    ");
> +       diffopt.output_prefix_data = &four_spaces;
>
>         argc = parse_options(argc, argv, NULL, options,
>                         builtin_branch_diff_usage, PARSE_OPT_KEEP_UNKNOWN);

You end up leaking the buffer of `four_spaces`. Granted, that's not a
big memory leak, but still. ;-) This was the only leak that
LeakSanitizer found in v2 when running the new test-script and playing
around with this a bit. This looks really good!

Martin

^ permalink raw reply	[flat|nested] 387+ messages in thread

* [PATCH v3 05/20] range-diff: also show the diff between patches
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
                       ` (16 preceding siblings ...)
  2018-05-05 19:52     ` [PATCH v3 19/20] range-diff: left-pad patch numbers Johannes Schindelin via GitGitGadget
@ 2018-05-06 15:26     ` Johannes Schindelin via GitGitGadget
  2018-05-06 15:35     ` [PATCH v3 10/20] range-diff: do not show "function names" in hunk headers Johannes Schindelin via GitGitGadget
                       ` (2 subsequent siblings)
  20 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-05-06 15:26 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

Just like tbdiff, we now show the diff between matching patches. This is
a "diff of two diffs", so it can be a bit daunting to read for the
beginner.

An alternative would be to display an interdiff, i.e. the hypothetical
diff which is the result of first reverting the old diff and then
applying the new diff.

Especially when rebasing often, an interdiff is often not feasible,
though: if the old diff cannot be applied in reverse (due to a moving
upstream), an interdiff can simply not be inferred.

This commit brings `range-diff` closer to feature parity with regard
to tbdiff.

To make `git range-diff` respect e.g. color.diff.* settings, we have
to adjust git_branch_config() accordingly.

Note: while we now parse diff options such as --color, the effect is not
yet the same as in tbdiff, where also the commit pairs would be colored.
This is left for a later commit.

Note also: while tbdiff accepts the `--no-patches` option to suppress
these diffs between patches, we prefer the `-s` option that is
automatically supported via our use of diff_opt_parse().

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/range-diff.c | 25 +++++++++++++++++++++----
 range-diff.c         | 34 +++++++++++++++++++++++++++++++---
 range-diff.h         |  4 +++-
 3 files changed, 55 insertions(+), 8 deletions(-)

diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index c37a72100..5f12bbfa9 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -2,6 +2,7 @@
 #include "builtin.h"
 #include "parse-options.h"
 #include "range-diff.h"
+#include "config.h"
 
 static const char * const builtin_range_diff_usage[] = {
 N_("git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
@@ -13,16 +14,31 @@ NULL
 int cmd_range_diff(int argc, const char **argv, const char *prefix)
 {
 	int creation_factor = 60;
+	struct diff_options diffopt = { NULL };
 	struct option options[] = {
 		OPT_INTEGER(0, "creation-factor", &creation_factor,
 			    N_("Percentage by which creation is weighted")),
 		OPT_END()
 	};
-	int res = 0;
+	int i, j, res = 0;
 	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
 
-	argc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,
-			     0);
+	git_config(git_diff_ui_config, NULL);
+
+	diff_setup(&diffopt);
+	diffopt.output_format = DIFF_FORMAT_PATCH;
+
+	argc = parse_options(argc, argv, NULL, options,
+			builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);
+
+	for (i = j = 0; i < argc; i++) {
+		int c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);
+
+		if (!c)
+			argv[j++] = argv[i];
+	}
+	argc = j;
+	diff_setup_done(&diffopt);
 
 	if (argc == 2) {
 		if (!strstr(argv[0], ".."))
@@ -57,7 +73,8 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 		usage_with_options(builtin_range_diff_usage, options);
 	}
 
-	res = show_range_diff(range1.buf, range2.buf, creation_factor);
+	res = show_range_diff(range1.buf, range2.buf, creation_factor,
+			      &diffopt);
 
 	strbuf_release(&range1);
 	strbuf_release(&range2);
diff --git a/range-diff.c b/range-diff.c
index e71cf0ba7..530f2fc32 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -6,6 +6,7 @@
 #include "hashmap.h"
 #include "xdiff-interface.h"
 #include "linear-assignment.h"
+#include "diffcore.h"
 
 struct patch_util {
 	/* For the search for an exact match */
@@ -254,7 +255,31 @@ static const char *short_oid(struct patch_util *util)
 	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
 }
 
-static void output(struct string_list *a, struct string_list *b)
+static struct diff_filespec *get_filespec(const char *name, const char *p)
+{
+	struct diff_filespec *spec = alloc_filespec(name);
+
+	fill_filespec(spec, &null_oid, 0, 0644);
+	spec->data = (char *)p;
+	spec->size = strlen(p);
+	spec->should_munmap = 0;
+	spec->is_stdin = 1;
+
+	return spec;
+}
+
+static void patch_diff(const char *a, const char *b,
+			      struct diff_options *diffopt)
+{
+	diff_queue(&diff_queued_diff,
+		   get_filespec("a", a), get_filespec("b", b));
+
+	diffcore_std(diffopt);
+	diff_flush(diffopt);
+}
+
+static void output(struct string_list *a, struct string_list *b,
+		   struct diff_options *diffopt)
 {
 	int i = 0, j = 0;
 
@@ -296,6 +321,9 @@ static void output(struct string_list *a, struct string_list *b)
 			printf("%d: %s ! %d: %s\n",
 			       b_util->matching + 1, short_oid(a_util),
 			       j + 1, short_oid(b_util));
+			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
+				patch_diff(a->items[b_util->matching].string,
+					   b->items[j].string, diffopt);
 			a_util->shown = 1;
 			j++;
 		}
@@ -303,7 +331,7 @@ static void output(struct string_list *a, struct string_list *b)
 }
 
 int show_range_diff(const char *range1, const char *range2,
-		    int creation_factor)
+		    int creation_factor, struct diff_options *diffopt)
 {
 	int res = 0;
 
@@ -318,7 +346,7 @@ int show_range_diff(const char *range1, const char *range2,
 	if (!res) {
 		find_exact_matches(&branch1, &branch2);
 		get_correspondences(&branch1, &branch2, creation_factor);
-		output(&branch1, &branch2);
+		output(&branch1, &branch2, diffopt);
 	}
 
 	string_list_clear(&branch1, 1);
diff --git a/range-diff.h b/range-diff.h
index dd30449c4..aea9d43f3 100644
--- a/range-diff.h
+++ b/range-diff.h
@@ -1,7 +1,9 @@
 #ifndef BRANCH_DIFF_H
 #define BRANCH_DIFF_H
 
+#include "diff.h"
+
 int show_range_diff(const char *range1, const char *range2,
-		    int creation_factor);
+		    int creation_factor, struct diff_options *diffopt);
 
 #endif
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v3 10/20] range-diff: do not show "function names" in hunk headers
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
                       ` (17 preceding siblings ...)
  2018-05-06 15:26     ` [PATCH v3 05/20] range-diff: also show the diff between patches Johannes Schindelin via GitGitGadget
@ 2018-05-06 15:35     ` Johannes Schindelin via GitGitGadget
  2018-06-30 20:41     ` [PATCH v3 20/20] range-diff: make --dual-color the default mode Johannes Schindelin via GitGitGadget
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
  20 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-05-06 15:35 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

We are comparing complete, formatted commit messages with patches. There
are no function names here, so stop looking for them.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/range-diff.c b/range-diff.c
index 0e0e77106..8df73da4e 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -9,6 +9,7 @@
 #include "diffcore.h"
 #include "commit.h"
 #include "pretty.h"
+#include "userdiff.h"
 
 struct patch_util {
 	/* For the search for an exact match */
@@ -306,6 +307,10 @@ static void output_pair_header(struct strbuf *buf,
 	fwrite(buf->buf, buf->len, 1, stdout);
 }
 
+static struct userdiff_driver no_func_name = {
+	.funcname = { "$^", 0 }
+};
+
 static struct diff_filespec *get_filespec(const char *name, const char *p)
 {
 	struct diff_filespec *spec = alloc_filespec(name);
@@ -315,6 +320,7 @@ static struct diff_filespec *get_filespec(const char *name, const char *p)
 	spec->size = strlen(p);
 	spec->should_munmap = 0;
 	spec->is_stdin = 1;
+	spec->driver = &no_func_name;
 
 	return spec;
 }
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-06 12:21           ` Johannes Schindelin
@ 2018-05-06 20:51             ` Eric Sunshine
  2018-05-07  2:04               ` Johannes Schindelin
  2018-05-07  1:45             ` Junio C Hamano
  1 sibling, 1 reply; 387+ messages in thread
From: Eric Sunshine @ 2018-05-06 20:51 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Junio C Hamano, Jeff King, Git List, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller

On Sun, May 6, 2018 at 8:21 AM, Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:
> On Sun, 6 May 2018, Junio C Hamano wrote:
>> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>> > On Sat, 5 May 2018, Jeff King wrote:
>> >> One minor point about the name: will it become annoying as a tab
>> >> completion conflict with git-branch?
>>
>> If tbdiff were "Thomas's branch diff", I would call this jbdiff ;-)
>> but I think the 't' in there stands for "topic", not "Thomas's".
>> How about "git topic-diff"?
>
> Or `git topic-branch-diff`?
>
> But then, we do not really use the term `topic branch` a lot in Git, *and*
> the operation in question is not really about showing differences between
> topic branches, but between revisions of topic branches.
>
> So far, the solution I like best is to use `git branch --diff <...>`,
> which also neatly side-steps the problem of cluttering the top-level
> command list (because tab completion).

Let's, please, not fall into the trap of polluting git-branch with
utterly unrelated functionality, as has happened a few times with
other Git commands. Let's especially not do so merely for the sake of
tab-completion. git-branch is for branch management; it's not for
diff'ing.

Of the suggestions thus far, Junio's git-topic-diff seems the least
worse, and doesn't suffer from tab-completion problems.

Building on Duy's suggestion: git-interdiff could be a superset of the
current git-branch-diff:

    # standard interdiff
    git interdiff womp-v1 womp-v2
    # 'tbdiff'-like output
    git interdiff --topic womp-v1 womp-v2

(Substitute "--topic" by any other better name.)

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
                     ` (19 preceding siblings ...)
  2018-05-06  5:22   ` Junio C Hamano
@ 2018-05-06 22:56   ` brian m. carlson
  2018-05-07  2:05     ` Johannes Schindelin
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
  21 siblings, 1 reply; 387+ messages in thread
From: brian m. carlson @ 2018-05-06 22:56 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

[-- Attachment #1: Type: text/plain, Size: 861 bytes --]

On Fri, May 04, 2018 at 05:34:27PM +0200, Johannes Schindelin wrote:
> 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.

I just want to say thanks for writing this.  I use tbdiff extensively at
work and having this built-in and much faster will really help.

I did a once-over of v1 and I'll probably take a look at v2 or v3
(whatever's the latest) later in the week.
-- 
brian m. carlson: Houston, Texas, US
OpenPGP: https://keybase.io/bk2204

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 867 bytes --]

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 13/18] color: provide inverted colors, too
  2018-05-06  6:41           ` Jeff King
@ 2018-05-07  1:20             ` Johannes Schindelin
  2018-05-07  7:37               ` Jeff King
  2018-05-07  1:35             ` Junio C Hamano
  1 sibling, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-07  1:20 UTC (permalink / raw)
  To: Jeff King
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Peff,

On Sun, 6 May 2018, Jeff King wrote:

> On Sun, May 06, 2018 at 02:35:44AM -0400, Jeff King wrote:
> 
> > You'd have to introduce GIT_COLOR_REVERSE. I don't think we have a
> > constant for it yet, but it's \x[7m.
> 
> Heh, of course you knew that already, as I just noticed your patch is
> using the reverse attribute internally (I had thought at first glance
> you were just specifying the background independently).
> 
> So really, I guess all I am arguing for is having GIT_COLOR_INV (or
> REVERSE) as a constant, and then teaching the code to combine it with
> the existing "new" color. It's perfectly OK to have:
> 
>   \x1b[7m\x1b[36m
> 
> instead of:
> 
>   \x1b[7;36m
> 
> It's two extra bytes, but I doubt anybody cares.

Yep, I agree that it is a small price to pay for the benefit of simply
using the reverse of diff.color.old (and .new).

While at it, I also changed the hunk header colors: they are *also* simply
the same ones, with the outer one having background and foreground
reversed.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 18/18] completion: support branch-diff
  2018-05-06  8:24     ` Duy Nguyen
@ 2018-05-07  1:23       ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-07  1:23 UTC (permalink / raw)
  To: Duy Nguyen
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Duy,

On Sun, 6 May 2018, Duy Nguyen wrote:

> On Fri, May 04, 2018 at 05:35:11PM +0200, Johannes Schindelin wrote:
> > Tab completion of `branch-diff` is very convenient, especially given
> > that the revision arguments that need to be passed to `git branch-diff`
> > are typically more complex than, say, your grandfather's `git log`
> > arguments.
> > 
> > Without this patch, we would only complete the `branch-diff` part but
> > not the options and other arguments.
> > 
> > This of itself may already be slightly disruptive for well-trained
> > fingers that assume that `git bra<TAB>ori<TAB>mas<TAB>` would expand to
> > `git branch origin/master`, as we now no longer automatically append a
> > space after completing `git branch`: this is now ambiguous.
> > 
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> >  contrib/completion/git-completion.bash | 18 ++++++++++++++++++
> >  1 file changed, 18 insertions(+)
> > 
> > diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
> > index 01dd9ff07a2..45addd525ac 100644
> > --- a/contrib/completion/git-completion.bash
> > +++ b/contrib/completion/git-completion.bash
> > @@ -1496,6 +1496,24 @@ _git_format_patch ()
> >  	__git_complete_revlist
> >  }
> >  
> > +__git_branch_diff_options="
> > +	--no-patches --creation-weight= --dual-color
> > +"
> > +
> > +_git_branch_diff ()
> > +{
> > +	case "$cur" in
> > +	--*)
> > +		__gitcomp "
> 
> You should use __gitcomp_builtin so you don't have to maintain
> $__git_branch_diff_options here. Something like this
> 
> -- 8< --
> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
> index 45addd525a..4745631daf 100644
> --- a/contrib/completion/git-completion.bash
> +++ b/contrib/completion/git-completion.bash
> @@ -1496,18 +1496,11 @@ _git_format_patch ()
>  	__git_complete_revlist
>  }
>  
> -__git_branch_diff_options="
> -	--no-patches --creation-weight= --dual-color
> -"
> -
>  _git_branch_diff ()
>  {
>  	case "$cur" in
>  	--*)
> -		__gitcomp "
> -			$__git_branch_diff_options
> -			$__git_diff_common_options
> -			"
> +		__gitcomp_builtin branch-diff "$__git_diff_common_options"
>  		return
>  		;;
>  	esac
> -- 8< --

Does this really work? I have this instead, for now, and verified that it
works:

-- snipsnap --
diff --git a/contrib/completion/git-completion.bash
b/contrib/completion/git-completion.bash
index 01dd9ff07a2..c498c053881 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1205,13 +1205,14 @@ _git_bisect ()

 _git_branch ()
 {
-       local i c=1 only_local_ref="n" has_r="n"
+       local i c=1 only_local_ref="n" has_r="n" diff_mode="n"

        while [ $c -lt $cword ]; do
                i="${words[c]}"
                case "$i" in
                -d|--delete|-m|--move)  only_local_ref="y" ;;
                -r|--remotes)           has_r="y" ;;
+               --diff)                 diff_mode="y" ;;
                esac
                ((c++))
        done
@@ -1221,11 +1222,22 @@ _git_branch ()
                __git_complete_refs --cur="${cur##--set-upstream-to=}"
                ;;
        --*)
+               if [ $diff_mode = "y" ]; then
+                       __gitcomp "
+                               --creation-factor= --dual-color
+                               $__git_diff_common_options
+                               "
+                       return
+               fi
                __gitcomp_builtin branch "--no-color --no-abbrev
                        --no-track --no-column
                        "
                ;;
        *)
+               if [ $diff_mode = "y" ]; then
+                       __git_complete_revlist
+                       return
+               fi
                if [ $only_local_ref = "y" -a $has_r = "n" ]; then
                        __gitcomp_direct "$(__git_heads "" "$cur" " ")"
                else


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-06 13:37             ` Igor Djordjevic
@ 2018-05-07  1:34               ` Johannes Schindelin
  2018-05-07 22:05                 ` Igor Djordjevic
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-07  1:34 UTC (permalink / raw)
  To: Igor Djordjevic
  Cc: Jeff King, git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Buga,

On Sun, 6 May 2018, Igor Djordjevic wrote:

> On 06/05/2018 14:10, Johannes Schindelin wrote:
> 
> > I think Todd's idea to shift it from a full-blown builtin to a cmdmode
> > of `branch` makes tons of sense.
> 
> I don`t know, I still find it a bit strange that in order to "diff
> something", you go to "something" and tell it to "diff itself" - not
> because it`s a weird concept (OOP, anyone? :]), but because we already
> have "diff" command that can accept different things, thus just teaching
> it to accept additional "something" (branch, in this case), seems more
> natural (to me) - "branch diff" being just another "diff" mode of
> operation.

You also have to call `git branch` to list branches. And to rename
branches. And to delete them. So why not also compare them at the same
time?

> What about that side thought you left out from my original message,
> making it `git diff --branch` instead?

I really did not like this, as all of the `git diff` options really are
about comparing two revisions, not two *sets* of revisions.

Further, if I put my unsuspecting user hat on, I would ask myself how you
can compare branches with one another? That is what I would expect `git
diff --branch` to do, not to compare two versions of *the same* branch.

So `git diff --branch` does not at all convey the same to me as `git
branch --diff`, and I find that the latter does match better what this
patch series tries to achieve.

I briefly considered `git branch --compare` instead, but then rejected it:
it would again sound more like I try to compare two separate (and likely
unrelated) branches with one another, and that simply does not make much
sense, and tbdiff would not help with that, anyway.

> But if "branch diff" is considered to be too special-cased mode of
> "diff" so that supporting it from `diff` itself would make it feel
> awkward in both usage and maintenance (in terms of many other regular
> `diff` specific options being unsupported), I guess I would understand
> having it outside `diff` altogether (and implemented as proposed `git
> branch --diff`, or something)... for the time being, at least :)

The branch diff is not even a special-cased mode of diff. It is *way* more
complicated than that. It tries to find 1:1 correspondences between *sets*
of commits, and then only outputs a "sort" of a diff between the commits
that correspond with each other. I say "sort" of a diff because that diff
does not look like `git diff <commit1> <commit2>` at all!

So I think it would just be confusing to add that mode to `git diff`.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 13/18] color: provide inverted colors, too
  2018-05-06  6:41           ` Jeff King
  2018-05-07  1:20             ` Johannes Schindelin
@ 2018-05-07  1:35             ` Junio C Hamano
  2018-05-07  5:38               ` Johannes Schindelin
  2018-05-07  7:40               ` Jeff King
  1 sibling, 2 replies; 387+ messages in thread
From: Junio C Hamano @ 2018-05-07  1:35 UTC (permalink / raw)
  To: Jeff King
  Cc: Johannes Schindelin, git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Jeff King <peff@peff.net> writes:

> On Sun, May 06, 2018 at 02:35:44AM -0400, Jeff King wrote:
>
>> You'd have to introduce GIT_COLOR_REVERSE. I don't think we have a
>> constant for it yet, but it's \x[7m.
>
> Heh, of course you knew that already, as I just noticed your patch is
> using the reverse attribute internally (I had thought at first glance
> you were just specifying the background independently).

I somehow suspected as such, but I also thought so and reacted "what
about us whose terminal is black-on-white unlike most others?",
before looking up what 7 meant ;-)

> So really, I guess all I am arguing for is having GIT_COLOR_INV (or
> REVERSE) as a constant, and then teaching the code to combine it with
> the existing "new" color. It's perfectly OK to have:
>
>   \x1b[7m\x1b[36m
>
> instead of:
>
>   \x1b[7;36m
>
> It's two extra bytes, but I doubt anybody cares.

I do not think two extra bytes will be missed, but it was not
immediately obvious to me how much flexibility or simplicity weu are
gaining by combining values from multiple configuration variables.
With a "letters on a new line is painted with ${new}, in addition,
the leading plus is further annotated with ${tbdiffNew}" (similarly
to "old") scheme, the user can take advantage of the fact that there
is no ${reset} between ${new} and ${tbdiffNew} and set tbdiffNew and
tbdiffOld to a same value (that does not change the color but
changes some other aspect of the appearance, like "reverse" or
"underline").  Since only pre-designed combination can be used (your
example works only because you chose to allow combination by
annotating the leading "+" with ${new}${tbdiffNew}), we'd need to
(1) establish a convention to paint things with similar meanings in
the same color, modifyable by individual command (e.g. you could say
anything new is by default green with "color.new=green", and then
"color.frotz.new=blink" "color.status.new=" "color.diff.new=blue"
would make frotz, status and diff subcommands to show new things in
blinking green, normal green, and blue), and (2) push the codebase
to adopt such color combination as a preferred design pattern if we
want the resulting system to be useful.

I guess you are getting simpler configuration, which is a big plus,
but to make a truly useful combining convention, we'd need to
rethink and find a way to transition existing configurations to the
new world, which may not be feasible.


^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-06 12:21           ` Johannes Schindelin
  2018-05-06 20:51             ` Eric Sunshine
@ 2018-05-07  1:45             ` Junio C Hamano
  2018-05-07  5:39               ` Johannes Schindelin
  1 sibling, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-05-07  1:45 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Jeff King, git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

>> If tbdiff were "Thomas's branch diff", I would call this jbdiff ;-)
>> but I think the 't' in there stands for "topic", not "Thomas's".
>> 
>> How about "git topic-diff"?
>
> Or `git topic-branch-diff`?

Yeah something along that line, which is about comparing each step
in two iterations of a single topic.  It would be wonderful if it
also supported a short-hand

	$ git tbdiff --reflog 1.day.ago js/branch-diff

that turned into:

	$ git tbdiff js/branch-diff..js/branch-diff@{1.day.ago} \
			js/branch-diff@{1.day.ago}..js/branch-diff

That compares "what was on the topic a day ago" with "what is new on
the topic since that time", which is exactly what an individual
contributor wants when reviewing how the topic was polished, I would
say.


[Footnote]

A variant I often use when accepting a rerolled series is

	$ git checkout js/branch-diff
	$ git checkout master...
	$ git am ./+js-branch-diff-v2
	$ git tbdiff ..@{-1} @{-1}..

so this is not only for individual contributors but also helps
integrators.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 12/18] branch-diff: use color for the commit pairs
  2018-05-05 23:48     ` Todd Zullinger
@ 2018-05-07  1:52       ` Johannes Schindelin
  2018-05-08  2:10         ` Todd Zullinger
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-07  1:52 UTC (permalink / raw)
  To: Todd Zullinger
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Todd,

On Sat, 5 May 2018, Todd Zullinger wrote:

> > @@ -430,6 +451,8 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
> >  	struct string_list branch1 = STRING_LIST_INIT_DUP;
> >  	struct string_list branch2 = STRING_LIST_INIT_DUP;
> >  
> > +	git_diff_basic_config("diff.color.frag", "magenta", NULL);
> > +
> >  	diff_setup(&diffopt);
> >  	diffopt.output_format = DIFF_FORMAT_PATCH;
> >  	diffopt.flags.suppress_diff_headers = 1;
> 
> Should this also (or only) check color.diff.frag?

This code is not querying diff.color.frag, it is setting it. Without
any way to override it.

Having thought about it longer, and triggered by Peff's suggestion to
decouple the "reverse" part from the actual color, I fixed this by

- *not* setting .frag to magenta,

- using the reverse method also to mark outer *hunk headers* (not only the
  outer -/+ markers).

- actually calling git_diff_ui_config()...

>  I thought that color.diff.* was preferred over diff.color.*, though
>  that doesn't seem to be entirely true in all parts of the current
>  codebase.
> 
> In testing this series it seems that setting color.diff
> options to change the various colors read earlier in this
> patch via diff_get_color_opt, as well as the 'frag' slot,
> are ignored.  Setting them via diff.color.<slot> does work.

In my tests, it did not even work via diff.color.<slot>. But I think I
fixed this (at least my local testing confirms this) by calling
git_diff_ui_config().

> The later patch adding a man page documents branch-diff as
> using `diff.color.*` and points to git-config(1), but the
> config docs only list color.diff.

In the current form (`git branch --diff`), I refrained from going into
*so* much detail ;-) But the gist still holds, and now the code should
support it, too.

The current work in progress can be pulled as `branch-diff` from
https://github.com/dscho/git, if I could ask you to test?

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 07/18] branch-diff: indent the diffs just like tbdiff
  2018-05-06 14:15     ` Martin Ågren
@ 2018-05-07  1:54       ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-07  1:54 UTC (permalink / raw)
  To: Martin Ågren
  Cc: Git Mailing List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

[-- Attachment #1: Type: text/plain, Size: 1369 bytes --]

Hi Martin,

On Sun, 6 May 2018, Martin Ågren wrote:

> On 4 May 2018 at 17:34, Johannes Schindelin <johannes.schindelin@gmx.de> wrote:
> > @@ -353,6 +358,7 @@ static void output(struct string_list *a, struct string_list *b,
> >  int cmd_branch_diff(int argc, const char **argv, const char *prefix)
> >  {
> >         struct diff_options diffopt = { NULL };
> > +       struct strbuf four_spaces = STRBUF_INIT;
> >         double creation_weight = 0.6;
> >         struct option options[] = {
> >                 OPT_SET_INT(0, "no-patches", &diffopt.output_format,
> > @@ -371,6 +377,9 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
> >
> >         diff_setup(&diffopt);
> >         diffopt.output_format = DIFF_FORMAT_PATCH;
> > +       diffopt.output_prefix = output_prefix_cb;
> > +       strbuf_addstr(&four_spaces, "    ");
> > +       diffopt.output_prefix_data = &four_spaces;
> >
> >         argc = parse_options(argc, argv, NULL, options,
> >                         builtin_branch_diff_usage, PARSE_OPT_KEEP_UNKNOWN);
> 
> You end up leaking the buffer of `four_spaces`. Granted, that's not a
> big memory leak, but still. ;-) This was the only leak that
> LeakSanitizer found in v2 when running the new test-script and playing
> around with this a bit. This looks really good!

Good point. Fixed.
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-06 20:51             ` Eric Sunshine
@ 2018-05-07  2:04               ` Johannes Schindelin
  2018-05-07  7:48                 ` Jeff King
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-07  2:04 UTC (permalink / raw)
  To: Eric Sunshine
  Cc: Junio C Hamano, Jeff King, Git List, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller

Hi Eric,

On Sun, 6 May 2018, Eric Sunshine wrote:

> On Sun, May 6, 2018 at 8:21 AM, Johannes Schindelin
> <Johannes.Schindelin@gmx.de> wrote:
> > On Sun, 6 May 2018, Junio C Hamano wrote:
> >> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> >> > On Sat, 5 May 2018, Jeff King wrote:
> >> >> One minor point about the name: will it become annoying as a tab
> >> >> completion conflict with git-branch?
> >>
> >> If tbdiff were "Thomas's branch diff", I would call this jbdiff ;-)
> >> but I think the 't' in there stands for "topic", not "Thomas's".
> >> How about "git topic-diff"?
> >
> > Or `git topic-branch-diff`?
> >
> > But then, we do not really use the term `topic branch` a lot in Git, *and*
> > the operation in question is not really about showing differences between
> > topic branches, but between revisions of topic branches.
> >
> > So far, the solution I like best is to use `git branch --diff <...>`,
> > which also neatly side-steps the problem of cluttering the top-level
> > command list (because tab completion).
> 
> Let's, please, not fall into the trap of polluting git-branch with
> utterly unrelated functionality, as has happened a few times with
> other Git commands. Let's especially not do so merely for the sake of
> tab-completion. git-branch is for branch management; it's not for
> diff'ing.

I totally disagree. `git branch` is *the* command to work with branches.
Yes, you can manage branches. But you can also list them. And now you can
also compare them.

> Of the suggestions thus far, Junio's git-topic-diff seems the least
> worse, and doesn't suffer from tab-completion problems.

Except that this is too limited a view.

Have you seen one of the more important tidbits in the cover letter, the
one about Git for Windows' *branch thicket*? In this case, it is not *one*
topic branch that we are talking about.

And even worse: what this patch series introduces is not at all a feature
to compare topic branches!

Instead, it is a way to compare iterations of patch series, versions of
topic branches, changes introduced into a topic branch by rebasing it,
etc. And `git topic-diff` simply does not say this. It says something
different, something that my patches cannot fulfill.

> Building on Duy's suggestion: git-interdiff could be a superset of the
> current git-branch-diff:
> 
>     # standard interdiff
>     git interdiff womp-v1 womp-v2
>     # 'tbdiff'-like output
>     git interdiff --topic womp-v1 womp-v2

No, no, and no. An interdiff is an interdiff is an interdiff. See e.g.
https://www.tutorialspoint.com/unix_commands/interdiff.htm for details.

The operation introduced by this patch series, or for that matter tbdiff,
*never ever* produced an interdiff. Get this "interdiff" label out of your
mind immediately when you think about this here operation.

One of my commit messages even talks about this, and says *why* we do not
generate interdiffs: they are in general not even well-defined.

Take my --rebase-merges patch series, for example. It is so long-running
that at some stages, all I did was to resolve merge conflicts incurred
from rebasing to `master`. That was literally all. Now, if you tried to
produce an interdiff, you would *already fail in the first step*, as the
previous overall diff does not apply in reverse on current `master`.

Out of all the options so far, the one that I liked was `git branch
--diff`. Seriously. I do not understand why you think that this is abusing
the `git branch` command. It is no less abusing it than `git branch
--edit-description`! And that is a *very good* command, and it is *very
good* that it is an option to `git branch`. It makes a total lot of sense,
I have never had to think "wait, in which Git command is this implemented
already?" And I would expect the exact same thing to happen with `git
branch --diff`.

Ciao,
Johannes

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-06 22:56   ` brian m. carlson
@ 2018-05-07  2:05     ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-07  2:05 UTC (permalink / raw)
  To: brian m. carlson
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Brian,

On Sun, 6 May 2018, brian m. carlson wrote:

> On Fri, May 04, 2018 at 05:34:27PM +0200, Johannes Schindelin wrote:
> > 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.
> 
> I just want to say thanks for writing this.  I use tbdiff extensively at
> work and having this built-in and much faster will really help.
> 
> I did a once-over of v1 and I'll probably take a look at v2 or v3
> (whatever's the latest) later in the week.

Thank you so much!
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 13/18] color: provide inverted colors, too
  2018-05-07  1:35             ` Junio C Hamano
@ 2018-05-07  5:38               ` Johannes Schindelin
  2018-05-07  7:40               ` Jeff King
  1 sibling, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-07  5:38 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Jeff King, git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Junio,

On Mon, 7 May 2018, Junio C Hamano wrote:

> Jeff King <peff@peff.net> writes:
> 
> > So really, I guess all I am arguing for is having GIT_COLOR_INV (or
> > REVERSE) as a constant, and then teaching the code to combine it with
> > the existing "new" color. It's perfectly OK to have:
> >
> >   \x1b[7m\x1b[36m
> >
> > instead of:
> >
> >   \x1b[7;36m
> >
> > It's two extra bytes, but I doubt anybody cares.
> 
> I do not think two extra bytes will be missed, but it was not
> immediately obvious to me how much flexibility or simplicity weu are
> gaining by combining values from multiple configuration variables.
> With a "letters on a new line is painted with ${new}, in addition,
> the leading plus is further annotated with ${tbdiffNew}" (similarly
> to "old") scheme, the user can take advantage of the fact that there
> is no ${reset} between ${new} and ${tbdiffNew} and set tbdiffNew and
> tbdiffOld to a same value (that does not change the color but
> changes some other aspect of the appearance, like "reverse" or
> "underline").  Since only pre-designed combination can be used (your
> example works only because you chose to allow combination by
> annotating the leading "+" with ${new}${tbdiffNew}), we'd need to
> (1) establish a convention to paint things with similar meanings in
> the same color, modifyable by individual command (e.g. you could say
> anything new is by default green with "color.new=green", and then
> "color.frotz.new=blink" "color.status.new=" "color.diff.new=blue"
> would make frotz, status and diff subcommands to show new things in
> blinking green, normal green, and blue), and (2) push the codebase
> to adopt such color combination as a preferred design pattern if we
> want the resulting system to be useful.
> 
> I guess you are getting simpler configuration, which is a big plus,
> but to make a truly useful combining convention, we'd need to
> rethink and find a way to transition existing configurations to the
> new world, which may not be feasible.

I really do not like the sound of that much complexity. It strikes me as
yet another instance of Yer Ain't Gonna Need It. In *particular* because
nested diffs are a special thing: you *already* get overwhelmed with
too much information, and adding colors to the fray won't help.

What does help is to keep the colors, so that they can mean the same thing
in inner vs outer diffs, but reverse foreground and background to make the
outer diff "stick out more".

Should my assessment be wrong, I think it'll still be relatively easy to
add support for config settings, *then*, not before we know it is needed.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-07  1:45             ` Junio C Hamano
@ 2018-05-07  5:39               ` Johannes Schindelin
  2018-05-07 15:12                 ` Junio C Hamano
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-07  5:39 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Jeff King, git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Junio,

On Mon, 7 May 2018, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> >> If tbdiff were "Thomas's branch diff", I would call this jbdiff ;-)
> >> but I think the 't' in there stands for "topic", not "Thomas's".
> >> 
> >> How about "git topic-diff"?
> >
> > Or `git topic-branch-diff`?
> 
> Yeah something along that line, which is about comparing each step
> in two iterations of a single topic.  It would be wonderful if it
> also supported a short-hand
> 
> 	$ git tbdiff --reflog 1.day.ago js/branch-diff
> 
> that turned into:
> 
> 	$ git tbdiff js/branch-diff..js/branch-diff@{1.day.ago} \
> 			js/branch-diff@{1.day.ago}..js/branch-diff

Or even easier: `git tbdiff js/branch-diff@{1.day.ago}...js/branch-diff`.

> That compares "what was on the topic a day ago" with "what is new on
> the topic since that time", which is exactly what an individual
> contributor wants when reviewing how the topic was polished, I would
> say.

It would be easy to introduce, but I am wary about its usefulness.
Unless you re-generate the branch from patches (which I guess you do a
lot, but I don't), you are likely to compare incomplete patch series: say,
when you call `git rebase -i` to reword 05/18's commit message, your
command will only compare 05--18 of the patch series.

Worse, if js/branch-diff needs to be uprooted (e.g. because it now depends
on some different patch, or because it already depended on a separate
patch series that was now updated), your `git branch --diff` call will
compare more than just my patches: it will assume that those dependencies
are part of the patch series, because they changed, too.

> [Footnote]
> 
> A variant I often use when accepting a rerolled series is
> 
> 	$ git checkout js/branch-diff
> 	$ git checkout master...
> 	$ git am ./+js-branch-diff-v2
> 	$ git tbdiff ..@{-1} @{-1}..
> 
> so this is not only for individual contributors but also helps
> integrators.

Yes, and I also pointed out (twice) that it will help interested parties
follow what I do with my merging-rebases in Git for Windows.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 13/18] color: provide inverted colors, too
  2018-05-07  1:20             ` Johannes Schindelin
@ 2018-05-07  7:37               ` Jeff King
  0 siblings, 0 replies; 387+ messages in thread
From: Jeff King @ 2018-05-07  7:37 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

On Sun, May 06, 2018 at 09:20:46PM -0400, Johannes Schindelin wrote:

> > Heh, of course you knew that already, as I just noticed your patch is
> > using the reverse attribute internally (I had thought at first glance
> > you were just specifying the background independently).
> > 
> > So really, I guess all I am arguing for is having GIT_COLOR_INV (or
> > REVERSE) as a constant, and then teaching the code to combine it with
> > the existing "new" color. It's perfectly OK to have:
> > 
> >   \x1b[7m\x1b[36m
> > 
> > instead of:
> > 
> >   \x1b[7;36m
> > 
> > It's two extra bytes, but I doubt anybody cares.
> 
> Yep, I agree that it is a small price to pay for the benefit of simply
> using the reverse of diff.color.old (and .new).
> 
> While at it, I also changed the hunk header colors: they are *also* simply
> the same ones, with the outer one having background and foreground
> reversed.

That sound sane.

If we ever did want to care about the number of bytes we output, I
suspect we could "compress" our ANSI terminal outputs by collapsing
adjacent colors into a single one. But IMHO it's not even worth worrying
about that optimization at this point.

-Peff

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 13/18] color: provide inverted colors, too
  2018-05-07  1:35             ` Junio C Hamano
  2018-05-07  5:38               ` Johannes Schindelin
@ 2018-05-07  7:40               ` Jeff King
  1 sibling, 0 replies; 387+ messages in thread
From: Jeff King @ 2018-05-07  7:40 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Johannes Schindelin, git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

On Mon, May 07, 2018 at 10:35:53AM +0900, Junio C Hamano wrote:

> > So really, I guess all I am arguing for is having GIT_COLOR_INV (or
> > REVERSE) as a constant, and then teaching the code to combine it with
> > the existing "new" color. It's perfectly OK to have:
> >
> >   \x1b[7m\x1b[36m
> >
> > instead of:
> >
> >   \x1b[7;36m
> >
> > It's two extra bytes, but I doubt anybody cares.
> 
> I do not think two extra bytes will be missed, but it was not
> immediately obvious to me how much flexibility or simplicity weu are
> gaining by combining values from multiple configuration variables.

My goal was just to let you set color.diff.new to something besides
green without having to also manually set color.tbdiff.new (or whatever
it's called) to match.

> With a "letters on a new line is painted with ${new}, in addition,
> the leading plus is further annotated with ${tbdiffNew}" (similarly
> to "old") scheme, the user can take advantage of the fact that there
> is no ${reset} between ${new} and ${tbdiffNew} and set tbdiffNew and
> tbdiffOld to a same value (that does not change the color but
> changes some other aspect of the appearance, like "reverse" or
> "underline").  Since only pre-designed combination can be used (your
> example works only because you chose to allow combination by
> annotating the leading "+" with ${new}${tbdiffNew}), we'd need to
> (1) establish a convention to paint things with similar meanings in
> the same color, modifyable by individual command (e.g. you could say
> anything new is by default green with "color.new=green", and then
> "color.frotz.new=blink" "color.status.new=" "color.diff.new=blue"
> would make frotz, status and diff subcommands to show new things in
> blinking green, normal green, and blue), and (2) push the codebase
> to adopt such color combination as a preferred design pattern if we
> want the resulting system to be useful.

Right, this is basically making that "new" piggy-backing explicit, but
only for this one case.

> I guess you are getting simpler configuration, which is a big plus,
> but to make a truly useful combining convention, we'd need to
> rethink and find a way to transition existing configurations to the
> new world, which may not be feasible.

Yes, one could probably develop a whole theming system for Git. We've
resisted it so far. :)

-Peff

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-07  2:04               ` Johannes Schindelin
@ 2018-05-07  7:48                 ` Jeff King
  2018-05-07 21:33                   ` Igor Djordjevic
  2018-05-08  0:30                   ` Junio C Hamano
  0 siblings, 2 replies; 387+ messages in thread
From: Jeff King @ 2018-05-07  7:48 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Eric Sunshine, Junio C Hamano, Git List, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller

On Sun, May 06, 2018 at 10:04:31PM -0400, Johannes Schindelin wrote:

> > Let's, please, not fall into the trap of polluting git-branch with
> > utterly unrelated functionality, as has happened a few times with
> > other Git commands. Let's especially not do so merely for the sake of
> > tab-completion. git-branch is for branch management; it's not for
> > diff'ing.
> 
> I totally disagree. `git branch` is *the* command to work with branches.
> Yes, you can manage branches. But you can also list them. And now you can
> also compare them.

One of the things I don't like about "git branch --diff" is that this
feature is not _just_ about branches at all. E.g., I could do:

  git tbdiff HEAD~10 HEAD~5 foo

Or even:

  git tbdiff v2.16.0 v2.17.0 my-rewritten-v2.17.0

Those arguments really are just commitishes, not necessarily branches.
One of the current interface rules for "git branch" is that the branch
names we hand it are interpreted _exactly_ as branch names. You cannot
"git branch -m v2.16.0", and there is no ambiguity in "git branch -d
foo" if "foo" is both a tag and a branch.

But this new mode does not fit the pattern at all.

If we were to attach this to an existing command, I think it has more to
do with "diff" than "branch". But I'm not sure we want to overload
"diff" either (which has traditionally been about two endpoints, and
does not really traverse at all, though arguably "foo...bar" is a bit of
a cheat :) ).

> > Of the suggestions thus far, Junio's git-topic-diff seems the least
> > worse, and doesn't suffer from tab-completion problems.
> 
> Except that this is too limited a view.

Right, I agree with you. Topic branches are the intended use, but that's
not what it _does_, and obviously it can be applied in other cases. So
since "branch" is too specific, I think "topic branch" is even more so.

It's really "diff-history" or something, I think. That's not very
catchy, but I think the best name would imply that it was diffing a set
of commits (so even "diff-commit" would not be right, because that again
sounds like endpoints).

-Peff

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-05 21:57       ` Johannes Schindelin
                           ` (2 preceding siblings ...)
  2018-05-06  2:33         ` Junio C Hamano
@ 2018-05-07  7:50         ` Jeff King
  2018-05-07 15:28           ` Duy Nguyen
  3 siblings, 1 reply; 387+ messages in thread
From: Jeff King @ 2018-05-07  7:50 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

On Sat, May 05, 2018 at 11:57:26PM +0200, Johannes Schindelin wrote:

> > It feels really petty complaining about the name, but I just want to
> > raise the point, since it will never be easier to change than right now.
> 
> I do hear you. Especially since I hate `git cherry` every single time that
> I try to tab-complete `git cherry-pick`.

Me too. :)

I've wondered if "git pick" would be a good alias for cherry-pick (the
"cherry" metaphor is probably not well understood by most users). And
"revert" should just be "pick -R", but that is a whole other discussion.

-Peff

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-07  5:39               ` Johannes Schindelin
@ 2018-05-07 15:12                 ` Junio C Hamano
  2018-05-21 10:41                   ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-05-07 15:12 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Jeff King, git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> It would be easy to introduce, but I am wary about its usefulness.
> Unless you re-generate the branch from patches (which I guess you do a
> lot, but I don't), you are likely to compare incomplete patch series: say,
> when you call `git rebase -i` to reword 05/18's commit message, your
> command will only compare 05--18 of the patch series.

Well that is exactly the point of that "..@{1} @{1}..", which turned
out to be very useful in practice at least for me when I am updating
a topic with "rebase -i", and then reviewing what I did with tbdiff.

I do not want 01-04 in the above case as I already know I did not
touch them.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-07  7:50         ` Jeff King
@ 2018-05-07 15:28           ` Duy Nguyen
  2018-05-07 19:58             ` Stefan Beller
  0 siblings, 1 reply; 387+ messages in thread
From: Duy Nguyen @ 2018-05-07 15:28 UTC (permalink / raw)
  To: Jeff King
  Cc: Johannes Schindelin, Git Mailing List, Junio C Hamano,
	Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

On Mon, May 7, 2018 at 9:50 AM, Jeff King <peff@peff.net> wrote:
> On Sat, May 05, 2018 at 11:57:26PM +0200, Johannes Schindelin wrote:
>
>> > It feels really petty complaining about the name, but I just want to
>> > raise the point, since it will never be easier to change than right now.
>>
>> I do hear you. Especially since I hate `git cherry` every single time that
>> I try to tab-complete `git cherry-pick`.
>
> Me too. :)

Just so you know I'm also not happy with that "git cherry". Since I'm
updating git-completion.bash in this area and we got 3 "me too" votes
(four if we count Szeder in another thread), I'm going to implementing
something to at least let you exclude "cherry" from the completion
list if you want.
-- 
Duy

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-05 20:03     ` Johannes Schindelin
@ 2018-05-07 17:07       ` Elijah Newren
  2018-05-07 17:50         ` SZEDER Gábor
  0 siblings, 1 reply; 387+ messages in thread
From: Elijah Newren @ 2018-05-07 17:07 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git Mailing List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Dscho,

On Sat, May 5, 2018 at 1:03 PM, Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:
> Hi Elijah,
>
> On Fri, 4 May 2018, Elijah Newren wrote:
>
<snip>
>> - tbdiff aligned output columns better when there were more than 9
>> patches (I'll comment more on patch 09/18)
>
> I added a new patch to align the patch numbers specifically. I considered
> squashing it into 9/18, but decided against it: it will make it easier to
> read through the rationale when calling `git annotate` on those lines.

Awesome, thanks.

<snip>
>> Also, I don't have bash-completion for either tbdiff or branch-diff.
>> :-(  But I saw some discussion on the v1 patches about how this gets
>> handled...  :-)
>
> Oh? Does 18/18 not work for you?
> https://public-inbox.org/git/71698f11835311c103aae565a2a761d10f4676b9.1525448066.git.johannes.schindelin@gmx.de/


It looks like it does work, in part, there were just two issues:

1) I apparently wasn't using all the nice improvements from the
completion script in my locally built git, but was instead still using
the one associated with my system-installed (and much older) git.
(Oops, my bad.)


2) Your completion commands for branch-diff will only complete one
revision range, not two.  e.g.
    git branch-diff origin/master..my-topic@{2} origin/master..my-top<tab>
won't complete "my-topic" as I'd expect.


Elijah

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-07 17:07       ` Elijah Newren
@ 2018-05-07 17:50         ` SZEDER Gábor
  2018-05-07 18:38           ` Elijah Newren
  0 siblings, 1 reply; 387+ messages in thread
From: SZEDER Gábor @ 2018-05-07 17:50 UTC (permalink / raw)
  To: Elijah Newren
  Cc: SZEDER Gábor, Johannes Schindelin, Git Mailing List,
	Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

> 2) Your completion commands for branch-diff will only complete one
> revision range, not two.  e.g.
>     git branch-diff origin/master..my-topic@{2} origin/master..my-top<tab>
> won't complete "my-topic" as I'd expect.

It does complete two revision ranges, but if you want to look at
reflogs, then you must escape the opening curly brace.  I'm not sure
why, but apparently after the unescaped '{' Bash thinks that it's a
new command, and doesn't even call our completion functions anymore.
It's not specific to the completion of 'branch-diff', or even to our
completion script.  I don't think we can do anything about it.


^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-07 17:50         ` SZEDER Gábor
@ 2018-05-07 18:38           ` Elijah Newren
  0 siblings, 0 replies; 387+ messages in thread
From: Elijah Newren @ 2018-05-07 18:38 UTC (permalink / raw)
  To: SZEDER Gábor
  Cc: Johannes Schindelin, Git Mailing List, Junio C Hamano,
	Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

On Mon, May 7, 2018 at 10:50 AM, SZEDER Gábor <szeder.dev@gmail.com> wrote:
>> 2) Your completion commands for branch-diff will only complete one
>> revision range, not two.  e.g.
>>     git branch-diff origin/master..my-topic@{2} origin/master..my-top<tab>
>> won't complete "my-topic" as I'd expect.
>
> It does complete two revision ranges, but if you want to look at
> reflogs, then you must escape the opening curly brace.  I'm not sure
> why, but apparently after the unescaped '{' Bash thinks that it's a
> new command, and doesn't even call our completion functions anymore.
> It's not specific to the completion of 'branch-diff', or even to our
> completion script.  I don't think we can do anything about it.

Ah, indeed.  Thanks for the pointer.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-07 15:28           ` Duy Nguyen
@ 2018-05-07 19:58             ` Stefan Beller
  0 siblings, 0 replies; 387+ messages in thread
From: Stefan Beller @ 2018-05-07 19:58 UTC (permalink / raw)
  To: Duy Nguyen
  Cc: Jeff King, Johannes Schindelin, Git Mailing List, Junio C Hamano,
	Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Jacob Keller, Eric Sunshine

On Mon, May 7, 2018 at 8:28 AM, Duy Nguyen <pclouds@gmail.com> wrote:

>>> I do hear you. Especially since I hate `git cherry` every single time that
>>> I try to tab-complete `git cherry-pick`.
>>
>> Me too. :)
>
> Just so you know I'm also not happy with that "git cherry". Since I'm
> updating git-completion.bash in this area and we got 3 "me too" votes
> (four if we count Szeder in another thread), I'm going to implementing
> something to at least let you exclude "cherry" from the completion
> list if you want.

And another "me too" here.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-07  7:48                 ` Jeff King
@ 2018-05-07 21:33                   ` Igor Djordjevic
  2018-05-21 10:33                     ` Johannes Schindelin
  2018-05-08  0:30                   ` Junio C Hamano
  1 sibling, 1 reply; 387+ messages in thread
From: Igor Djordjevic @ 2018-05-07 21:33 UTC (permalink / raw)
  To: Jeff King, Johannes Schindelin
  Cc: Eric Sunshine, Junio C Hamano, Git List, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller

On 07/05/2018 09:48, Jeff King wrote:
> 
> > > Let's, please, not fall into the trap of polluting git-branch with
> > > utterly unrelated functionality, as has happened a few times with
> > > other Git commands. Let's especially not do so merely for the sake of
> > > tab-completion. git-branch is for branch management; it's not for
> > > diff'ing.
> >
> > I totally disagree. `git branch` is *the* command to work with branches.
> > Yes, you can manage branches. But you can also list them. And now you can
> > also compare them.
> 
> One of the things I don't like about "git branch --diff" is that this
> feature is not _just_ about branches at all. E.g., I could do:
> 
>   git tbdiff HEAD~10 HEAD~5 foo
> 
> Or even:
> 
>   git tbdiff v2.16.0 v2.17.0 my-rewritten-v2.17.0
> 
> Those arguments really are just commitishes, not necessarily branches.
> One of the current interface rules for "git branch" is that the branch
> names we hand it are interpreted _exactly_ as branch names. You cannot
> "git branch -m v2.16.0", and there is no ambiguity in "git branch -d
> foo" if "foo" is both a tag and a branch.
> 
> But this new mode does not fit the pattern at all.
> 
> If we were to attach this to an existing command, I think it has more to
> do with "diff" than "branch". But I'm not sure we want to overload
> "diff" either (which has traditionally been about two endpoints, and
> does not really traverse at all, though arguably "foo...bar" is a bit of
> a cheat :) ).
> 
> > > Of the suggestions thus far, Junio's git-topic-diff seems the least
> > > worse, and doesn't suffer from tab-completion problems.
> >
> > Except that this is too limited a view.
> 
> Right, I agree with you. Topic branches are the intended use, but that's
> not what it _does_, and obviously it can be applied in other cases. So
> since "branch" is too specific, I think "topic branch" is even more so.
> 
> It's really "diff-history" or something, I think. That's not very
> catchy, but I think the best name would imply that it was diffing a set
> of commits (so even "diff-commit" would not be right, because that again
> sounds like endpoints).

This is exactly what I feel as well, thanks for concise and 
to-the-point spelling out.

From user interface perspective, I would expect something like this 
to be possible (and natural):

(1) git diff topic-v1...topic-v2
(2) git diff --branch topic-v1...topic-v2

(1) is what we are all familiar with, providing a diff between two 
revisions with focus on file changes, where (2) shifts focus to 
history changes.

It`s all still a comparison between two revisions (pointed to by 
"topic-v1" and "topic-v2" branch heads in this specific example), but 
it differs in what we are comparing - (1) set of files contained in 
endpoints, or (2) set of revisions contained in (or "leading to") 
endpoints.

Hmm... what about `git diff --history`? :/ It does seem more "true" 
to what it does, though I still like `git diff --branch` more 
(catchier, indeed).

Regards, Buga

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-07  1:34               ` Johannes Schindelin
@ 2018-05-07 22:05                 ` Igor Djordjevic
  2018-05-07 22:24                   ` Stefan Beller
  0 siblings, 1 reply; 387+ messages in thread
From: Igor Djordjevic @ 2018-05-07 22:05 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Jeff King, git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Dscho,

On 07/05/2018 03:34, Johannes Schindelin wrote:
> 
> > > I think Todd's idea to shift it from a full-blown builtin to a cmdmode
> > > of `branch` makes tons of sense.
> >
> > I don`t know, I still find it a bit strange that in order to "diff
> > something", you go to "something" and tell it to "diff itself" - not
> > because it`s a weird concept (OOP, anyone? :]), but because we already
> > have "diff" command that can accept different things, thus just teaching
> > it to accept additional "something" (branch, in this case), seems more
> > natural (to me) - "branch diff" being just another "diff" mode of
> > operation.
> 
> You also have to call `git branch` to list branches. And to rename
> branches. And to delete them. So why not also compare them at the same
> time?

Maybe because we already have a command that specifically does 
comparison? :)

List, rename, delete -- all these seem more as basic CRUD operations, 
where comparison is a more complex one. And not to get me wrong - I 
could see "branch diff" being part of "branch", but not really when 
"diff" already exists as a separate thing, already doing quite some 
(but still diff related, and configurable) stuff.

> > What about that side thought you left out from my original message,
> > making it `git diff --branch` instead?
> 
> I really did not like this, as all of the `git diff` options really are
> about comparing two revisions, not two *sets* of revisions.

I see what you mean, but I would argue this being a deliberate user 
choice here, like picking a diff "strategy" - I`d say it still utterly 
does compare two revisions (branch tips, in this case), just putting 
focus on comparing revisions that lead to them (branch history), 
instead of just files found in them (branch files).

> Further, if I put my unsuspecting user hat on, I would ask myself how you
> can compare branches with one another? That is what I would expect `git
> diff --branch` to do, not to compare two versions of *the same* branch.

I totally agree with you here, and thus I have a question - what 
determines "two versions of *the same* branch"? :) Do you still 
explicitly provide both "old" and "new" version branch tips?

I see "multiple versions of the same branch" more as a conceptual 
model, and not something Git is aware of (I think?) - BUT, even if it 
was, I don`t see why this should be a(n artificial) restriction?

Basically, what you (conceptually) call "two versions of the same 
branch", I simply call "two branches" (from usage standpoint).

And you may have a branch that got split, or more of them that got 
unified, so defining "previous branch version" may not be that 
straightforward - it`s really just "two commit ranges" (as man page 
defines it in general), with "two versions of a patch series" only 
being the most common/expected use case of the former.

Finally, if user picks two totally unrelated "branches" to compare, 
he won`t get a really useful diff - but it`s the same as if he would 
compare two totally unrelated commits (where tree state massively 
changed in between, or having unrelated histories, even).

Besides, while I might still not be much into the matter, but isn`t 
"branch" in Git just a pointer to revision? Being so, there is really 
no such thing as "branch" in terms of being a specific (sub)set of 
revisions (commits), other then "everything from branch head/pointer 
to root commit" (in general).

Yes, we do perceive "a branch" being a specific set of topic related 
commits, but which *exact* commits we are interested in ("branch" lower 
bounds) may differ in regards to what we aim for - how far do we consider 
one branch to reach in the past depends solely on the use case.

> So `git diff --branch` does not at all convey the same to me as `git
> branch --diff`, and I find that the latter does match better what this
> patch series tries to achieve.

I agree with the first part, but it seems to me your finding is 
biased due to your (expected) use case.

> > But if "branch diff" is considered to be too special-cased mode of
> > "diff" so that supporting it from `diff` itself would make it feel
> > awkward in both usage and maintenance (in terms of many other regular
> > `diff` specific options being unsupported), I guess I would understand
> > having it outside `diff` altogether (and implemented as proposed `git
> > branch --diff`, or something)... for the time being, at least :)
> 
> The branch diff is not even a special-cased mode of diff. It is *way* more
> complicated than that. It tries to find 1:1 correspondences between *sets*
> of commits, and then only outputs a "sort" of a diff between the commits
> that correspond with each other. I say "sort" of a diff because that diff
> does not look like `git diff <commit1> <commit2>` at all!

But there is not only one `git diff <commit1> <commit2>` looks, it 
depends on other options (like --name-status, for example), which is 
my point exactly :)

With something like `git diff --branch <commit1>...<commit2>` you 
would get yet another "diff look", useful for use case in question 
here.

Regards, Buga

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-07 22:05                 ` Igor Djordjevic
@ 2018-05-07 22:24                   ` Stefan Beller
  2018-05-07 23:39                     ` Igor Djordjevic
  2018-05-08  3:44                     ` Jeff King
  0 siblings, 2 replies; 387+ messages in thread
From: Stefan Beller @ 2018-05-07 22:24 UTC (permalink / raw)
  To: Igor Djordjevic
  Cc: Johannes Schindelin, Jeff King, git, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Jacob Keller, Eric Sunshine

On Mon, May 7, 2018 at 3:05 PM, Igor Djordjevic
<igor.d.djordjevic@gmail.com> wrote:

> List, rename, delete -- all these seem more as basic CRUD operations,
> where comparison is a more complex one. And not to get me wrong - I
> could see "branch diff" being part of "branch", but not really when
> "diff" already exists as a separate thing, already doing quite some
> (but still diff related, and configurable) stuff.

If we go with "branch --diff", because it has the CRUD operations already
there for branches, I might ask for "remote --diff" to diff two remotes. ;)
(That command "remote --diff" would not make any sense, would it?)

> Basically, what you (conceptually) call "two versions of the same
> branch", I simply call "two branches" (from usage standpoint).

If I diff 2 (topic) branches, which are based on a different version
from upstream, then I see changes from commits that I don't care
about, but this tool explicitly excludes them. Instead it includes
the ordering of the commits as well as its commit messages to
the diff.

So I would not say this tool "diffs two branches", as that is understood
as "diffing the trees, where each of the two branches points two",
whereas this tool diffs a patch series, or if you give Git-ranges,
then it would produce such a patch series in memory.


> And you may have a branch that got split, or more of them that got
> unified, so defining "previous branch version" may not be that
> straightforward - it`s really just "two commit ranges" (as man page
> defines it in general), with "two versions of a patch series" only
> being the most common/expected use case of the former.
>
> Finally, if user picks two totally unrelated "branches" to compare,
> he won`t get a really useful diff - but it`s the same as if he would
> compare two totally unrelated commits (where tree state massively
> changed in between, or having unrelated histories, even).

I used just that, but narrowed down the comparison to one file
instead of the whole tree.

> With something like `git diff --branch <commit1>...<commit2>` you
> would get yet another "diff look", useful for use case in question
> here.

Personally I think this patch series should neither extend git-diff
nor git-branch.

It should not extend git-diff, because currently git-diff can diff
tree-ishs (and does that very well) and comparing to
worktree/index.

It should also not extend git-branch, as that command is for
CRUD operations that you hinted at earlier (Earlier I proposed
git-remote --diff for diffing two remote, which makes no sense,
another one might be git-worktree, which also just does CRUD
for worktrees. It would be a bad idea to have "git worktree --diff")

Hence I propose "git range-diff", similar to topic-diff, that
was proposed earlier.

* it "diffs ranges" of commits.
* it can also deal with out-of-git things like patch series,
  but that is a mere by product and may not be desired.
  Just like git-diff can also compare two files outside a git
  repo, that would not be a good use case.
  Keep the name Git-centric!
* it autocompletes well.

Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-07 22:24                   ` Stefan Beller
@ 2018-05-07 23:39                     ` Igor Djordjevic
  2018-05-08  3:44                     ` Jeff King
  1 sibling, 0 replies; 387+ messages in thread
From: Igor Djordjevic @ 2018-05-07 23:39 UTC (permalink / raw)
  To: Stefan Beller
  Cc: Johannes Schindelin, Jeff King, git, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Jacob Keller, Eric Sunshine

Hi Stefan,

On 08/05/2018 00:24, Stefan Beller wrote:
> 
> > List, rename, delete -- all these seem more as basic CRUD operations,
> > where comparison is a more complex one. And not to get me wrong - I
> > could see "branch diff" being part of "branch", but not really when
> > "diff" already exists as a separate thing, already doing quite some
> > (but still diff related, and configurable) stuff.
> 
> If we go with "branch --diff", because it has the CRUD operations already
> there for branches, I might ask for "remote --diff" to diff two remotes. ;)
> (That command "remote --diff" would not make any sense, would it?)

I`m not sure if this is a reply to me or in general, and whether you 
support what I sad, or argue against it...? Because what you`re 
saying was (or at least should have been) my exact point there :)

> > Basically, what you (conceptually) call "two versions of the same
> > branch", I simply call "two branches" (from usage standpoint).
> 
> If I diff 2 (topic) branches, which are based on a different version
> from upstream, then I see changes from commits that I don't care
> about, but this tool explicitly excludes them. Instead it includes
> the ordering of the commits as well as its commit messages to
> the diff.

Here, I was merely pointing out that you still need to provide two 
branch heads - which might be expected to resemble "two versions of 
the same topic", but they are still (just) "two branches" in Git world.

> > And you may have a branch that got split, or more of them that got
> > unified, so defining "previous branch version" may not be that
> > straightforward - it`s really just "two commit ranges" (as man page
> > defines it in general), with "two versions of a patch series" only
> > being the most common/expected use case of the former.
> >
> > Finally, if user picks two totally unrelated "branches" to compare,
> > he won`t get a really useful diff - but it`s the same as if he would
> > compare two totally unrelated commits (where tree state massively
> > changed in between, or having unrelated histories, even).
> 
> I used just that, but narrowed down the comparison to one file
> instead of the whole tree.

Again, not sure if this should support the argument, or argue against 
it? :) My point was that there might be other use cases (as you seem 
to have supported now), and as "diff" is pretty forgiving, might be 
"diff branch" should be as well.

> > With something like `git diff --branch <commit1>...<commit2>` you
> > would get yet another "diff look", useful for use case in question
> > here.
> 
> Personally I think this patch series should neither extend git-diff
> nor git-branch.
> 
> It should not extend git-diff, because currently git-diff can diff
> tree-ishs (and does that very well) and comparing to
> worktree/index.

Hmm, are you saying that `git diff` actually has a too generic name 
for its (more specific) purpose?

> It should also not extend git-branch, as that command is for
> CRUD operations that you hinted at earlier (Earlier I proposed
> git-remote --diff for diffing two remote, which makes no sense,
> another one might be git-worktree, which also just does CRUD
> for worktrees. It would be a bad idea to have "git worktree --diff")

Agreed here.

> Hence I propose "git range-diff", similar to topic-diff, that
> was proposed earlier.

I find it strange that we already have both "diff" and "diff-something" 
commands, and yet you still propose "something-diff" naming pattern 
instead (but I guess it`s mainly because of auto-complete concerns).

Please forgive my lack of code base familiarity, but from what I`ve 
seen so far, and at least from end-user perspective, I may rather expect 
`git diff-range` as low level implementation, and possibly exposed 
through `git diff --range` (with a nice single letter abbreviation?).

> * it "diffs ranges" of commits.

Thus "diff-range", as your description says itself :) ("range-diff" 
might sound like it "ranges diffs"...?)

> * it can also deal with out-of-git things like patch series,
>   but that is a mere by product and may not be desired.
>   Just like git-diff can also compare two files outside a git
>   repo, that would not be a good use case.

Hmm, so still follows `git diff` in general... `git diff --range`? :D

> * it autocompletes well.

Only here I`m not sure if something like `git diff --range` (with 
accompanying single letter option) would be considered "auto-complete 
friendly", or not?

Regards, Buga

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-07  7:48                 ` Jeff King
  2018-05-07 21:33                   ` Igor Djordjevic
@ 2018-05-08  0:30                   ` Junio C Hamano
  1 sibling, 0 replies; 387+ messages in thread
From: Junio C Hamano @ 2018-05-08  0:30 UTC (permalink / raw)
  To: Jeff King
  Cc: Johannes Schindelin, Eric Sunshine, Git List, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller

Jeff King <peff@peff.net> writes:

> One of the things I don't like about "git branch --diff" is that this
> feature is not _just_ about branches at all.

I actually wouldn't be that much against the word "branch" in
"branch-diff" on the ground that we are typically not feeding
branches to the command (we are feeding two ranges, and one endpoint
of each range typically gets expressed using branch name), as we
have a precedent in "show-branch", for example, that often takes
branches but does not have to.

> It's really "diff-history" or something, I think. That's not very
> catchy, but I think the best name would imply that it was diffing a set
> of commits (so even "diff-commit" would not be right, because that again
> sounds like endpoints).

Sure.  This should't be a submode "--diff" of "git branch" just like
it shouldn't be a submode of "git commit" only because it is about
comparing two sets of commits.  "diff" is about comparing two
endpoints, and not about comparing two sets.  "log" is the closest
thing, if we really want to coerce it into an existing set of
commands, as it is about a set of commits, but it does not do
multiple sets, let alone comparing them.

"branch-diff" was just a good as "diff-history", except that both of
them may irritate command line completion users.  I do not think I
care too much about which exact command name it gets, but I think it
is a bad idea to tacked it to an existing command as a submode that
does unrelated thing to what the main command does.  So from that
point of view, "branch-diff" and "diff-history" are equally good
being a distinct command, and equally bad sharing prefix with common
existing command.


^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 12/18] branch-diff: use color for the commit pairs
  2018-05-07  1:52       ` Johannes Schindelin
@ 2018-05-08  2:10         ` Todd Zullinger
  2018-06-01  8:17           ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Todd Zullinger @ 2018-05-08  2:10 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Johannes,

Johannes Schindelin wrote:
> Hi Todd,
> 
> On Sat, 5 May 2018, Todd Zullinger wrote:
> 
>>> @@ -430,6 +451,8 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
>>>  	struct string_list branch1 = STRING_LIST_INIT_DUP;
>>>  	struct string_list branch2 = STRING_LIST_INIT_DUP;
>>>  
>>> +	git_diff_basic_config("diff.color.frag", "magenta", NULL);
>>> +
>>>  	diff_setup(&diffopt);
>>>  	diffopt.output_format = DIFF_FORMAT_PATCH;
>>>  	diffopt.flags.suppress_diff_headers = 1;
>> 
>> Should this also (or only) check color.diff.frag?
> 
> This code is not querying diff.color.frag, it is setting it. Without
> any way to override it.
> 
> Having thought about it longer, and triggered by Peff's suggestion to
> decouple the "reverse" part from the actual color, I fixed this by
> 
> - *not* setting .frag to magenta,
> 
> - using the reverse method also to mark outer *hunk headers* (not only the
>   outer -/+ markers).
> 
> - actually calling git_diff_ui_config()...

Excellent.  That seems to work nicely now, respecting the
color.diff.<slot> config.

> The current work in progress can be pulled as `branch-diff` from
> https://github.com/dscho/git, if I could ask you to test?

While the colors and 'branch --diff' usage seem to work
nicely, I found that with 4ac3413cc8 ("branch-diff: left-pad
patch numbers", 2018-05-05), 'git branch' itself is broken.

Running 'git branch' creates a branch named 'branch'.
Calling 'git branch --list' shows only 'branch' as the only
branch.

I didn't look too closely, but I'm guessing that the argv
handling is leaving the 'branch' argument in place where it
should be stripped?

This unsurprisingly breaks a large number of tests. :)

Thanks,

-- 
Todd
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A common mistake people make when trying to design something
completely foolproof is to underestimate the ingenuity of complete
fools.
    -- Douglas Adams


^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-07 22:24                   ` Stefan Beller
  2018-05-07 23:39                     ` Igor Djordjevic
@ 2018-05-08  3:44                     ` Jeff King
  2018-05-08  3:48                       ` Jeff King
  2018-05-22 11:38                       ` Ævar Arnfjörð Bjarmason
  1 sibling, 2 replies; 387+ messages in thread
From: Jeff King @ 2018-05-08  3:44 UTC (permalink / raw)
  To: Stefan Beller
  Cc: Igor Djordjevic, Johannes Schindelin, git, Junio C Hamano,
	Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Jacob Keller, Eric Sunshine

On Mon, May 07, 2018 at 03:24:59PM -0700, Stefan Beller wrote:

> Hence I propose "git range-diff", similar to topic-diff, that
> was proposed earlier.
> 
> * it "diffs ranges" of commits.
> * it can also deal with out-of-git things like patch series,
>   but that is a mere by product and may not be desired.
>   Just like git-diff can also compare two files outside a git
>   repo, that would not be a good use case.
>   Keep the name Git-centric!
> * it autocompletes well.

FWIW, I like this by far of all of the suggested names.

-Peff

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-08  3:44                     ` Jeff King
@ 2018-05-08  3:48                       ` Jeff King
  2018-05-22 11:38                       ` Ævar Arnfjörð Bjarmason
  1 sibling, 0 replies; 387+ messages in thread
From: Jeff King @ 2018-05-08  3:48 UTC (permalink / raw)
  To: Stefan Beller
  Cc: Igor Djordjevic, Johannes Schindelin, git, Junio C Hamano,
	Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Jacob Keller, Eric Sunshine

On Mon, May 07, 2018 at 11:44:29PM -0400, Jeff King wrote:

> On Mon, May 07, 2018 at 03:24:59PM -0700, Stefan Beller wrote:
> 
> > Hence I propose "git range-diff", similar to topic-diff, that
> > was proposed earlier.
> > 
> > * it "diffs ranges" of commits.
> > * it can also deal with out-of-git things like patch series,
> >   but that is a mere by product and may not be desired.
> >   Just like git-diff can also compare two files outside a git
> >   repo, that would not be a good use case.
> >   Keep the name Git-centric!
> > * it autocompletes well.
> 
> FWIW, I like this by far of all of the suggested names.

I hit "send" before I had a chance to expound. ;)

The thing that I really like about it is that it names the _concept_.
If I were writing a manual page describing what this output is, I would
call it a "range diff". And naturally, the command to generate range
diffs is "git range-diff".

I think "git diff --range" would also be OK, but IMHO it's useful to
keep the "git diff" family as always comparing end-points.

-Peff

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-05 19:41             ` Johannes Schindelin
@ 2018-05-09 16:24               ` Ramsay Jones
  2018-06-01  8:23                 ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Ramsay Jones @ 2018-05-09 16:24 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason



On 05/05/18 20:41, Johannes Schindelin wrote:
[snip]

[Sorry for the late reply - still catching up after (long weekend)
UK public holiday]

> Well, what I would want to do is let the cloud work for me. By adding an
> automated build to my Visual Studio Team Services (VSTS) account, of
> course, as I have "cloud privilege" (i.e. I work in the organization
> providing the service, so I get to play with all of it for free).
> 
> So I really don't want to build sparse every time a new revision needs to
> be tested (whether that be from one of my branches, an internal PR for
> pre-review of patches to be sent to the mailing list, or maybe even `pu`
> or the personalized branches on https://github.com/gitster/git).
> 
> I really would need a ready-to-install sparse, preferably as light-weight
> as possible (by not requiring any dependencies outside what is available
> in VSTS' hosted Linux build agents.
> 
> Maybe there is a specific apt source for sparse?

Not that I'm aware of, sorry! :(

[release _source_ tar-balls are available, but that's not
what you are after, right?]

I don't know what is involved in setting up a 'ppa repo' for
Ubuntu, which I suspect is the kind of thing you want, but it
would have helped me several times in the past (so that I could
have something to point people to) ... ;-)

ATB,
Ramsay Jones


^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 01/18] Add a function to solve least-cost assignment problems
  2018-05-03 15:30 ` [PATCH 01/18] Add a function to solve least-cost assignment problems Johannes Schindelin
@ 2018-05-13 18:19   ` Duy Nguyen
  2018-05-21  9:52     ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Duy Nguyen @ 2018-05-13 18:19 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git Mailing List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

On Thu, May 3, 2018 at 5:30 PM, Johannes Schindelin
<johannes.schindelin@gmx.de> wrote:
> +       /* reduction transfer */
> +       free_row = xmalloc(sizeof(int) * row_count);
> +       for (int i = 0; i < row_count; i++) {

travis complains about this


hungarian.c: In function ‘compute_assignment’:
hungarian.c:47:11: error: redeclaration of ‘i’ with no linkage
  for (int i = 0; i < row_count; i++) {
           ^
hungarian.c:21:6: note: previous declaration of ‘i’ was here
  int i, j, phase;
      ^
hungarian.c:47:2: error: ‘for’ loop initial declarations are only
allowed in C99 mode
  for (int i = 0; i < row_count; i++) {
  ^
hungarian.c:47:2: note: use option -std=c99 or -std=gnu99 to compile your code
-- 
Duy

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
                   ` (20 preceding siblings ...)
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
@ 2018-05-21  4:48 ` Junio C Hamano
  2018-05-21  9:51   ` Johannes Schindelin
  21 siblings, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-05-21  4:48 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

I've been using both branch-diff and tbdiff while comparing rerolled
topics before accepting them.  One obvious difference between the
two is that the speed to compute pairing is quite different but that
is expected ;-)

Another difference that is somewhat irritating is that a SP that
leads a context line in a diff hunk that is introduced in the newer
iteration is often painted in reverse, and I do not know what aspect
of that single SP on these lines the branch-diff wants to pull my
attention to.

    https://pasteboard.co/Hm9ujI7F.png

In the picture, the three pre-context lines that are all indented by
a HT are prefixed by a SP, and that is prefixed by a '+' sign of the
outer diff.

We can use --dual-color mode to unsee these but it is somewhat
frustrating not to know what the branch-diff program wants to tell
me by highlighting the single SPs on these lines.

Thanks.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-21  4:48 ` [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Junio C Hamano
@ 2018-05-21  9:51   ` Johannes Schindelin
  2018-05-22  1:42     ` Junio C Hamano
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-21  9:51 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Junio,

On Mon, 21 May 2018, Junio C Hamano wrote:

> I've been using both branch-diff and tbdiff while comparing rerolled
> topics before accepting them.  One obvious difference between the two is
> that the speed to compute pairing is quite different but that is
> expected ;-)

Yep.

It is also expected that the branch --diff actually works without you
having to make sure that the Python modules numpy and hungarian are built
correctly (which you Linux folks couldn't care less about because it is
done for ya).

I spent such a lot on time trying to get it to work on Windows that it
probably only now reaches parity with the time I spent on implementing
branch --diff.

> Another difference that is somewhat irritating is that a SP that
> leads a context line in a diff hunk that is introduced in the newer
> iteration is often painted in reverse, and I do not know what aspect
> of that single SP on these lines the branch-diff wants to pull my
> attention to.

Right. This is due to the fact that the diff does not realize that it
looks at pre/post images being diffs. And hence it does not understand
that the single space indicates a context line and is therefore not a
whitespace error before the tab.

>     https://pasteboard.co/Hm9ujI7F.png
> 
> In the picture, the three pre-context lines that are all indented by
> a HT are prefixed by a SP, and that is prefixed by a '+' sign of the
> outer diff.

Yep, that's exactly it.

The way tbdiff did it was to parse the diff and re-roll the coloring on
its own. I am not really keen on doing that in `branch --diff`, too.

> We can use --dual-color mode to unsee these but it is somewhat
> frustrating not to know what the branch-diff program wants to tell
> me by highlighting the single SPs on these lines.

I was wondering from the get-go whether it would make sense to make
--dual-color the default.

But now I wonder whether there is actually *any* use in `--color` without
`--dual-color`.

What do you think? Should I make the dual color mode the *only* color
mode?

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 01/18] Add a function to solve least-cost assignment problems
  2018-05-13 18:19   ` Duy Nguyen
@ 2018-05-21  9:52     ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-21  9:52 UTC (permalink / raw)
  To: Duy Nguyen
  Cc: Git Mailing List, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

[-- Attachment #1: Type: text/plain, Size: 983 bytes --]

Hi Duy,

On Sun, 13 May 2018, Duy Nguyen wrote:

> On Thu, May 3, 2018 at 5:30 PM, Johannes Schindelin
> <johannes.schindelin@gmx.de> wrote:
> > +       /* reduction transfer */
> > +       free_row = xmalloc(sizeof(int) * row_count);
> > +       for (int i = 0; i < row_count; i++) {
> 
> travis complains about this
> 
> 
> hungarian.c: In function ‘compute_assignment’:
> hungarian.c:47:11: error: redeclaration of ‘i’ with no linkage
>   for (int i = 0; i < row_count; i++) {
>            ^
> hungarian.c:21:6: note: previous declaration of ‘i’ was here
>   int i, j, phase;
>       ^
> hungarian.c:47:2: error: ‘for’ loop initial declarations are only
> allowed in C99 mode
>   for (int i = 0; i < row_count; i++) {
>   ^
> hungarian.c:47:2: note: use option -std=c99 or -std=gnu99 to compile your code

Yep, I fixed it locally already before seeing your mail. It is good to
know that some people pay attention, though!

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-07 21:33                   ` Igor Djordjevic
@ 2018-05-21 10:33                     ` Johannes Schindelin
  2018-05-21 17:56                       ` Stefan Beller
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-21 10:33 UTC (permalink / raw)
  To: Igor Djordjevic
  Cc: Jeff King, Eric Sunshine, Junio C Hamano, Git List, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller

Hi Buga,

On Mon, 7 May 2018, Igor Djordjevic wrote:

> On 07/05/2018 09:48, Jeff King wrote:
> > 
> > > > Let's, please, not fall into the trap of polluting git-branch with
> > > > utterly unrelated functionality, as has happened a few times with
> > > > other Git commands. Let's especially not do so merely for the sake of
> > > > tab-completion. git-branch is for branch management; it's not for
> > > > diff'ing.
> > >
> > > I totally disagree. `git branch` is *the* command to work with branches.
> > > Yes, you can manage branches. But you can also list them. And now you can
> > > also compare them.
> > 
> > One of the things I don't like about "git branch --diff" is that this
> > feature is not _just_ about branches at all. E.g., I could do:
> > 
> >   git tbdiff HEAD~10 HEAD~5 foo
> > 
> > Or even:
> > 
> >   git tbdiff v2.16.0 v2.17.0 my-rewritten-v2.17.0
> > 
> > Those arguments really are just commitishes, not necessarily branches.
> > One of the current interface rules for "git branch" is that the branch
> > names we hand it are interpreted _exactly_ as branch names. You cannot
> > "git branch -m v2.16.0", and there is no ambiguity in "git branch -d
> > foo" if "foo" is both a tag and a branch.
> > 
> > But this new mode does not fit the pattern at all.
> > 
> > If we were to attach this to an existing command, I think it has more to
> > do with "diff" than "branch". But I'm not sure we want to overload
> > "diff" either (which has traditionally been about two endpoints, and
> > does not really traverse at all, though arguably "foo...bar" is a bit of
> > a cheat :) ).
> > 
> > > > Of the suggestions thus far, Junio's git-topic-diff seems the least
> > > > worse, and doesn't suffer from tab-completion problems.
> > >
> > > Except that this is too limited a view.
> > 
> > Right, I agree with you. Topic branches are the intended use, but that's
> > not what it _does_, and obviously it can be applied in other cases. So
> > since "branch" is too specific, I think "topic branch" is even more so.
> > 
> > It's really "diff-history" or something, I think. That's not very
> > catchy, but I think the best name would imply that it was diffing a set
> > of commits (so even "diff-commit" would not be right, because that again
> > sounds like endpoints).
> 
> This is exactly what I feel as well, thanks for concise and 
> to-the-point spelling out.
> 
> From user interface perspective, I would expect something like this 
> to be possible (and natural):
> 
> (1) git diff topic-v1...topic-v2

No, we cannot. The `git diff topic-v1...topic-v2` invocation has worked
for a looooong time, and does something very different.

We should not even allow ourselves to think of such a breakage.

> (2) git diff --branch topic-v1...topic-v2

From my point of view, `git diff --branch` indicates that I diff
*branches*. Which is not really something that makes sense, and definitely
not what this command is about.

We are not comparing branches.

We are comparing versions of the same branch.

> (1) is what we are all familiar with, providing a diff between two 
> revisions with focus on file changes, where (2) shifts focus to 
> history changes.
> 
> It`s all still a comparison between two revisions (pointed to by 
> "topic-v1" and "topic-v2" branch heads in this specific example), but 
> it differs in what we are comparing - (1) set of files contained in 
> endpoints, or (2) set of revisions contained in (or "leading to") 
> endpoints.

It is very much not about comparing *two* revisions. It is very much about
comparing two *ranges of* revisions, and not just any ranges, no. Those
ranges need to be so related as to contain mostly identical changes.
Otherwise, `git branch --diff` will spend a ton of time, just to come back
with a series of `-` lines followed by a series of `+` lines
(figuratively, not literally). Which would be stupid, to spend that much
time on something that `git rev-list --left-right topic1...topic2` would
have computed a lot faster.

> Hmm... what about `git diff --history`? :/ It does seem more "true" 
> to what it does, though I still like `git diff --branch` more 
> (catchier, indeed).

It certainly is catchier. But also a ton more puzzling.

I do not want to compare histories, after all. That would be like saying:
okay, topic1 and topic2 ended up at the same stage, but *how* did they
get there?

What I *want* to ask via the command implemented by this patch series is
the question: there was a set of patches previously, and now I have a set
of revised patches, what changed?

Most fellow German software engineers (who seem to have a knack for
idiotically long variable/function names) would now probably suggest:

	git compare-patch-series-with-revised-patch-series

I hope you agree that that is better *and* worse than your suggestions,
depending from what angle you look at it: it is better because it
describes what the command is *actually* doing. But it is much worse at
the same time because it is too long.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-07 15:12                 ` Junio C Hamano
@ 2018-05-21 10:41                   ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-21 10:41 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Jeff King, git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Junio,

On Tue, 8 May 2018, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > It would be easy to introduce, but I am wary about its usefulness.
> > Unless you re-generate the branch from patches (which I guess you do a
> > lot, but I don't), you are likely to compare incomplete patch series: say,
> > when you call `git rebase -i` to reword 05/18's commit message, your
> > command will only compare 05--18 of the patch series.
> 
> Well that is exactly the point of that "..@{1} @{1}..", which turned
> out to be very useful in practice at least for me when I am updating
> a topic with "rebase -i", and then reviewing what I did with tbdiff.
> 
> I do not want 01-04 in the above case as I already know I did not
> touch them.

And you are a seasoned veteran maintainer.

To the occasional contributor, this information is not obvious, and it is
not stored in their brain. It needs to be made explicit, which is why this
here command outputs those `abcdef = 012345` lines: it lists all the
commits, stating which ones are unchanged. In your 01-04 example, those
lines would be of the form `abcdef = abcdef`, of course.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-21 10:33                     ` Johannes Schindelin
@ 2018-05-21 17:56                       ` Stefan Beller
  2018-05-21 20:24                         ` Jeff King
  0 siblings, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-05-21 17:56 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Igor Djordjevic, Jeff King, Eric Sunshine, Junio C Hamano,
	Git List, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Jacob Keller

Hi Johannes,

>> (2) git diff --branch topic-v1...topic-v2
>
> From my point of view, `git diff --branch` indicates that I diff
> *branches*. Which is not really something that makes sense, and definitely
> not what this command is about.
>
> We are not comparing branches.
>
> We are comparing versions of the same branch.

I happen to have a messier workflow than you have, as I
develop a "resend" of a topic in a new branch (or I have to
restore the old sent topic from the reflog).

Now that I have the tool I also compare two branches,
namely, the branch that Junio queued
(origin/base..origin/sb/intelligent-name) vs the resend
that I had locally (origin/base..foo).

Next time I might compare Junios queued topic to the
local format-patch'es that I already annotated.

So in a way this diffs different versions of a topic, "diff-topic-versions".

>> It`s all still a comparison between two revisions (pointed to by
>> "topic-v1" and "topic-v2" branch heads in this specific example), but
>> it differs in what we are comparing - (1) set of files contained in
>> endpoints, or (2) set of revisions contained in (or "leading to")
>> endpoints.
>
> It is very much not about comparing *two* revisions.

I wonder if we can make the tool more intelligent to take two revisions
and it figures out the range by finding the base branch itself.
Probably as a follow up.


> It is very much about
> comparing two *ranges of* revisions, and not just any ranges, no. Those
> ranges need to be so related as to contain mostly identical changes.

range-diff, eh?

> Most fellow German software engineers (who seem to have a knack for
> idiotically long variable/function names) would now probably suggest:
>
>         git compare-patch-series-with-revised-patch-series

or short:

  revision-compare
  compare-revs
  com-revs

  revised-diff
  revise-diff
  revised-compare

  diff-revise

> I hope you agree that that is better *and* worse than your suggestions,
> depending from what angle you look at it: it is better because it
> describes what the command is *actually* doing. But it is much worse at
> the same time because it is too long.

btw, you think very much in terms of *patch series*, but there are workflows
without patches (pull requests at Github et Al., changes in Gerrit),
and I would think the output of the tool under discussion would still be
useful.

In [1] Junio gives his use case, it is "before accepting them", which could
be comparing an mbox or patch files against a branch, or first building
up a local history on a detached head (and then wondering if to reset
the branch to the new history), which would be all in Git.

That use case still has 'patches' involved, but these are not the main
selling point for the tool, as you could turn patches into commits before
using this tool.

[1] https://public-inbox.org/git/xmqqvabh1ung.fsf@gitster-ct.c.googlers.com/

Thanks,
Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-21 17:56                       ` Stefan Beller
@ 2018-05-21 20:24                         ` Jeff King
  2018-05-21 21:40                           ` Brandon Williams
  0 siblings, 1 reply; 387+ messages in thread
From: Jeff King @ 2018-05-21 20:24 UTC (permalink / raw)
  To: Stefan Beller
  Cc: Johannes Schindelin, Igor Djordjevic, Eric Sunshine,
	Junio C Hamano, Git List, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Jacob Keller

On Mon, May 21, 2018 at 10:56:47AM -0700, Stefan Beller wrote:

> > It is very much about
> > comparing two *ranges of* revisions, and not just any ranges, no. Those
> > ranges need to be so related as to contain mostly identical changes.
> 
> range-diff, eh?
> 
> > Most fellow German software engineers (who seem to have a knack for
> > idiotically long variable/function names) would now probably suggest:
> >
> >         git compare-patch-series-with-revised-patch-series
> 
> or short:
> 
>   revision-compare
>   compare-revs
>   com-revs
> 
>   revised-diff
>   revise-diff
>   revised-compare
> 
>   diff-revise

I still like "range diff", but I think something around "revise" is a
good line of thought, too. Because it implies that we expect the two
ranges to be composed of almost-the-same commits.

That leads to another use case where I think focusing on topic branches
(or even branches at all) would be a misnomer. Imagine I cherry-pick a
bunch of commits with:

  git cherry-pick -10 $old_commit

I might then want to see how the result differs with something like:

  git range-diff $old_commit~10..$old_commit HEAD~10..HEAD

I wouldn't think of this as a topic-branch operation, but just as
comparing two sequences of commits. I guess "revise" isn't strictly
accurate here either, as I'm not revising. But I do assume the two
ranges share some kind of mapping of patches.

-Peff

PS I wish there were a nicer syntax to do that. Perhaps
   "git range-diff -10 $old_commit HEAD" could work, though occasionally
   the two ranges are not the same length (e.g., if you ended up
   skipping one of the cherry-picked commits). Anyway, those kind of
   niceties can easily come later on top. :)

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-21 20:24                         ` Jeff King
@ 2018-05-21 21:40                           ` Brandon Williams
  2018-05-21 21:48                             ` Stefan Beller
  2018-05-21 21:52                             ` Jeff King
  0 siblings, 2 replies; 387+ messages in thread
From: Brandon Williams @ 2018-05-21 21:40 UTC (permalink / raw)
  To: Jeff King
  Cc: Stefan Beller, Johannes Schindelin, Igor Djordjevic,
	Eric Sunshine, Junio C Hamano, Git List, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Jacob Keller

On 05/21, Jeff King wrote:
> On Mon, May 21, 2018 at 10:56:47AM -0700, Stefan Beller wrote:
> 
> > > It is very much about
> > > comparing two *ranges of* revisions, and not just any ranges, no. Those
> > > ranges need to be so related as to contain mostly identical changes.
> > 
> > range-diff, eh?
> > 
> > > Most fellow German software engineers (who seem to have a knack for
> > > idiotically long variable/function names) would now probably suggest:
> > >
> > >         git compare-patch-series-with-revised-patch-series
> > 
> > or short:
> > 
> >   revision-compare
> >   compare-revs
> >   com-revs
> > 
> >   revised-diff
> >   revise-diff
> >   revised-compare
> > 
> >   diff-revise

I haven't really been following all of the discussion but from what I
can tell the point of this command is to generate a diff based on two
different versions of a series, so why not call it 'series-diff'? :)

-- 
Brandon Williams

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-21 21:40                           ` Brandon Williams
@ 2018-05-21 21:48                             ` Stefan Beller
  2018-05-21 21:52                             ` Jeff King
  1 sibling, 0 replies; 387+ messages in thread
From: Stefan Beller @ 2018-05-21 21:48 UTC (permalink / raw)
  To: Brandon Williams
  Cc: Jeff King, Johannes Schindelin, Igor Djordjevic, Eric Sunshine,
	Junio C Hamano, Git List, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Jacob Keller

On Mon, May 21, 2018 at 2:40 PM, Brandon Williams <bmwill@google.com> wrote:
revised-compare
>> >
>> >   diff-revise
>
> I haven't really been following all of the discussion but from what I
> can tell the point of this command is to generate a diff based on two
> different versions of a series, so why not call it 'series-diff'? :)

Upon mentioning series-diff, I misheard Brandon and thought he proposed

   serious-diff

:-)
Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-21 21:40                           ` Brandon Williams
  2018-05-21 21:48                             ` Stefan Beller
@ 2018-05-21 21:52                             ` Jeff King
  2018-05-22  2:08                               ` Junio C Hamano
  1 sibling, 1 reply; 387+ messages in thread
From: Jeff King @ 2018-05-21 21:52 UTC (permalink / raw)
  To: Brandon Williams
  Cc: Stefan Beller, Johannes Schindelin, Igor Djordjevic,
	Eric Sunshine, Junio C Hamano, Git List, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Jacob Keller

On Mon, May 21, 2018 at 02:40:57PM -0700, Brandon Williams wrote:

> > > > Most fellow German software engineers (who seem to have a knack for
> > > > idiotically long variable/function names) would now probably suggest:
> > > >
> > > >         git compare-patch-series-with-revised-patch-series
> > > 
> > > or short:
> > > 
> > >   revision-compare
> > >   compare-revs
> > >   com-revs
> > > 
> > >   revised-diff
> > >   revise-diff
> > >   revised-compare
> > > 
> > >   diff-revise
> 
> I haven't really been following all of the discussion but from what I
> can tell the point of this command is to generate a diff based on two
> different versions of a series, so why not call it 'series-diff'? :)

That's OK with me, though I prefer "range" as I think we use that term
elsewhere ("series" is usually part of "patch series", but many people
do not use a workflow with that term).

-Peff

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-21  9:51   ` Johannes Schindelin
@ 2018-05-22  1:42     ` Junio C Hamano
  2018-06-01  8:28       ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-05-22  1:42 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

>> In the picture, the three pre-context lines that are all indented by
>> a HT are prefixed by a SP, and that is prefixed by a '+' sign of the
>> outer diff.
>
> Yep, that's exactly it.
>
> The way tbdiff did it was to parse the diff and re-roll the coloring on
> its own. I am not really keen on doing that in `branch --diff`, too.

Are you saying that these are "whitespace errors" getting painted?
It is somewhat odd because my whitespace errors are configured to be
painted in "reverse blue".  Perhaps you are forcing the internal
diff not to pay attention to the end-user configuration---which
actually does make sense, as reusing of "git diff" to take "diff of
diff" is a mere implementation detail.

In any case, the "whitespace errors" in "diff of diff" are mostly
distracting.

> I was wondering from the get-go whether it would make sense to make
> --dual-color the default.
>
> But now I wonder whether there is actually *any* use in `--color` without
> `--dual-color`.
>
> What do you think? Should I make the dual color mode the *only* color
> mode?

Sorry but you are asking a good question to a wrong person.

I normally do not seek much useful information in colored output, so
my reaction would not be very useful.  Non dual-color mode irritates
me due to the false whitespace errors, and dual-color mode irritates
me because it looks sufficiently different from tbdiff output that I
am used to see.


^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-21 21:52                             ` Jeff King
@ 2018-05-22  2:08                               ` Junio C Hamano
  0 siblings, 0 replies; 387+ messages in thread
From: Junio C Hamano @ 2018-05-22  2:08 UTC (permalink / raw)
  To: Jeff King
  Cc: Brandon Williams, Stefan Beller, Johannes Schindelin,
	Igor Djordjevic, Eric Sunshine, Git List, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Jacob Keller

Jeff King <peff@peff.net> writes:

>> I haven't really been following all of the discussion but from what I
>> can tell the point of this command is to generate a diff based on two
>> different versions of a series, so why not call it 'series-diff'? :)
>
> That's OK with me, though I prefer "range" as I think we use that term
> elsewhere ("series" is usually part of "patch series", but many people
> do not use a workflow with that term).

FWIW, I am OK with either, with a bit of preference to "range" over
"series".  As long as this stays to be an independent command (as
opposed to be made into a new mode to existing command) and the
command name is not overly hard to type, I am OK with anything ;-)

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-08  3:44                     ` Jeff King
  2018-05-08  3:48                       ` Jeff King
@ 2018-05-22 11:38                       ` Ævar Arnfjörð Bjarmason
  2018-05-25 22:06                         ` Stefan Beller
  1 sibling, 1 reply; 387+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-05-22 11:38 UTC (permalink / raw)
  To: Jeff King
  Cc: Stefan Beller, Igor Djordjevic, Johannes Schindelin, git,
	Junio C Hamano, Thomas Rast, Thomas Gummerer, Ramsay Jones,
	Jacob Keller, Eric Sunshine


On Tue, May 08 2018, Jeff King wrote:

> On Mon, May 07, 2018 at 03:24:59PM -0700, Stefan Beller wrote:
>
>> Hence I propose "git range-diff", similar to topic-diff, that
>> was proposed earlier.
>>
>> * it "diffs ranges" of commits.
>> * it can also deal with out-of-git things like patch series,
>>   but that is a mere by product and may not be desired.
>>   Just like git-diff can also compare two files outside a git
>>   repo, that would not be a good use case.
>>   Keep the name Git-centric!
>> * it autocompletes well.
>
> FWIW, I like this by far of all of the suggested names.

I agree, "range-diff" is the best one mentioned so far.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
  2018-05-22 11:38                       ` Ævar Arnfjörð Bjarmason
@ 2018-05-25 22:06                         ` Stefan Beller
       [not found]                           ` <CAA8fPEkNjy+ETz4Mx+C2kUfLjLzR9uuOmO3GfN48ZH1SwyfE1A@mail.gmail.com>
  0 siblings, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-05-25 22:06 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Jeff King, Igor Djordjevic,
	Ævar Arnfjörð Bjarmason, git, Junio C Hamano,
	Thomas Rast, Thomas Gummerer, Ramsay Jones, Jacob Keller,
	Eric Sunshine

Johannes,

On IRC you wrote:
<dscho> And BTW this is not bike-shedding to me. Discussing the name
of a variable, or indentation, or line wrapping, is. But improving the
user experience is important. We *suck* on that, historically, and I
do want to break with that habit.
...
<dscho> avar, _ikke_: so a colleague of mine whose opinion on naming I
respect more than all Git developers combined *also* came up with the
term `range-diff`, independently.
...
<dscho> Yes, you are looking at two ranges. But not *any* two ranges.
*That* is my point.

So I sat back and want to try again;

IIUC your dislike for "range-diff" boils down to:
(A) it doesn't diff any arbitrary range, as the output would become
very cumbersome and hard to understand,
(B) it is not a good intuitive name for users, as they would not think
of range-diff when they'd want to have this feature.

Regarding (A), I think the same can be said about input to the diff
machinery, e.g. 'git diff v2.0.0 v2.17.0' is just very much text, and
it is hardly useful (except as a patch fed to the machine).

Over time there were added tons of options that make the diff output
easier to digest, e.g. additional pathspecs to restrict to a sub tree or
ignoring certain things (white spaces mostly), such that
'git diff -w v2.0.0 v2.17.0 -- refs.h' is easier for a human to grok.

Regarding (B), I agree, but blame it on the nature of an open
source project that provides a toolbox. So the way a user is
going to discover this feature is via stackoverflow or via
asking a coworker or finding the example output somewhere.

I think that last point could be part of the feedback:
git-diff has prominently hints at its name via "diff --git ..."
in the first line of its output, so maybe the output of this feature
also wants to name itself?

Other thoughts:
We could go down the route and trying to find a best possible
technical name, for which I could offer:

  revision-walk-difference
  revwalk-diff

As that literally describes the output: two rev walks are
performed and then those outputs of the rev walks is diffed.
Based off these technicals we could get more creative:

  redo-rev-walk-spot-the-difference
  re-walk-spot
  retravel-spot
  spot-diff

But I think all these do not address the feedback (B).

"What would a user find intuitive?"; I personally thought
a bit about how I discovered cherry-pick. I just took it as
a given name, without much thought, as I discovered it
by tell tale, not looking for it in the docs. It sort of made
sense as a command that I learned earlier about,
"interactive rebase", also has had the "pick" command,
such that "picking" made sense. I think I retroactively
made sense of the "cherry" part. Now I tried to find it
in the mailing list archive and actually learn about its origin,
but no good stories are found there.

For what the user might find most useful, I just looked
at other tools in Gerrits landscape and there the expectation
seems that you upload your code first and do the diff of the different
patches serverside. I think the same holds for Github or other
branch based reviewing systems. You can force push the
branch that is pull requested and the web UI somehow makes
sense of it.

That leads me to the (weak) conclusion of branch-diff or tbdiff
to be useful most for patch based / mailing list based workflows
as there is no magic server helping you out.

Searching for "kernel +tbdiff" to find the kernel devs using tbdiff
gave me no results, so I may be mistaken there.

Trying to find "interdiffs" (for the lack of a better name) between
patches on the kernel mailing list also is not obvious to the uninitiated.

So for the various workflows, I could come up with

  change-diff
  pullrequest-diff
  patch-series-diff

but we do not look at diffs, rather we only use this tool to work on
incremental things, so maybe instead:

  change-history
  pullrequest-history
  patch-series-evolution

Note how these are 3 suggestions, one for each major workflow,
and I'd *REALLY* would want to have a tool that is agnostic to the
workflow on top (whether you use pull requests or Gerrit changes),
but now I would like to step back and remind us that this tool
is only mostly used for viewing the evolution of your new thing,
but it can also be very useful to inspect non-new things.
(backported patches to maint, or some -stable branch)

Or rather: We do not know the major use case yet. Sure
I will use it in my cover letter and that is on my mind now,
but I think there are other use cases that are not explored
yet, so we should rather make the naming decision based
off of technicals rather than anticipated use case and user
discovery methods.

I hope this is actually useful feedback on the naming discovery.

Thanks,
Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Fwd: [PATCH v2 02/18] Add a new builtin: branch-diff
       [not found]                           ` <CAA8fPEkNjy+ETz4Mx+C2kUfLjLzR9uuOmO3GfN48ZH1SwyfE1A@mail.gmail.com>
@ 2018-05-26  6:15                             ` Øyvind Rønningstad
  2018-06-01  8:15                             ` Johannes Schindelin
  1 sibling, 0 replies; 387+ messages in thread
From: Øyvind Rønningstad @ 2018-05-26  6:15 UTC (permalink / raw)
  To: git

Just want to throw my support in for range-diff since ranges is what
you pass to the command.

Alternatively, diff-diff since that's how I've crudely tried to
accomplish this before.

git diff A..B > diff1
git diff C..D > diff2
winmerge diff1 diff2

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 01/18] Add a function to solve least-cost assignment problems
  2018-05-04 15:34   ` [PATCH v2 01/18] Add a function to solve least-cost assignment problems Johannes Schindelin
  2018-05-05 18:24     ` Jeff King
@ 2018-05-30 13:55     ` SZEDER Gábor
  2018-05-30 16:14       ` Stefan Beller
  1 sibling, 1 reply; 387+ messages in thread
From: SZEDER Gábor @ 2018-05-30 13:55 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: SZEDER Gábor, git, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Stefan Beller, Jacob Keller, Eric Sunshine

> The Jonker-Volgenant algorithm was implemented to answer questions such
> as: given two different versions of a topic branch (or iterations of a
> patch series), what is the best pairing of commits/patches between the
> different versions?
> 
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  Makefile    |   1 +
>  hungarian.c | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  hungarian.h |  19 +++++

(Nit: I personally don't really like these filenames, I know they will
surprise and distract me every time I notice them for years to come... :)

> +int compute_assignment(int column_count, int row_count, double *cost,
> +		       int *column2row, int *row2column)
> +{
> +	double *v = xmalloc(sizeof(double) * column_count), *d;
> +	int *free_row, free_count = 0, saved_free_count, *pred, *col;
> +	int i, j, phase;

<snip>

> +	for (free_count = 0; free_count < saved_free_count; free_count++) {
> +		int i1 = free_row[free_count], low = 0, up = 0, last, k;
> +		double min, c, u1;

<snip most of the loop's body>

> +		/* augmentation */
> +		do {
> +			if (j < 0)
> +				BUG("negative j: %d", j);
> +			i = pred[j];
> +			column2row[j] = i;
> +			k = j;
> +			j = row2column[i];
> +			row2column[i] = k;

Coccinelle suggests to replace the last three lines above with:

  SWAP(j, row2column[i]);

I think it's right, using the SWAP macro makes the resulting code not
only shorter and clearer, but it also saves the reader from thinking
about whether it's important to set 'k = j' (I think it's not), or 'k'
is just used here in lieu of a dedicated 'tmp' variable (I think it
is).

> +		} while (i1 != i);
> +	}
> +

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 01/18] Add a function to solve least-cost assignment problems
  2018-05-30 13:55     ` SZEDER Gábor
@ 2018-05-30 16:14       ` Stefan Beller
  2018-05-30 23:28         ` brian m. carlson
  0 siblings, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-05-30 16:14 UTC (permalink / raw)
  To: SZEDER Gábor
  Cc: Johannes Schindelin, git, Junio C Hamano, Thomas Rast,
	Thomas Gummerer, Ævar Arnfjörð Bjarmason,
	Ramsay Jones, Jacob Keller, Eric Sunshine

On Wed, May 30, 2018 at 6:55 AM, SZEDER Gábor <szeder.dev@gmail.com> wrote:
>> The Jonker-Volgenant algorithm was implemented to answer questions such
>> as: given two different versions of a topic branch (or iterations of a
>> patch series), what is the best pairing of commits/patches between the
>> different versions?
>>
>> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
>> ---
>>  Makefile    |   1 +
>>  hungarian.c | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>>  hungarian.h |  19 +++++
>
> (Nit: I personally don't really like these filenames, I know they will
> surprise and distract me every time I notice them for years to come... :)

Good point. I remember my initial reaction to the file names was expecting
some hungarian notation, which totally didn't make sense, so I refrained from
commenting. Searching the web for the algorithm, maybe 'lapjv.c' is adequate?
(short for "Linear Assignment Problem Jonker Volgenant") Matlab has a function
named lapjv solving the same problem, so it would fall in line with the outside
world.

Out of interest, why is it called hungarian in the first place? (I presume that
comes from some background of DScho in image processing or such, so the
the answer will be interesting for sure:)

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 01/18] Add a function to solve least-cost assignment problems
  2018-05-30 16:14       ` Stefan Beller
@ 2018-05-30 23:28         ` brian m. carlson
  2018-05-31 12:19           ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: brian m. carlson @ 2018-05-30 23:28 UTC (permalink / raw)
  To: Stefan Beller
  Cc: SZEDER Gábor, Johannes Schindelin, git, Junio C Hamano,
	Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Jacob Keller, Eric Sunshine

[-- Attachment #1: Type: text/plain, Size: 1142 bytes --]

On Wed, May 30, 2018 at 09:14:06AM -0700, Stefan Beller wrote:
> Good point. I remember my initial reaction to the file names was expecting
> some hungarian notation, which totally didn't make sense, so I refrained from
> commenting. Searching the web for the algorithm, maybe 'lapjv.c' is adequate?
> (short for "Linear Assignment Problem Jonker Volgenant") Matlab has a function
> named lapjv solving the same problem, so it would fall in line with the outside
> world.
> 
> Out of interest, why is it called hungarian in the first place? (I presume that
> comes from some background of DScho in image processing or such, so the
> the answer will be interesting for sure:)

I think it's because tbdiff uses the hungarian Python module, which
implements the Hungarian method, also known as the Kuhn-Munkres
algorithm, for solving the linear assignment problem.  This is the
Jonker-Volgenant algorithm, which solves the same problem.  It's faster,
but less tolerant.

At least this is what I just learned after about ten minutes of
searching.
-- 
brian m. carlson: Houston, Texas, US
OpenPGP: https://keybase.io/bk2204

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 867 bytes --]

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 01/18] Add a function to solve least-cost assignment problems
  2018-05-30 23:28         ` brian m. carlson
@ 2018-05-31 12:19           ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-05-31 12:19 UTC (permalink / raw)
  To: brian m. carlson
  Cc: Stefan Beller, SZEDER Gábor, git, Junio C Hamano,
	Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Jacob Keller, Eric Sunshine

Hi Brian,

On Wed, 30 May 2018, brian m. carlson wrote:

> On Wed, May 30, 2018 at 09:14:06AM -0700, Stefan Beller wrote:
> > Good point. I remember my initial reaction to the file names was
> > expecting some hungarian notation, which totally didn't make sense, so
> > I refrained from commenting. Searching the web for the algorithm,
> > maybe 'lapjv.c' is adequate?  (short for "Linear Assignment Problem
> > Jonker Volgenant") Matlab has a function named lapjv solving the same
> > problem, so it would fall in line with the outside world.
> > 
> > Out of interest, why is it called hungarian in the first place? (I
> > presume that comes from some background of DScho in image processing
> > or such, so the the answer will be interesting for sure:)
> 
> I think it's because tbdiff uses the hungarian Python module, which
> implements the Hungarian method, also known as the Kuhn-Munkres
> algorithm, for solving the linear assignment problem.  This is the
> Jonker-Volgenant algorithm, which solves the same problem.  It's faster,
> but less tolerant.
> 
> At least this is what I just learned after about ten minutes of
> searching.

You learned well.

The Assignment Problem (or "Linear Assignment Problem") is generally
solved by the Hungarian algorithm. I forgot why it is called that way.
Kuhn-Munkres came up with a simplification of the algorithm IIRC but it
still is O(N^4). Then Jonker-Volgenant took a very different approach that
somehow results in O(N^3). It's been *years* since I studied both
implementations, so I cannot really explain what they do, and how the
latter achieves its order-of-magnitude better performance.

And after reading these mails, I agree that the name "hungarian" might be
confusing.

I also think that "lapjv" is confusing. In general, I try to let Matlab
conventions inform on my naming as little as possible, and I find my
naming fu a lot better for it.

So in this case, how about `linear-assignment.c`?

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 02/18] Add a new builtin: branch-diff
       [not found]                           ` <CAA8fPEkNjy+ETz4Mx+C2kUfLjLzR9uuOmO3GfN48ZH1SwyfE1A@mail.gmail.com>
  2018-05-26  6:15                             ` Fwd: " Øyvind Rønningstad
@ 2018-06-01  8:15                             ` Johannes Schindelin
  1 sibling, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-06-01  8:15 UTC (permalink / raw)
  To: Øyvind Rønningstad
  Cc: Stefan Beller, Eric Sunshine, Igor Djordjevic, Jacob Keller,
	Jeff King, Junio C Hamano, Ramsay Jones, Thomas Gummerer,
	Thomas Rast, git, Ævar Arnfjörð Bjarmason

[-- Attachment #1: Type: text/plain, Size: 462 bytes --]

Hi team,

especially Stefan: your thorough investigation about a better name than
range-diff gives me confidence that my decision to retract my objection
against has merit: it seems to be by far the one name that everybody but
me agrees on. And I can adapt easily.

On Sat, 26 May 2018, Øyvind Rønningstad wrote:

> Just want to throw my support in for range-diff since ranges is what you
> pass to the command.

`range-diff` it is.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v2 12/18] branch-diff: use color for the commit pairs
  2018-05-08  2:10         ` Todd Zullinger
@ 2018-06-01  8:17           ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-06-01  8:17 UTC (permalink / raw)
  To: Todd Zullinger
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason, Ramsay Jones,
	Stefan Beller, Jacob Keller, Eric Sunshine

Hi Todd,

On Mon, 7 May 2018, Todd Zullinger wrote:

> Johannes Schindelin wrote:
> > 
> > On Sat, 5 May 2018, Todd Zullinger wrote:
> > 
> >>> @@ -430,6 +451,8 @@ int cmd_branch_diff(int argc, const char **argv, const char *prefix)
> >>>  	struct string_list branch1 = STRING_LIST_INIT_DUP;
> >>>  	struct string_list branch2 = STRING_LIST_INIT_DUP;
> >>>  
> >>> +	git_diff_basic_config("diff.color.frag", "magenta", NULL);
> >>> +
> >>>  	diff_setup(&diffopt);
> >>>  	diffopt.output_format = DIFF_FORMAT_PATCH;
> >>>  	diffopt.flags.suppress_diff_headers = 1;
> >> 
> >> Should this also (or only) check color.diff.frag?
> > 
> > This code is not querying diff.color.frag, it is setting it. Without
> > any way to override it.
> > 
> > Having thought about it longer, and triggered by Peff's suggestion to
> > decouple the "reverse" part from the actual color, I fixed this by
> > 
> > - *not* setting .frag to magenta,
> > 
> > - using the reverse method also to mark outer *hunk headers* (not only
> > the outer -/+ markers).
> > 
> > - actually calling git_diff_ui_config()...
> 
> Excellent.  That seems to work nicely now, respecting the
> color.diff.<slot> config.
> 
> > The current work in progress can be pulled as `branch-diff` from
> > https://github.com/dscho/git, if I could ask you to test?
> 
> While the colors and 'branch --diff' usage seem to work
> nicely, I found that with 4ac3413cc8 ("branch-diff: left-pad
> patch numbers", 2018-05-05), 'git branch' itself is broken.
> 
> Running 'git branch' creates a branch named 'branch'.
> Calling 'git branch --list' shows only 'branch' as the only
> branch.
> 
> I didn't look too closely, but I'm guessing that the argv
> handling is leaving the 'branch' argument in place where it
> should be stripped?
> 
> This unsurprisingly breaks a large number of tests. :)

You will be delighted to learn that all of this is now moot, as I renamed
the command to `range-diff`, as this is what the wisdom of the crowd
chose.

Ciao,
Johannes

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 02/18] Add a new builtin: branch-diff
  2018-05-09 16:24               ` Ramsay Jones
@ 2018-06-01  8:23                 ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-06-01  8:23 UTC (permalink / raw)
  To: Ramsay Jones
  Cc: git, Junio C Hamano, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Ramsay,

On Wed, 9 May 2018, Ramsay Jones wrote:

> On 05/05/18 20:41, Johannes Schindelin wrote:
> [snip]
> 
> [Sorry for the late reply - still catching up after (long weekend)
> UK public holiday]
> 
> > Well, what I would want to do is let the cloud work for me. By adding an
> > automated build to my Visual Studio Team Services (VSTS) account, of
> > course, as I have "cloud privilege" (i.e. I work in the organization
> > providing the service, so I get to play with all of it for free).
> > 
> > So I really don't want to build sparse every time a new revision needs to
> > be tested (whether that be from one of my branches, an internal PR for
> > pre-review of patches to be sent to the mailing list, or maybe even `pu`
> > or the personalized branches on https://github.com/gitster/git).
> > 
> > I really would need a ready-to-install sparse, preferably as light-weight
> > as possible (by not requiring any dependencies outside what is available
> > in VSTS' hosted Linux build agents.
> > 
> > Maybe there is a specific apt source for sparse?
> 
> Not that I'm aware of, sorry! :(
> 
> [release _source_ tar-balls are available, but that's not
> what you are after, right?]

No, that's not what I am after, because my goal is not to build sparse
every time somebody pushes a new commit.

I want to use the Hosted Agents of Visual Studio Team Services (because I
have cloud privilege, as part of the team working on VSTS, I can use them
for free, as much as I want, within reason of course). And yes, I want to
use the Hosted Linux Agents for the sparse job.

So I cannot compile sparse and then install it into an agent. Those agents
are recycled after every build, so that every new build starts from a
clean slate.

If you have anything in the way of providing some easily-consumable
package, that would do the job. I guess I could build sparse.deb via
checkinstall in one VSTS build, offer it as artifact, and consume that
from the VSTS job that uses it on the Git branches.

Could you point me to a robus, yet current revision of sparse (and ideally
provide me with precise instructions how to build it so that I do not have
to hunt for that information)?

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike
  2018-05-22  1:42     ` Junio C Hamano
@ 2018-06-01  8:28       ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-06-01  8:28 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Thomas Rast, Thomas Gummerer,
	Ævar Arnfjörð Bjarmason

Hi Junio,

On Tue, 22 May 2018, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> >> In the picture, the three pre-context lines that are all indented by
> >> a HT are prefixed by a SP, and that is prefixed by a '+' sign of the
> >> outer diff.
> >
> > Yep, that's exactly it.
> >
> > The way tbdiff did it was to parse the diff and re-roll the coloring on
> > its own. I am not really keen on doing that in `branch --diff`, too.
> 
> Are you saying that these are "whitespace errors" getting painted?

Indentation errors, to be precise. Yes.

> It is somewhat odd because my whitespace errors are configured to be
> painted in "reverse blue".  Perhaps you are forcing the internal
> diff not to pay attention to the end-user configuration---which
> actually does make sense, as reusing of "git diff" to take "diff of
> diff" is a mere implementation detail.

It may have been the case before I introduced that call to
git_diff_ui_config(), but that happened after -v2, and I did not
contribute -v3 yet.

> In any case, the "whitespace errors" in "diff of diff" are mostly
> distracting.

Precisely. That's why I tried to suppress them in --dual-color mode.

I did not try to suppress them in --color (--no-dual-color) mode, as I
find that mode pretty useless.

> > I was wondering from the get-go whether it would make sense to make
> > --dual-color the default.
> >
> > But now I wonder whether there is actually *any* use in `--color` without
> > `--dual-color`.
> >
> > What do you think? Should I make the dual color mode the *only* color
> > mode?
> 
> Sorry but you are asking a good question to a wrong person.
> 
> I normally do not seek much useful information in colored output, so
> my reaction would not be very useful.  Non dual-color mode irritates
> me due to the false whitespace errors, and dual-color mode irritates
> me because it looks sufficiently different from tbdiff output that I
> am used to see.

Do you use --dual-color normally?

I derive *a ton* of information from the colored diff. It really helps me
navigate the output of range-diff very quickly.

I ask whether you use --dual-color because in that case I would consider
scrapping the way I handle color right now and try to imitate tbdiff's
way. But that would lose whitespace error coloring *altogether*. So I, for
one, would be unable to see where a subsequent patch series iteration
fixes whitespace errors of a previous iteration.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* [PATCH v3 20/20] range-diff: make --dual-color the default mode
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
                       ` (18 preceding siblings ...)
  2018-05-06 15:35     ` [PATCH v3 10/20] range-diff: do not show "function names" in hunk headers Johannes Schindelin via GitGitGadget
@ 2018-06-30 20:41     ` Johannes Schindelin via GitGitGadget
  2018-07-16  8:06       ` Eric Sunshine
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
  20 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-06-30 20:41 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

After using this command extensively for the last two months, this
developer came to the conclusion that even if the dual color mode still
leaves a lot of room for confusion what was actually changed, the
non-dual color mode is substantially worse in that regard.

Therefore, we really want to make the dual color mode the default.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/git-range-diff.txt       | 13 ++++++++-----
 builtin/range-diff.c                   | 10 ++++++----
 contrib/completion/git-completion.bash |  2 +-
 3 files changed, 15 insertions(+), 10 deletions(-)

diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
index 189236cc6..02d33ac43 100644
--- a/Documentation/git-range-diff.txt
+++ b/Documentation/git-range-diff.txt
@@ -31,11 +31,14 @@ all of their ancestors have been shown.
 
 OPTIONS
 -------
---dual-color::
-	When the commit diffs differ, recreate the original diffs'
-	coloring, and add outer -/+ diff markers with the *background*
-	being red/green to make it easier to see e.g. when there was a
-	change in what exact lines were added.
+--no-dual-color::
+	When the commit diffs differ, `git range-diff` recreates the
+	original diffs' coloring, and add outer -/+ diff markers with
+	the *background* being red/green to make it easier to see e.g.
+	when there was a change in what exact lines were added. This is
+	known to `range-diff` as "dual coloring". Use `--no-dual-color`
+	to revert to color all lines according to the outer diff markers
+	(and completely ignore the inner diff when it comes to color).
 
 --creation-factor=<percent>::
 	Set the creation/deletion cost fudge factor to `<percent>`.
diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index e8f7fe452..6cee0c73a 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -20,11 +20,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 {
 	int creation_factor = 60;
 	struct diff_options diffopt = { NULL };
-	int dual_color = 0;
+	int simple_color = -1;
 	struct option options[] = {
 		OPT_INTEGER(0, "creation-factor", &creation_factor,
 			    N_("Percentage by which creation is weighted")),
-		OPT_BOOL(0, "dual-color", &dual_color,
+		OPT_BOOL(0, "no-dual-color", &simple_color,
 			    N_("color both diff and diff-between-diffs")),
 		OPT_END()
 	};
@@ -53,8 +53,10 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 	argc = j;
 	diff_setup_done(&diffopt);
 
-	if (dual_color) {
-		diffopt.use_color = 1;
+	if (simple_color < 1) {
+		if (!simple_color)
+			/* force color when --dual-color was used */
+			diffopt.use_color = 1;
 		diffopt.flags.dual_color_diffed_diffs = 1;
 	}
 
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 402490673..e35fc28fc 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1981,7 +1981,7 @@ _git_range_diff ()
   case "$cur" in
   --*)
           __gitcomp "
-	  	--creation-factor= --dual-color
+	  	--creation-factor= --no-dual-color
                   $__git_diff_common_options
                   "
           return
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v3 00/20] Add `range-diff`, a `tbdiff` lookalike
  2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
                     ` (20 preceding siblings ...)
  2018-05-06 22:56   ` brian m. carlson
@ 2018-07-03 11:26   ` Johannes Schindelin via GitGitGadget
  2018-04-30 21:54     ` [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems Johannes Schindelin via GitGitGadget
                       ` (20 more replies)
  21 siblings, 21 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-03 11:26 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

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.

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.

While at it, I forward-ported AEvar's patch to force `--decorate=no` because `git -p tbdiff` would fail otherwise.

Side 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
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:

```sh
        base=^{/Start.the.merging-rebase}
        tag=v2.17.0.windows.1
        pre=$tag$base^2
        git branch-diff --dual-color $pre$base..$pre $tag$base..$tag
```

The --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.

Changes since v2:

- Right-aligned the patch numbers in the commit pairs.
- Used ALLOC_ARRAY() in hungarian.c instead of xmalloc(sizeof()*size).
- Changed compute_assignment()s return type from int to void, as it always succeeds.
- Changed the Hungarian Algorithm to use an integer cost matrix.
- Changed the --creation-weight <double> option to --creation-factor <percent> where <percent> is an integer.
- Retitled 1/19 and 2/19 to better conform with the current conventions, as pointed out (and suggested) by Junio.
- Shut up Coverity, and at the same time avoided passing the unnecessary `i` and `j` parameters to output_pair_header().
- Removed support for the `--no-patches` option: we inherit diff_options' support for `-s` already (and much more).
- 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.
- Instead of overriding the fragment header color, the dual color mode will now reverse the "outer" fragment headers, too.
- 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.
- Pretty much re-wrote the completion, to support the new --diff mode of git-branch. See below: it was reverted for range-diff.
- Renamed t7910 to t3206, to be closer to the git-branch tests.
- Ensured that git_diff_ui_config() gets called, and therefore color.diff.* respected.
- Avoided leaking `four_spaces`.
- Fixed a declaration in a for (;;) statement (which Junio had as a fixup! that I almost missed).
- Renamed `branch --diff`, which had been renamed from `branch-diff` (which was picked to avoid re-using `tbdiff`) to `range-diff`.
- Renamed `hungarian.c` and its header to `linear-assignment.c`
- Made `--dual-color` the default, and changed it to still auto-detect whether color should be used rather than forcing it

Johannes Schindelin (19):
  linear-assignment: a function to solve least-cost assignment problems
  Introduce `range-diff` to compare iterations of a topic branch
  range-diff: first rudimentary implementation
  range-diff: improve the order of the shown commits
  range-diff: also show the diff between patches
  range-diff: right-trim commit messages
  range-diff: indent the diffs just like tbdiff
  range-diff: suppress the diff headers
  range-diff: adjust the output of the commit pairs
  range-diff: do not show "function names" in hunk headers
  range-diff: use color for the commit pairs
  color: add the meta color GIT_COLOR_REVERSE
  diff: add an internal option to dual-color diffs of diffs
  range-diff: offer to dual-color the diffs
  range-diff --dual-color: work around bogus white-space warning
  range-diff: add a man page
  completion: support `git range-diff`
  range-diff: left-pad patch numbers
  range-diff: make --dual-color the default mode

Thomas Rast (1):
  range-diff: add tests

 .gitignore                             |   1 +
 Documentation/git-range-diff.txt       | 238 ++++++++++
 Makefile                               |   3 +
 builtin.h                              |   1 +
 builtin/range-diff.c                   | 104 +++++
 color.h                                |   1 +
 command-list.txt                       |   1 +
 contrib/completion/git-completion.bash |  14 +
 diff.c                                 |  94 +++-
 diff.h                                 |   2 +
 git.c                                  |   1 +
 linear-assignment.c                    | 203 +++++++++
 linear-assignment.h                    |  22 +
 range-diff.c                           | 437 ++++++++++++++++++
 range-diff.h                           |   9 +
 t/.gitattributes                       |   1 +
 t/t3206-range-diff.sh                  | 145 ++++++
 t/t3206/history.export                 | 604 +++++++++++++++++++++++++
 18 files changed, 1865 insertions(+), 16 deletions(-)
 create mode 100644 Documentation/git-range-diff.txt
 create mode 100644 builtin/range-diff.c
 create mode 100644 linear-assignment.c
 create mode 100644 linear-assignment.h
 create mode 100644 range-diff.c
 create mode 100644 range-diff.h
 create mode 100755 t/t3206-range-diff.sh
 create mode 100644 t/t3206/history.export


base-commit: e3331758f12da22f4103eec7efe1b5304a9be5e9
Published-As: https://github.com/gitgitgadget/git/releases/tags/pr-1%2Fdscho%2Fbranch-diff-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1/dscho/branch-diff-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/1

Range-diff vs v2:

  1:  3f51970cb !  1:  39272eefc Add a function to solve least-cost assignment problems
     @@ -1,11 +1,17 @@
      Author: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -    Add a function to solve least-cost assignment problems
     +    linear-assignment: a function to solve least-cost assignment problems
      
     -    The Jonker-Volgenant algorithm was implemented to answer questions such
     -    as: given two different versions of a topic branch (or iterations of a
     -    patch series), what is the best pairing of commits/patches between the
     -    different versions?
     +    The problem solved by the code introduced in this commit goes like this:
     +    given two sets of items, and a cost matrix which says how much it
     +    "costs" to assign any given item of the first set to any given item of
     +    the second, assign all items (except when the sets have different size)
     +    in the cheapest way.
     +
     +    We use the Jonker-Volgenant algorithm to solve the assignment problem to
     +    answer questions such as: given two different versions of a topic branch
     +    (or iterations of a patch series), what is the best pairing of
     +    commits/patches between the different versions?
      
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     @@ -16,15 +22,15 @@
       LIB_OBJS += graph.o
       LIB_OBJS += grep.o
       LIB_OBJS += hashmap.o
     -+LIB_OBJS += hungarian.o
     ++LIB_OBJS += linear-assignment.o
       LIB_OBJS += help.o
       LIB_OBJS += hex.o
       LIB_OBJS += ident.o
      
     -diff --git a/hungarian.c b/hungarian.c
     +diff --git a/linear-assignment.c b/linear-assignment.c
      new file mode 100644
      --- /dev/null
     -+++ b/hungarian.c
     ++++ b/linear-assignment.c
      @@
      +/*
      + * Based on: Jonker, R., & Volgenant, A. (1987). <i>A shortest augmenting path
     @@ -32,8 +38,7 @@
      + * 38(4), 325-340.
      + */
      +#include "cache.h"
     -+#include "hungarian.h"
     -+#include <float.h>
     ++#include "linear-assignment.h"
      +
      +#define COST(column, row) cost[(column) + column_count * (row)]
      +
     @@ -41,15 +46,16 @@
      + * The parameter `cost` is the cost matrix: the cost to assign column j to row
      + * i is `cost[j + column_count * i].
      + */
     -+int compute_assignment(int column_count, int row_count, double *cost,
     -+		       int *column2row, int *row2column)
     ++void compute_assignment(int column_count, int row_count, int *cost,
     ++			int *column2row, int *row2column)
      +{
     -+	double *v = xmalloc(sizeof(double) * column_count), *d;
     ++	int *v, *d;
      +	int *free_row, free_count = 0, saved_free_count, *pred, *col;
      +	int i, j, phase;
      +
      +	memset(column2row, -1, sizeof(int) * column_count);
      +	memset(row2column, -1, sizeof(int) * row_count);
     ++	ALLOC_ARRAY(v, column_count);
      +
      +	/* column reduction */
      +	for (j = column_count - 1; j >= 0; j--) {
     @@ -71,15 +77,15 @@
      +	}
      +
      +	/* reduction transfer */
     -+	free_row = xmalloc(sizeof(int) * row_count);
     -+	for (int i = 0; i < row_count; i++) {
     ++	ALLOC_ARRAY(free_row, row_count);
     ++	for (i = 0; i < row_count; i++) {
      +		int j1 = row2column[i];
      +		if (j1 == -1)
      +			free_row[free_count++] = i;
      +		else if (j1 < -1)
      +			row2column[i] = -2 - j1;
      +		else {
     -+			double min = COST(!j1, i) - v[!j1];
     ++			int min = COST(!j1, i) - v[!j1];
      +			for (j = 1; j < column_count; j++)
      +				if (j != j1 && min > COST(j, i) - v[j])
      +					min = COST(j, i) - v[j];
     @@ -91,7 +97,7 @@
      +	    (column_count < row_count ? row_count - column_count : 0)) {
      +		free(v);
      +		free(free_row);
     -+		return 0;
     ++		return;
      +	}
      +
      +	/* augmenting row reduction */
     @@ -101,15 +107,15 @@
      +		saved_free_count = free_count;
      +		free_count = 0;
      +		while (k < saved_free_count) {
     -+			double u1, u2;
     ++			int u1, u2;
      +			int j1 = 0, j2, i0;
      +
      +			i = free_row[k++];
      +			u1 = COST(j1, i) - v[j1];
      +			j2 = -1;
     -+			u2 = DBL_MAX;
     ++			u2 = INT_MAX;
      +			for (j = 1; j < column_count; j++) {
     -+				double c = COST(j, i) - v[j];
     ++				int c = COST(j, i) - v[j];
      +				if (u2 > c) {
      +					if (u1 < c) {
      +						u2 = c;
     @@ -148,12 +154,12 @@
      +
      +	/* augmentation */
      +	saved_free_count = free_count;
     -+	d = xmalloc(sizeof(double) * column_count);
     -+	pred = xmalloc(sizeof(int) * column_count);
     -+	col = xmalloc(sizeof(int) * column_count);
     ++	ALLOC_ARRAY(d, column_count);
     ++	ALLOC_ARRAY(pred, column_count);
     ++	ALLOC_ARRAY(col, column_count);
      +	for (free_count = 0; free_count < saved_free_count; free_count++) {
      +		int i1 = free_row[free_count], low = 0, up = 0, last, k;
     -+		double min, c, u1;
     ++		int min, c, u1;
      +
      +		for (j = 0; j < column_count; j++) {
      +			d[j] = COST(j, i1) - v[j];
     @@ -228,14 +234,12 @@
      +	free(d);
      +	free(v);
      +	free(free_row);
     -+
     -+	return 0;
      +}
      
     -diff --git a/hungarian.h b/hungarian.h
     +diff --git a/linear-assignment.h b/linear-assignment.h
      new file mode 100644
      --- /dev/null
     -+++ b/hungarian.h
     ++++ b/linear-assignment.h
      @@
      +#ifndef HUNGARIAN_H
      +#define HUNGARIAN_H
     @@ -252,7 +256,10 @@
      + * assignments (-1 for unassigned, which can happen only if column_count !=
      + * row_count).
      + */
     -+int compute_assignment(int column_count, int row_count, double *cost,
     -+		       int *column2row, int *row2column);
     ++void compute_assignment(int column_count, int row_count, int *cost,
     ++			int *column2row, int *row2column);
     ++
     ++/* The maximal cost in the cost matrix (to prevent integer overflows). */
     ++#define COST_MAX (1<<16)
      +
      +#endif
  2:  a1ea0320b <  -:  --------- Add a new builtin: branch-diff
  -:  --------- >  2:  7f15b26d4 Introduce `range-diff` to compare iterations of a topic branch
  3:  e530e450e !  3:  076e1192d branch-diff: first rudimentary implementation
     @@ -1,46 +1,117 @@
      Author: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -    branch-diff: first rudimentary implementation
     +    range-diff: first rudimentary implementation
      
     -    At this stage, `git branch-diff` can determine corresponding commits of
     -    two related commit ranges. This makes use of the recently introduced
     +    At this stage, `git range-diff` can determine corresponding commits
     +    of two related commit ranges. This makes use of the recently introduced
          implementation of the Hungarian algorithm.
      
          The core of this patch is a straight port of the ideas of tbdiff, the
     -    seemingly dormant project at https://github.com/trast/tbdiff.
     +    apparently dormant project at https://github.com/trast/tbdiff.
      
          The output does not at all match `tbdiff`'s output yet, as this patch
          really concentrates on getting the patch matching part right.
      
     -    Note: due to differences in the diff algorithm (`tbdiff` uses the
     -    Python module `difflib`, Git uses its xdiff fork), the cost matrix
     -    calculated by `branch-diff` is different (but very similar) to the one
     -    calculated by `tbdiff`. Therefore, it is possible that they find
     -    different matching commits in corner cases (e.g. when a patch was split
     -    into two patches of roughly equal length).
     +    Note: due to differences in the diff algorithm (`tbdiff` uses the Python
     +    module `difflib`, Git uses its xdiff fork), the cost matrix calculated
     +    by `range-diff` is different (but very similar) to the one calculated
     +    by `tbdiff`. Therefore, it is possible that they find different matching
     +    commits in corner cases (e.g. when a patch was split into two patches of
     +    roughly equal length).
      
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
     ---- a/builtin/branch-diff.c
     -+++ b/builtin/branch-diff.c
     +diff --git a/Makefile b/Makefile
     +--- a/Makefile
     ++++ b/Makefile
     +@@
     + LIB_OBJS += prompt.o
     + LIB_OBJS += protocol.o
     + LIB_OBJS += quote.o
     ++LIB_OBJS += range-diff.o
     + LIB_OBJS += reachable.o
     + LIB_OBJS += read-cache.o
     + LIB_OBJS += reflog-walk.o
     +
     +diff --git a/builtin/range-diff.c b/builtin/range-diff.c
     +--- a/builtin/range-diff.c
     ++++ b/builtin/range-diff.c
      @@
       #include "cache.h"
       #include "builtin.h"
       #include "parse-options.h"
     ++#include "range-diff.h"
     + 
     + static const char * const builtin_range_diff_usage[] = {
     + N_("git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
     +@@
     + 			    N_("Percentage by which creation is weighted")),
     + 		OPT_END()
     + 	};
     ++	int res = 0;
     ++	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
     + 
     +-	argc = parse_options(argc, argv, NULL, options,
     +-			     builtin_range_diff_usage, 0);
     ++	argc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,
     ++			     0);
     + 
     +-	return 0;
     ++	if (argc == 2) {
     ++		if (!strstr(argv[0], ".."))
     ++			warning(_("no .. in range: '%s'"), argv[0]);
     ++		strbuf_addstr(&range1, argv[0]);
     ++
     ++		if (!strstr(argv[1], ".."))
     ++			warning(_("no .. in range: '%s'"), argv[1]);
     ++		strbuf_addstr(&range2, argv[1]);
     ++	} else if (argc == 3) {
     ++		strbuf_addf(&range1, "%s..%s", argv[0], argv[1]);
     ++		strbuf_addf(&range2, "%s..%s", argv[0], argv[2]);
     ++	} else if (argc == 1) {
     ++		const char *b = strstr(argv[0], "..."), *a = argv[0];
     ++		int a_len;
     ++
     ++		if (!b)
     ++			die(_("single arg format requires a symmetric range"));
     ++
     ++		a_len = (int)(b - a);
     ++		if (!a_len) {
     ++			a = "HEAD";
     ++			a_len = strlen(a);
     ++		}
     ++		b += 3;
     ++		if (!*b)
     ++			b = "HEAD";
     ++		strbuf_addf(&range1, "%s..%.*s", b, a_len, a);
     ++		strbuf_addf(&range2, "%.*s..%s", a_len, a, b);
     ++	} else {
     ++		error(_("need two commit ranges"));
     ++		usage_with_options(builtin_range_diff_usage, options);
     ++	}
     ++
     ++	res = show_range_diff(range1.buf, range2.buf, creation_factor);
     ++
     ++	strbuf_release(&range1);
     ++	strbuf_release(&range2);
     ++
     ++	return res;
     + }
     +
     +diff --git a/range-diff.c b/range-diff.c
     +new file mode 100644
     +--- /dev/null
     ++++ b/range-diff.c
     +@@
     ++#include "cache.h"
     ++#include "range-diff.h"
      +#include "string-list.h"
      +#include "run-command.h"
      +#include "argv-array.h"
      +#include "hashmap.h"
      +#include "xdiff-interface.h"
     -+#include "hungarian.h"
     - 
     - static const char * const builtin_branch_diff_usage[] = {
     - N_("git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
     -@@
     - 	return 0;
     - }
     - 
     ++#include "linear-assignment.h"
     ++
      +struct patch_util {
      +	/* For the search for an exact match */
      +	struct hashmap_entry e;
     @@ -219,16 +290,19 @@
      +		return count;
      +
      +	error(_("failed to generate diff"));
     -+	return INT_MAX;
     ++	return COST_MAX;
      +}
      +
     -+static int get_correspondences(struct string_list *a, struct string_list *b,
     -+			       double creation_weight)
     ++static void get_correspondences(struct string_list *a, struct string_list *b,
     ++				int creation_factor)
      +{
      +	int n = a->nr + b->nr;
     -+	double *cost = xmalloc(sizeof(double) * n * n), c;
     -+	int *a2b = xmalloc(sizeof(int) * n), *b2a = xmalloc(sizeof(int) * n);
     -+	int i, j, res;
     ++	int *cost, c, *a2b, *b2a;
     ++	int i, j;
     ++
     ++	ALLOC_ARRAY(cost, st_mult(n, n));
     ++	ALLOC_ARRAY(a2b, n);
     ++	ALLOC_ARRAY(b2a, n);
      +
      +	for (i = 0; i < a->nr; i++) {
      +		struct patch_util *a_util = a->items[i].util;
     @@ -241,12 +315,12 @@
      +			else if (a_util->matching < 0 && b_util->matching < 0)
      +				c = diffsize(a_util->diff, b_util->diff);
      +			else
     -+				c = INT_MAX;
     ++				c = COST_MAX;
      +			cost[i + n * j] = c;
      +		}
      +
      +		c = a_util->matching < 0 ?
     -+			a_util->diffsize * creation_weight : INT_MAX;
     ++			a_util->diffsize * creation_factor / 100 : COST_MAX;
      +		for (j = b->nr; j < n; j++)
      +			cost[i + n * j] = c;
      +	}
     @@ -255,7 +329,7 @@
      +		struct patch_util *util = b->items[j].util;
      +
      +		c = util->matching < 0 ?
     -+			util->diffsize * creation_weight : INT_MAX;
     ++			util->diffsize * creation_factor / 100 : COST_MAX;
      +		for (i = a->nr; i < n; i++)
      +			cost[i + n * j] = c;
      +	}
     @@ -264,7 +338,7 @@
      +		for (j = b->nr; j < n; j++)
      +			cost[i + n * j] = 0;
      +
     -+	res = compute_assignment(n, n, cost, a2b, b2a);
     ++	compute_assignment(n, n, cost, a2b, b2a);
      +
      +	for (i = 0; i < a->nr; i++)
      +		if (a2b[i] >= 0 && a2b[i] < b->nr) {
     @@ -278,8 +352,6 @@
      +	free(cost);
      +	free(a2b);
      +	free(b2a);
     -+
     -+	return res;
      +}
      +
      +static const char *short_oid(struct patch_util *util)
     @@ -314,71 +386,40 @@
      +	}
      +}
      +
     - int cmd_branch_diff(int argc, const char **argv, const char *prefix)
     - {
     - 	double creation_weight = 0.6;
     -@@
     - 			0, parse_creation_weight },
     - 		OPT_END()
     - 	};
     ++int show_range_diff(const char *range1, const char *range2,
     ++		    int creation_factor)
     ++{
      +	int res = 0;
     -+	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
     ++
      +	struct string_list branch1 = STRING_LIST_INIT_DUP;
      +	struct string_list branch2 = STRING_LIST_INIT_DUP;
     - 
     - 	argc = parse_options(argc, argv, NULL, options,
     - 			builtin_branch_diff_usage, 0);
     - 
     --	return 0;
     -+	if (argc == 2) {
     -+		if (!strstr(argv[0], ".."))
     -+			warning(_("no .. in range: '%s'"), argv[0]);
     -+		strbuf_addstr(&range1, argv[0]);
     -+
     -+		if (!strstr(argv[1], ".."))
     -+			warning(_("no .. in range: '%s'"), argv[1]);
     -+		strbuf_addstr(&range2, argv[1]);
     -+	} else if (argc == 3) {
     -+		strbuf_addf(&range1, "%s..%s", argv[0], argv[1]);
     -+		strbuf_addf(&range2, "%s..%s", argv[0], argv[2]);
     -+	} else if (argc == 1) {
     -+		const char *b = strstr(argv[0], "..."), *a = argv[0];
     -+		int a_len;
      +
     -+		if (!b)
     -+			die(_("single arg format requires a symmetric range"));
     -+
     -+		a_len = (int)(b - a);
     -+		if (!a_len) {
     -+			a = "HEAD";
     -+			a_len = strlen(a);
     -+		}
     -+		b += 3;
     -+		if (!*b)
     -+			b = "HEAD";
     -+		strbuf_addf(&range1, "%s..%.*s", b, a_len, a);
     -+		strbuf_addf(&range2, "%.*s..%s", a_len, a, b);
     -+	} else {
     -+		error(_("need two commit ranges"));
     -+		usage_with_options(builtin_branch_diff_usage, options);
     -+	}
     -+
     -+	if (read_patches(range1.buf, &branch1))
     -+		res = error(_("could not parse log for '%s'"), range1.buf);
     -+	if (!res && read_patches(range2.buf, &branch2))
     -+		res = error(_("could not parse log for '%s'"), range2.buf);
     ++	if (read_patches(range1, &branch1))
     ++		res = error(_("could not parse log for '%s'"), range1);
     ++	if (!res && read_patches(range2, &branch2))
     ++		res = error(_("could not parse log for '%s'"), range2);
      +
      +	if (!res) {
      +		find_exact_matches(&branch1, &branch2);
     -+		res = get_correspondences(&branch1, &branch2, creation_weight);
     -+		if (!res)
     -+			output(&branch1, &branch2);
     ++		get_correspondences(&branch1, &branch2, creation_factor);
     ++		output(&branch1, &branch2);
      +	}
      +
     -+	strbuf_release(&range1);
     -+	strbuf_release(&range2);
      +	string_list_clear(&branch1, 1);
      +	string_list_clear(&branch2, 1);
      +
     -+	return !!res;
     - }
     ++	return res;
     ++}
     +
     +diff --git a/range-diff.h b/range-diff.h
     +new file mode 100644
     +--- /dev/null
     ++++ b/range-diff.h
     +@@
     ++#ifndef BRANCH_DIFF_H
     ++#define BRANCH_DIFF_H
     ++
     ++int show_range_diff(const char *range1, const char *range2,
     ++		    int creation_factor);
     ++
     ++#endif
  4:  3032e2709 !  4:  e98489c8c branch-diff: improve the order of the shown commits
     @@ -1,13 +1,13 @@
      Author: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -    branch-diff: improve the order of the shown commits
     +    range-diff: improve the order of the shown commits
      
     -    This patch lets branch-diff use the same order as tbdiff.
     +    This patch lets `git range-diff` use the same order as tbdiff.
      
          The idea is simple: for left-to-right readers, it is natural to assume
     -    that the branch-diff is performed between an older vs a newer version of
     -    the branch. As such, the user is probably more interested in the
     -    question "where did this come from?" rather than "where did that one
     +    that the `git range-diff` is performed between an older vs a newer
     +    version of the branch. As such, the user is probably more interested in
     +    the question "where did this come from?" rather than "where did that one
          go?".
      
          To that end, we list the commits in the order of the second commit range
     @@ -16,9 +16,9 @@
      
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
     ---- a/builtin/branch-diff.c
     -+++ b/builtin/branch-diff.c
     +diff --git a/range-diff.c b/range-diff.c
     +--- a/range-diff.c
     ++++ b/range-diff.c
      @@
       	struct hashmap_entry e;
       	const char *diff, *patch;
  5:  12d9c7977 <  -:  --------- branch-diff: also show the diff between patches
  -:  --------- >  5:  935cad180 range-diff: also show the diff between patches
  6:  53ee6ba38 !  6:  93ac1931d branch-diff: right-trim commit messages
     @@ -1,6 +1,6 @@
      Author: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -    branch-diff: right-trim commit messages
     +    range-diff: right-trim commit messages
      
          When comparing commit messages, we need to keep in mind that they are
          indented by four spaces. That is, empty lines are no longer empty, but
     @@ -9,13 +9,13 @@
      
          Let's just right-trim the lines in the commit message, it's not like
          trailing white-space in the commit messages are important enough to care
     -    about in branch-diff.
     +    about in `git range-diff`.
      
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
     ---- a/builtin/branch-diff.c
     -+++ b/builtin/branch-diff.c
     +diff --git a/range-diff.c b/range-diff.c
     +--- a/range-diff.c
     ++++ b/range-diff.c
      @@
       				strbuf_addbuf(&buf, &line);
       				strbuf_addstr(&buf, "\n\n");
  7:  c856c460a <  -:  --------- branch-diff: indent the diffs just like tbdiff
  -:  --------- >  7:  ca5282815 range-diff: indent the diffs just like tbdiff
  8:  35a9681a1 !  8:  80622685f branch-diff: suppress the diff headers
     @@ -1,6 +1,6 @@
      Author: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -    branch-diff: suppress the diff headers
     +    range-diff: suppress the diff headers
      
          When showing the diff between corresponding patches of the two branch
          versions, we have to make up a fake filename to run the diff machinery.
     @@ -10,9 +10,9 @@
      
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
     ---- a/builtin/branch-diff.c
     -+++ b/builtin/branch-diff.c
     +diff --git a/builtin/range-diff.c b/builtin/range-diff.c
     +--- a/builtin/range-diff.c
     ++++ b/builtin/range-diff.c
      @@
       
       	diff_setup(&diffopt);
  9:  0e4c8279e !  9:  6b31cbf72 branch-diff: adjust the output of the commit pairs
     @@ -1,33 +1,33 @@
      Author: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -    branch-diff: adjust the output of the commit pairs
     +    range-diff: adjust the output of the commit pairs
      
     -    This change brings branch-diff yet another step closer to feature parity
     -    with tbdiff: it now shows the oneline, too, and indicates with `=` when
     -    the commits have identical diffs.
     +    This change brings `git range-diff` yet another step closer to
     +    feature parity with tbdiff: it now shows the oneline, too, and indicates
     +    with `=` when the commits have identical diffs.
      
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
     ---- a/builtin/branch-diff.c
     -+++ b/builtin/branch-diff.c
     +diff --git a/range-diff.c b/range-diff.c
     +--- a/range-diff.c
     ++++ b/range-diff.c
      @@
     - #include "hungarian.h"
     - #include "diff.h"
     + #include "xdiff-interface.h"
     + #include "linear-assignment.h"
       #include "diffcore.h"
      +#include "commit.h"
      +#include "pretty.h"
       
     - static const char * const builtin_branch_diff_usage[] = {
     - N_("git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
     + struct patch_util {
     + 	/* For the search for an exact match */
      @@
     - 	return res;
     + 	free(b2a);
       }
       
      -static const char *short_oid(struct patch_util *util)
      +static void output_pair_header(struct strbuf *buf,
     -+			       int i, struct patch_util *a_util,
     -+			       int j, struct patch_util *b_util)
     ++			       struct patch_util *a_util,
     ++			       struct patch_util *b_util)
       {
      -	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
      +	static char *dashes;
     @@ -43,25 +43,25 @@
      +	}
      +
      +	strbuf_reset(buf);
     -+	if (i < 0)
     ++	if (!a_util)
      +		strbuf_addf(buf, "-:  %s ", dashes);
      +	else
     -+		strbuf_addf(buf, "%d:  %s ", i + 1,
     ++		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
      +			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
      +
     -+	if (i < 0)
     ++	if (!a_util)
      +		strbuf_addch(buf, '>');
     -+	else if (j < 0)
     ++	else if (!b_util)
      +		strbuf_addch(buf, '<');
      +	else if (strcmp(a_util->patch, b_util->patch))
      +		strbuf_addch(buf, '!');
      +	else
      +		strbuf_addch(buf, '=');
      +
     -+	if (j < 0)
     ++	if (!b_util)
      +		strbuf_addf(buf, " -:  %s", dashes);
      +	else
     -+		strbuf_addf(buf, " %d:  %s", j + 1,
     ++		strbuf_addf(buf, " %d:  %s", b_util->i + 1,
      +			    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));
      +
      +	commit = lookup_commit_reference(oid);
     @@ -79,7 +79,7 @@
      +	fwrite(buf->buf, buf->len, 1, stdout);
       }
       
     - static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)
     + static struct diff_filespec *get_filespec(const char *name, const char *p)
      @@
       static void output(struct string_list *a, struct string_list *b,
       		   struct diff_options *diffopt)
     @@ -94,7 +94,7 @@
       		if (i < a->nr && a_util->matching < 0) {
      -			printf("%d: %s < -: --------\n",
      -			       i + 1, short_oid(a_util));
     -+			output_pair_header(&buf, i, a_util, -1, NULL);
     ++			output_pair_header(&buf, a_util, NULL);
       			i++;
       			continue;
       		}
     @@ -103,7 +103,7 @@
       		while (j < b->nr && b_util->matching < 0) {
      -			printf("-: -------- > %d: %s\n",
      -			       j + 1, short_oid(b_util));
     -+			output_pair_header(&buf, -1, NULL, j, b_util);
     ++			output_pair_header(&buf, NULL, b_util);
       			b_util = ++j < b->nr ? b->items[j].util : NULL;
       		}
       
     @@ -113,8 +113,7 @@
      -			printf("%d: %s ! %d: %s\n",
      -			       b_util->matching + 1, short_oid(a_util),
      -			       j + 1, short_oid(b_util));
     -+			output_pair_header(&buf,
     -+					   b_util->matching, a_util, j, b_util);
     ++			output_pair_header(&buf, a_util, b_util);
       			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
       				patch_diff(a->items[b_util->matching].string,
       					   b->items[j].string, diffopt);
     @@ -125,4 +124,4 @@
      +	strbuf_release(&buf);
       }
       
     - int cmd_branch_diff(int argc, const char **argv, const char *prefix)
     + int show_range_diff(const char *range1, const char *range2,
 10:  2695a6abc ! 10:  ef997bb8b branch-diff: do not show "function names" in hunk headers
     @@ -1,25 +1,25 @@
      Author: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -    branch-diff: do not show "function names" in hunk headers
     +    range-diff: do not show "function names" in hunk headers
      
          We are comparing complete, formatted commit messages with patches. There
          are no function names here, so stop looking for them.
      
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
     ---- a/builtin/branch-diff.c
     -+++ b/builtin/branch-diff.c
     +diff --git a/range-diff.c b/range-diff.c
     +--- a/range-diff.c
     ++++ b/range-diff.c
      @@
       #include "diffcore.h"
       #include "commit.h"
       #include "pretty.h"
      +#include "userdiff.h"
       
     - static const char * const builtin_branch_diff_usage[] = {
     - N_("git branch-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
     + struct patch_util {
     + 	/* For the search for an exact match */
      @@
     - 	return data;
     + 	fwrite(buf->buf, buf->len, 1, stdout);
       }
       
      +static struct userdiff_driver no_func_name = {
 11:  313beeed3 ! 11:  3d9e5b0ba branch-diff: add tests
     @@ -1,11 +1,12 @@
      Author: Thomas Rast <tr@thomasrast.ch>
      
     -    branch-diff: add tests
     +    range-diff: add tests
      
          These are essentially lifted from https://github.com/trast/tbdiff, with
     -    light touch-ups to account for the new command name.
     +    light touch-ups to account for the command now being an option of `git
     +    branch`.
      
     -    Apart from renaming `tbdiff` to `branch-diff`, only one test case needed
     +    Apart from renaming `tbdiff` to `range-diff`, only one test case needed
          to be adjusted: 11 - 'changed message'.
      
          The underlying reason it had to be adjusted is that diff generation is
     @@ -28,26 +29,27 @@
       /t8005/*.txt eol=lf
       /t9*/*.dump eol=lf
      
     -diff --git a/t/t7910-branch-diff.sh b/t/t7910-branch-diff.sh
     +diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
      new file mode 100755
      --- /dev/null
     -+++ b/t/t7910-branch-diff.sh
     ++++ b/t/t3206-range-diff.sh
      @@
      +#!/bin/sh
      +
     -+test_description='branch-diff tests'
     ++test_description='range-diff tests'
      +
      +. ./test-lib.sh
      +
     -+# Note that because of git-branch-diff's heuristics, test_commit does more
     ++# Note that because of the range-diff's heuristics, test_commit does more
      +# harm than good.  We need some real history.
      +
      +test_expect_success 'setup' '
     -+	git fast-import < "$TEST_DIRECTORY"/t7910/history.export
     ++	git fast-import < "$TEST_DIRECTORY"/t3206/history.export
      +'
      +
      +test_expect_success 'simple A..B A..C (unmodified)' '
     -+	git branch-diff --no-color master..topic master..unmodified >actual &&
     ++	git range-diff --no-color master..topic master..unmodified \
     ++		>actual &&
      +	cat >expected <<-EOF &&
      +	1:  4de457d = 1:  35b9b25 s/5/A/
      +	2:  fccce22 = 2:  de345ab s/4/A/
     @@ -58,19 +60,19 @@
      +'
      +
      +test_expect_success 'simple B...C (unmodified)' '
     -+	git branch-diff --no-color topic...unmodified >actual &&
     ++	git range-diff --no-color topic...unmodified >actual &&
      +	# same "expected" as above
      +	test_cmp expected actual
      +'
      +
      +test_expect_success 'simple A B C (unmodified)' '
     -+	git branch-diff --no-color master topic unmodified >actual &&
     ++	git range-diff --no-color master topic unmodified >actual &&
      +	# same "expected" as above
      +	test_cmp expected actual
      +'
      +
      +test_expect_success 'trivial reordering' '
     -+	git branch-diff --no-color master topic reordered >actual &&
     ++	git range-diff --no-color master topic reordered >actual &&
      +	cat >expected <<-EOF &&
      +	1:  4de457d = 1:  aca177a s/5/A/
      +	3:  147e64e = 2:  14ad629 s/11/B/
     @@ -81,7 +83,7 @@
      +'
      +
      +test_expect_success 'removed a commit' '
     -+	git branch-diff --no-color master topic removed >actual &&
     ++	git range-diff --no-color master topic removed >actual &&
      +	cat >expected <<-EOF &&
      +	1:  4de457d = 1:  7657159 s/5/A/
      +	2:  fccce22 < -:  ------- s/4/A/
     @@ -92,7 +94,7 @@
      +'
      +
      +test_expect_success 'added a commit' '
     -+	git branch-diff --no-color master topic added >actual &&
     ++	git range-diff --no-color master topic added >actual &&
      +	cat >expected <<-EOF &&
      +	1:  4de457d = 1:  2716022 s/5/A/
      +	2:  fccce22 = 2:  b62accd s/4/A/
     @@ -104,7 +106,7 @@
      +'
      +
      +test_expect_success 'new base, A B C' '
     -+	git branch-diff --no-color master topic rebased >actual &&
     ++	git range-diff --no-color master topic rebased >actual &&
      +	cat >expected <<-EOF &&
      +	1:  4de457d = 1:  cc9c443 s/5/A/
      +	2:  fccce22 = 2:  c5d9641 s/4/A/
     @@ -116,7 +118,7 @@
      +
      +test_expect_success 'new base, B...C' '
      +	# this syntax includes the commits from master!
     -+	git branch-diff --no-color topic...rebased >actual &&
     ++	git range-diff --no-color topic...rebased >actual &&
      +	cat >expected <<-EOF &&
      +	-:  ------- > 1:  a31b12e unrelated
      +	1:  4de457d = 2:  cc9c443 s/5/A/
     @@ -128,7 +130,7 @@
      +'
      +
      +test_expect_success 'changed commit' '
     -+	git branch-diff --no-color topic...changed >actual &&
     ++	git range-diff --no-color topic...changed >actual &&
      +	cat >expected <<-EOF &&
      +	1:  4de457d = 1:  a4b3333 s/5/A/
      +	2:  fccce22 = 2:  f51d370 s/4/A/
     @@ -157,7 +159,7 @@
      +'
      +
      +test_expect_success 'changed message' '
     -+	git branch-diff --no-color topic...changed-message >actual &&
     ++	git range-diff --no-color topic...changed-message >actual &&
      +	sed s/Z/\ /g >expected <<-EOF &&
      +	1:  4de457d = 1:  f686024 s/5/A/
      +	2:  fccce22 ! 2:  4ab067d s/4/A/
     @@ -178,10 +180,10 @@
      +
      +test_done
      
     -diff --git a/t/t7910/history.export b/t/t7910/history.export
     +diff --git a/t/t3206/history.export b/t/t3206/history.export
      new file mode 100644
      --- /dev/null
     -+++ b/t/t7910/history.export
     ++++ b/t/t3206/history.export
      @@
      +blob
      +mark :1
 12:  ba4791918 ! 12:  7273cc647 branch-diff: use color for the commit pairs
     @@ -1,30 +1,28 @@
      Author: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -    branch-diff: use color for the commit pairs
     +    range-diff: use color for the commit pairs
      
     -    Arguably the most important part of branch-diff's output is the list of
     -    commits in the two branches, together with their relationships.
     +    Arguably the most important part of `git range-diff`'s output is the
     +    list of commits in the two branches, together with their relationships.
      
          For that reason, tbdiff introduced color-coding that is pretty
          intuitive, especially for unchanged patches (all dim yellow, like the
          first line in `git show`'s output) vs modified patches (old commit is
          red, new commit is green). Let's imitate that color scheme.
      
     -    While at it, also copy tbdiff's change of the fragment color to magenta.
     -
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
     ---- a/builtin/branch-diff.c
     -+++ b/builtin/branch-diff.c
     +diff --git a/range-diff.c b/range-diff.c
     +--- a/range-diff.c
     ++++ b/range-diff.c
      @@
     - 	return res;
     + 	free(b2a);
       }
       
      -static void output_pair_header(struct strbuf *buf,
      +static void output_pair_header(struct diff_options *diffopt, struct strbuf *buf,
     - 			       int i, struct patch_util *a_util,
     - 			       int j, struct patch_util *b_util)
     + 			       struct patch_util *a_util,
     + 			       struct patch_util *b_util)
       {
       	static char *dashes;
       	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
     @@ -42,10 +40,10 @@
       			*p = '-';
       	}
       
     -+	if (j < 0) {
     ++	if (!b_util) {
      +		color = color_old;
      +		status = '<';
     -+	} else if (i < 0) {
     ++	} else if (!a_util) {
      +		color = color_new;
      +		status = '>';
      +	} else if (strcmp(a_util->patch, b_util->patch)) {
     @@ -58,15 +56,15 @@
      +
       	strbuf_reset(buf);
      +	strbuf_addstr(buf, status == '!' ? color_old : color);
     - 	if (i < 0)
     + 	if (!a_util)
       		strbuf_addf(buf, "-:  %s ", dashes);
       	else
     - 		strbuf_addf(buf, "%d:  %s ", i + 1,
     + 		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
       			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
       
     --	if (i < 0)
     +-	if (!a_util)
      -		strbuf_addch(buf, '>');
     --	else if (j < 0)
     +-	else if (!b_util)
      -		strbuf_addch(buf, '<');
      -	else if (strcmp(a_util->patch, b_util->patch))
      -		strbuf_addch(buf, '!');
     @@ -78,7 +76,7 @@
      +	if (status == '!')
      +		strbuf_addf(buf, "%s%s", color_reset, color_new);
       
     - 	if (j < 0)
     + 	if (!b_util)
       		strbuf_addf(buf, " -:  %s", dashes);
      @@
       		const char *commit_buffer = get_commit_buffer(commit, NULL);
     @@ -101,33 +99,24 @@
       
       		/* Show unmatched LHS commit whose predecessors were shown. */
       		if (i < a->nr && a_util->matching < 0) {
     --			output_pair_header(&buf, i, a_util, -1, NULL);
     -+			output_pair_header(diffopt, &buf, i, a_util, -1, NULL);
     +-			output_pair_header(&buf, a_util, NULL);
     ++			output_pair_header(diffopt, &buf, a_util, NULL);
       			i++;
       			continue;
       		}
       
       		/* Show unmatched RHS commits. */
       		while (j < b->nr && b_util->matching < 0) {
     --			output_pair_header(&buf, -1, NULL, j, b_util);
     -+			output_pair_header(diffopt, &buf, -1, NULL, j, b_util);
     +-			output_pair_header(&buf, NULL, b_util);
     ++			output_pair_header(diffopt, &buf, NULL, b_util);
       			b_util = ++j < b->nr ? b->items[j].util : NULL;
       		}
       
       		/* Show matching LHS/RHS pair. */
       		if (j < b->nr) {
       			a_util = a->items[b_util->matching].util;
     --			output_pair_header(&buf,
     -+			output_pair_header(diffopt, &buf,
     - 					   b_util->matching, a_util, j, b_util);
     +-			output_pair_header(&buf, a_util, b_util);
     ++			output_pair_header(diffopt, &buf, a_util, b_util);
       			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
       				patch_diff(a->items[b_util->matching].string,
     -@@
     - 	struct string_list branch1 = STRING_LIST_INIT_DUP;
     - 	struct string_list branch2 = STRING_LIST_INIT_DUP;
     - 
     -+	git_diff_basic_config("diff.color.frag", "magenta", NULL);
     -+
     - 	diff_setup(&diffopt);
     - 	diffopt.output_format = DIFF_FORMAT_PATCH;
     - 	diffopt.flags.suppress_diff_headers = 1;
     + 					   b->items[j].string, diffopt);
 13:  1ebbe3595 <  -:  --------- color: provide inverted colors, too
  -:  --------- > 13:  96a3073fb color: add the meta color GIT_COLOR_REVERSE
 14:  ae0ea5dfc ! 14:  6be4baf60 diff: add an internal option to dual-color diffs of diffs
     @@ -14,7 +14,8 @@
          now.
      
          This is a feature that was invented by git-tbdiff, and it will be used
     -    in `branch-diff` in the next commit.
     +    by `git range-diff` in the next commit, by offering it via a new option:
     +    `--dual-color`.
      
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     @@ -22,26 +23,15 @@
      --- a/diff.c
      +++ b/diff.c
      @@
     - 	GIT_COLOR_BOLD_YELLOW,	/* NEW_MOVED ALTERNATIVE */
     - 	GIT_COLOR_FAINT,	/* NEW_MOVED_DIM */
     - 	GIT_COLOR_FAINT_ITALIC,	/* NEW_MOVED_ALTERNATIVE_DIM */
     -+	GIT_COLOR_INV_RED,	/* OLD_INV */
     -+	GIT_COLOR_INV_GREEN,	/* NEW_INV */
     - };
     - 
     - static NORETURN void die_want_option(const char *option_name)
     -@@
     - 		return DIFF_FILE_NEW_MOVED_DIM;
     - 	if (!strcasecmp(var, "newmovedalternativedimmed"))
     - 		return DIFF_FILE_NEW_MOVED_ALT_DIM;
     -+	if (!strcasecmp(var, "oldinv"))
     -+		return DIFF_FILE_OLD_INV;
     -+	if (!strcasecmp(var, "newinv"))
     -+		return DIFF_FILE_NEW_INV;
     - 	return -1;
     + 	ecbdata->blank_at_eof_in_postimage = (at - l2) + 1;
       }
       
     -@@
     +-static void emit_line_0(struct diff_options *o, const char *set, const char *reset,
     ++static void emit_line_0(struct diff_options *o,
     ++			const char *set, unsigned reverse, const char *reset,
     + 			int first, const char *line, int len)
     + {
     + 	int has_trailing_newline, has_trailing_carriage_return;
       	int nofirst;
       	FILE *file = o->file;
       
     @@ -54,8 +44,11 @@
       	if (len == 0) {
       		has_trailing_newline = (first == '\n');
      @@
     + 	}
       
       	if (len || !nofirst) {
     ++		if (reverse && want_color(o->use_color))
     ++			fputs(GIT_COLOR_REVERSE, file);
       		fputs(set, file);
      -		if (!nofirst)
      +		if (first && !nofirst)
     @@ -63,6 +56,15 @@
       		fwrite(line, len, 1, file);
       		fputs(reset, file);
      @@
     + static void emit_line(struct diff_options *o, const char *set, const char *reset,
     + 		      const char *line, int len)
     + {
     +-	emit_line_0(o, set, reset, line[0], line+1, len-1);
     ++	emit_line_0(o, set, 0, reset, line[0], line+1, len-1);
     + }
     + 
     + enum diff_symbol {
     +@@
       
       static void emit_line_ws_markup(struct diff_options *o,
       				const char *set, const char *reset,
     @@ -77,20 +79,24 @@
       	}
       
      -	if (!ws)
     -+	if (!ws && set_sign == set)
     - 		emit_line_0(o, set, reset, sign, line, len);
     +-		emit_line_0(o, set, reset, sign, line, len);
      -	else if (blank_at_eof)
     ++	if (!ws && !set_sign)
     ++		emit_line_0(o, set, 0, reset, sign, line, len);
      +	else if (!ws) {
      +		/* Emit just the prefix, then the rest. */
     -+		emit_line_0(o, set_sign, reset, sign, "", 0);
     -+		emit_line_0(o, set, reset, 0, line, len);
     ++		emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
     ++			    sign, "", 0);
     ++		emit_line_0(o, set, 0, reset, 0, line, len);
      +	} else if (blank_at_eof)
       		/* Blank line at EOF - paint '+' as well */
     - 		emit_line_0(o, ws, reset, sign, line, len);
     +-		emit_line_0(o, ws, reset, sign, line, len);
     ++		emit_line_0(o, ws, 0, reset, sign, line, len);
       	else {
       		/* Emit just the prefix, then the rest. */
      -		emit_line_0(o, set, reset, sign, "", 0);
     -+		emit_line_0(o, set_sign, reset, sign, "", 0);
     ++		emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
     ++			    sign, "", 0);
       		ws_check_emit(line, len, ws_rule,
       			      o->file, set, reset, ws);
       	}
     @@ -103,17 +109,28 @@
       	struct strbuf sb = STRBUF_INIT;
       
       	enum diff_symbol s = eds->s;
     +@@
     + 		context = diff_get_color_opt(o, DIFF_CONTEXT);
     + 		reset = diff_get_color_opt(o, DIFF_RESET);
     + 		putc('\n', o->file);
     +-		emit_line_0(o, context, reset, '\\',
     ++		emit_line_0(o, context, 0, reset, '\\',
     + 			    nneof, strlen(nneof));
     + 		break;
     + 	case DIFF_SYMBOL_SUBMODULE_HEADER:
      @@
       	case DIFF_SYMBOL_CONTEXT:
       		set = diff_get_color_opt(o, DIFF_CONTEXT);
       		reset = diff_get_color_opt(o, DIFF_RESET);
      -		emit_line_ws_markup(o, set, reset, line, len, ' ',
     -+		set_sign = set;
     ++		set_sign = NULL;
      +		if (o->flags.dual_color_diffed_diffs) {
      +			char c = !len ? 0 : line[0];
      +
      +			if (c == '+')
      +				set = diff_get_color_opt(o, DIFF_FILE_NEW);
     ++			else if (c == '@')
     ++				set = diff_get_color_opt(o, DIFF_FRAGINFO);
      +			else if (c == '-')
      +				set = diff_get_color_opt(o, DIFF_FILE_OLD);
      +		}
     @@ -127,13 +144,15 @@
       		reset = diff_get_color_opt(o, DIFF_RESET);
      -		emit_line_ws_markup(o, set, reset, line, len, '+',
      +		if (!o->flags.dual_color_diffed_diffs)
     -+			set_sign = set;
     ++			set_sign = NULL;
      +		else {
      +			char c = !len ? 0 : line[0];
      +
     -+			set_sign = diff_get_color_opt(o, DIFF_FILE_NEW_INV);
     ++			set_sign = set;
      +			if (c == '-')
      +				set = diff_get_color_opt(o, DIFF_FILE_OLD);
     ++			else if (c == '@')
     ++				set = diff_get_color_opt(o, DIFF_FRAGINFO);
      +			else if (c != '+')
      +				set = diff_get_color_opt(o, DIFF_CONTEXT);
      +		}
     @@ -147,13 +166,15 @@
       		reset = diff_get_color_opt(o, DIFF_RESET);
      -		emit_line_ws_markup(o, set, reset, line, len, '-',
      +		if (!o->flags.dual_color_diffed_diffs)
     -+			set_sign = set;
     ++			set_sign = NULL;
      +		else {
      +			char c = !len ? 0 : line[0];
      +
     -+			set_sign = diff_get_color_opt(o, DIFF_FILE_OLD_INV);
     ++			set_sign = set;
      +			if (c == '+')
      +				set = diff_get_color_opt(o, DIFF_FILE_NEW);
     ++			else if (c == '@')
     ++				set = diff_get_color_opt(o, DIFF_FRAGINFO);
      +			else if (c != '-')
      +				set = diff_get_color_opt(o, DIFF_CONTEXT);
      +		}
     @@ -161,6 +182,23 @@
       				    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);
       		break;
       	case DIFF_SYMBOL_WORDS_PORCELAIN:
     +@@
     + 	const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
     + 	const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);
     + 	const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
     ++	const char *reverse = ecbdata->color_diff ? GIT_COLOR_REVERSE : "";
     + 	static const char atat[2] = { '@', '@' };
     + 	const char *cp, *ep;
     + 	struct strbuf msgbuf = STRBUF_INIT;
     +@@
     + 	ep += 2; /* skip over @@ */
     + 
     + 	/* The hunk header in fraginfo color */
     ++	if (ecbdata->opt->flags.dual_color_diffed_diffs)
     ++		strbuf_addstr(&msgbuf, reverse);
     + 	strbuf_addstr(&msgbuf, frag);
     + 	strbuf_add(&msgbuf, line, ep - line);
     + 	strbuf_addstr(&msgbuf, reset);
      
      diff --git a/diff.h b/diff.h
      --- a/diff.h
     @@ -173,14 +211,3 @@
       };
       
       static inline void diff_flags_or(struct diff_flags *a,
     -@@
     - 	DIFF_FILE_NEW_MOVED = 13,
     - 	DIFF_FILE_NEW_MOVED_ALT = 14,
     - 	DIFF_FILE_NEW_MOVED_DIM = 15,
     --	DIFF_FILE_NEW_MOVED_ALT_DIM = 16
     -+	DIFF_FILE_NEW_MOVED_ALT_DIM = 16,
     -+	DIFF_FILE_OLD_INV = 17,
     -+	DIFF_FILE_NEW_INV = 18
     - };
     - const char *diff_get_color(int diff_use_color, enum color_diff ix);
     - #define diff_get_color_opt(o, ix) \
 15:  b9be01705 ! 15:  02e13c0c6 branch-diff: offer to dual-color the diffs
     @@ -1,6 +1,6 @@
      Author: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -    branch-diff: offer to dual-color the diffs
     +    range-diff: offer to dual-color the diffs
      
          When showing what changed between old and new commits, we show a diff of
          the patches. This diff is a diff between diffs, therefore there are
     @@ -13,21 +13,22 @@
      
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -diff --git a/builtin/branch-diff.c b/builtin/branch-diff.c
     ---- a/builtin/branch-diff.c
     -+++ b/builtin/branch-diff.c
     +diff --git a/builtin/range-diff.c b/builtin/range-diff.c
     +--- a/builtin/range-diff.c
     ++++ b/builtin/range-diff.c
      @@
       {
     + 	int creation_factor = 60;
       	struct diff_options diffopt = { NULL };
     - 	struct strbuf four_spaces = STRBUF_INIT;
      +	int dual_color = 0;
     - 	double creation_weight = 0.6;
       	struct option options[] = {
     + 		OPT_INTEGER(0, "creation-factor", &creation_factor,
     + 			    N_("Percentage by which creation is weighted")),
      +		OPT_BOOL(0, "dual-color", &dual_color,
      +			    N_("color both diff and diff-between-diffs")),
     - 		OPT_SET_INT(0, "no-patches", &diffopt.output_format,
     - 			    N_("short format (no diffs)"),
     - 			    DIFF_FORMAT_NO_OUTPUT),
     + 		OPT_END()
     + 	};
     + 	int i, j, res = 0;
      @@
       	argc = j;
       	diff_setup_done(&diffopt);
 16:  b99ab186c ! 16:  dfa7b1e71 branch-diff --dual-color: work around bogus white-space warning
     @@ -1,6 +1,6 @@
      Author: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -    branch-diff --dual-color: work around bogus white-space warning
     +    range-diff --dual-color: work around bogus white-space warning
      
          When displaying a diff of diffs, it is possible that there is an outer
          `+` before a context line. That happens when the context changed between
     @@ -20,8 +20,9 @@
          However, the proper fix would be relatively ugly and intrusive because
          it would have to *weaken* the WS_SPACE_BEFORE_TAB option in ws.c.
          Besides, we do not expose the --dual-color option in cases other than
     -    the `branch-diff` command, which only uses a hard-coded output_prefix of
     -    four spaces (which misses the problem by one column ;-)).
     +    the `git range-diff` command, which only uses a hard-coded
     +    output_prefix of four spaces (which misses the problem by one
     +    column... ;-)).
      
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     @@ -29,7 +30,7 @@
      --- a/diff.c
      +++ b/diff.c
      @@
     - 				set = diff_get_color_opt(o, DIFF_FILE_OLD);
     + 				set = diff_get_color_opt(o, DIFF_FRAGINFO);
       			else if (c != '+')
       				set = diff_get_color_opt(o, DIFF_CONTEXT);
      +			/* Avoid space-before-tab warning */
 17:  950c75377 ! 17:  799da25ef branch-diff: add a man page
     @@ -1,29 +1,30 @@
      Author: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -    branch-diff: add a man page
     +    range-diff: add a man page
      
     -    This is a heavily butchered version of the README written by Thomas
     -    Rast and Thomas Gummerer, lifted from https://github.com/trast/tbdiff.
     +    The bulk of this patch consists of a heavily butchered version of
     +    tbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from
     +    https://github.com/trast/tbdiff.
      
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -diff --git a/Documentation/git-branch-diff.txt b/Documentation/git-branch-diff.txt
     +diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
      new file mode 100644
      --- /dev/null
     -+++ b/Documentation/git-branch-diff.txt
     ++++ b/Documentation/git-range-diff.txt
      @@
     -+git-branch-diff(1)
     ++git-range-diff(1)
      +==================
      +
      +NAME
      +----
     -+git-branch-diff - Compare two versions of a branch
     ++git-range-diff - Compare two commit ranges (e.g. two versions of a branch)
      +
      +SYNOPSIS
      +--------
      +[verse]
     -+'git branch-diff' [--color=[<when>]] [--no-color] [<diff-options>]
     -+	[--dual-color] [--no-patches] [--creation-weight=<weight>]
     ++'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]
     ++	[--dual-color] [--creation-factor=<factor>]
      +	( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
      +
      +DESCRIPTION
     @@ -45,23 +46,19 @@
      +
      +OPTIONS
      +-------
     -+--no-patches::
     -+	Suppress the diffs between commit pairs that were deemed to
     -+	correspond; only show the pairings.
     -+
      +--dual-color::
      +	When the commit diffs differ, recreate the original diffs'
      +	coloring, and add outer -/+ diff markers with the *background*
      +	being red/green to make it easier to see e.g. when there was a
      +	change in what exact lines were added.
      +
     -+--creation-weight=<factor>::
     -+	Set the creation/deletion cost fudge factor to `<factor>`.
     -+	Defaults to 0.6. Try a larger value if `git branch-diff`
     -+	erroneously considers a large change a total rewrite (deletion
     -+	of one commit and addition of another), and a smaller one in
     -+	the reverse case. See the ``Algorithm`` section below for an
     -+	explanation why this is needed.
     ++--creation-factor=<percent>::
     ++	Set the creation/deletion cost fudge factor to `<percent>`.
     ++	Defaults to 60. Try a larger value if `git range-diff` erroneously
     ++	considers a large change a total rewrite (deletion of one commit
     ++	and addition of another), and a smaller one in the reverse case.
     ++	See the ``Algorithm`` section below for an explanation why this is
     ++	needed.
      +
      +<range1> <range2>::
      +	Compare the commits specified by the two ranges, where
     @@ -74,10 +71,10 @@
      +	Equivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.
      +	Note that `<base>` does not need to be the exact branch point
      +	of the branches. Example: after rebasing a branch `my-topic`,
     -+	`git branch-diff my-topic@{u} my-topic@{1} my-topic` would
     ++	`git range-diff my-topic@{u} my-topic@{1} my-topic` would
      +	show the differences introduced by the rebase.
      +
     -+`git branch-diff` also accepts the regular diff options (see
     ++`git range-diff` also accepts the regular diff options (see
      +linkgit:git-diff[1]), most notably the `--color=[<when>]` and
      +`--no-color` options. These options are used when generating the "diff
      +between patches", i.e. to compare the author, commit message and diff of
     @@ -87,23 +84,23 @@
      +
      +CONFIGURATION
      +-------------
     -+This command uses the `diff.color.*` and `pager.branch-diff` settings
     ++This command uses the `diff.color.*` and `pager.range-diff` settings
      +(the latter is on by default).
      +See linkgit:git-config[1].
      +
      +
     -+Examples
     ++EXAMPLES
      +--------
      +
      +When a rebase required merge conflicts to be resolved, compare the changes
      +introduced by the rebase directly afterwards using:
      +
      +------------
     -+$ git branch-diff @{u} @{1} @
     ++$ git range-diff @{u} @{1} @
      +------------
      +
      +
     -+A typical output of `git branch-diff` would look like this:
     ++A typical output of `git range-diff` would look like this:
      +
      +------------
      +-:  ------- > 1:  0ddba11 Prepare for the inevitable!
     @@ -216,11 +213,11 @@
      +------------
      +
      +The cost of an edge `o--C` is the size of `C`'s diff, modified by a
     -+fudge factor that should be smaller than 1.0. The cost of an edge `o--o`
     -+is free. The fudge factor is necessary because even if `1` and `C` have
     -+nothing in common, they may still share a few empty lines and such,
     -+possibly making the assignment `1--C`, `o--o` slightly cheaper than
     -+`1--o`, `o--C` even if `1` and `C` have nothing in common. With the
     ++fudge factor that should be smaller than 100%. The cost of an edge
     ++`o--o` is free. The fudge factor is necessary because even if `1` and
     ++`C` have nothing in common, they may still share a few empty lines and
     ++such, possibly making the assignment `1--C`, `o--o` slightly cheaper
     ++than `1--o`, `o--C` even if `1` and `C` have nothing in common. With the
      +fudge factor we require a much larger common part to consider patches as
      +corresponding.
      +
 18:  71698f118 <  -:  --------- completion: support branch-diff
  -:  --------- > 18:  d05b54c60 completion: support `git range-diff`
  -:  --------- > 19:  144363006 range-diff: left-pad patch numbers
  -:  --------- > 20:  4a68b95ce range-diff: make --dual-color the default mode

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-04-30 21:54     ` [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems Johannes Schindelin via GitGitGadget
@ 2018-07-06 22:43       ` Junio C Hamano
  2018-07-07 11:34         ` Johannes Schindelin
  2018-07-11 10:07       ` SZEDER Gábor
  1 sibling, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-07-06 22:43 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget; +Cc: git, Johannes Schindelin

"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:

> From: Johannes Schindelin <johannes.schindelin@gmx.de>
>
> The problem solved by the code introduced in this commit goes like this:
> given two sets of items, and a cost matrix which says how much it
> "costs" to assign any given item of the first set to any given item of
> the second, assign all items (except when the sets have different size)
> in the cheapest way.
>
> We use the Jonker-Volgenant algorithm to solve the assignment problem to
> answer questions such as: given two different versions of a topic branch
> (or iterations of a patch series), what is the best pairing of
> commits/patches between the different versions?
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---

Does the "gitgitgadget" thing lie on the Date: e-mail header?

Postdating the patch with in-body header is fine, but mailbox tools
often use and trust the Date: timestamp when sorting and finding
messages etc. so sending a new patch to add linear-assignment.c that
is different from what was added 9 weeks ago with "Date: Mon, 30 Apr
2018" header can easily cause me to miss that message when I look
for things that happened within the past few weeks, for example.


^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 18/20] completion: support `git range-diff`
  2018-05-03 14:44     ` [PATCH v3 18/20] completion: support `git range-diff` Johannes Schindelin via GitGitGadget
@ 2018-07-06 22:46       ` Junio C Hamano
  2018-07-07 11:38         ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-07-06 22:46 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget; +Cc: git, Johannes Schindelin

"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:

> From: Johannes Schindelin <johannes.schindelin@gmx.de>
>
> Tab completion of `git range-diff` is very convenient, especially
> given that the revision arguments to specify the commit ranges to
> compare are typically more complex than, say, your grandfather's `git
> log` arguments.
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>

Have three-dash lines here, or perhaps have some validation hook in
the garden-shears tool to notice these leftoer bits we see below?

>
> squash! WIP completion: support `git range-diff`
>
> Revert "WIP completion: support `git range-diff`"
>
> This reverts commit 2e7af652af9e53a19fd947f8ebe37a78043afa49.
> ---
>  contrib/completion/git-completion.bash | 14 ++++++++++++++
>  1 file changed, 14 insertions(+)
>
> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
> index 94c95516e..402490673 100644
> --- a/contrib/completion/git-completion.bash
> +++ b/contrib/completion/git-completion.bash
> @@ -1976,6 +1976,20 @@ _git_push ()
>  	__git_complete_remote_or_refspec
>  }
>  
> +_git_range_diff ()
> +{
> +  case "$cur" in
> +  --*)
> +          __gitcomp "
> +	  	--creation-factor= --dual-color
> +                  $__git_diff_common_options
> +                  "
> +          return
> +          ;;
> +  esac
> +  __git_complete_revlist
> +}
> +
>  _git_rebase ()
>  {
>  	__git_find_repo_path

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-06 22:43       ` Junio C Hamano
@ 2018-07-07 11:34         ` Johannes Schindelin
  2018-07-07 16:34           ` Junio C Hamano
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-07 11:34 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Schindelin via GitGitGadget, git

Hi Junio,

On Fri, 6 Jul 2018, Junio C Hamano wrote:

> "Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
> writes:
> 
> > From: Johannes Schindelin <johannes.schindelin@gmx.de>
> >
> > The problem solved by the code introduced in this commit goes like this:
> > given two sets of items, and a cost matrix which says how much it
> > "costs" to assign any given item of the first set to any given item of
> > the second, assign all items (except when the sets have different size)
> > in the cheapest way.
> >
> > We use the Jonker-Volgenant algorithm to solve the assignment problem to
> > answer questions such as: given two different versions of a topic branch
> > (or iterations of a patch series), what is the best pairing of
> > commits/patches between the different versions?
> >
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> 
> Does the "gitgitgadget" thing lie on the Date: e-mail header?

No, GitGitGadget takes the literal output from `git format-patch`, as far
as I can tell. So if at all, it is `format-patch` that is lying.

You can compare the mail's date to the commit date:

https://public-inbox.org/git/39272eefcfe66de3ca1aa2ee43d6626ce558caae.1530617166.git.gitgitgadget@gmail.com/
https://github.com/dscho/git/commit/39272eefcfe66de3ca1aa2ee43d6626ce558caae

(the nice thing about GitGitGadget is that you can rely on its mails to
reflect *precisely* what the commit is like, the user does not have any
opportunity to interfere with the code that generates the mails:
https://github.com/gitgitgadget/gitgitgadget/blob/c4805370f/lib/patch-series.ts#L605-L611).

> Postdating the patch with in-body header is fine, but mailbox tools
> often use and trust the Date: timestamp when sorting and finding
> messages etc. so sending a new patch to add linear-assignment.c that
> is different from what was added 9 weeks ago with "Date: Mon, 30 Apr
> 2018" header can easily cause me to miss that message when I look
> for things that happened within the past few weeks, for example.

Well, isn't it too bad that we use emails to transport commits, then.

Seriously, I have very little sympathy here, as all I am doing is to
automate *the suggested usage* of `git format-patch` and `git send-email`
(the latter of which I cannot even use due to its limitations).

So if you want to see this "fixed", you should think how you want to see
`git format-patch` fixed.

Or maybe you want to write a script that re-orders the patches on top of
the cover letter according to the `[PATCH M/N]` order, to reinstate the
order of the original commits that got somewhat lost via emailing them.

Of course, you could also save yourself a lot of trouble and use Git:

	git fetch https://github.com/gitgitgadget/git \
		pr-1/dscho/branch-diff-v3
	git cherry-pick -s ..FETCH_HEAD

(This is assuming that you insist, as you did in the past, on changing the
base commit from what the original author chose. If you are fine with my
choice, which is the current `master`, then you could save yourself *even
more* trouble by just pulling my branch, and merely signing off on the
merge commit. Which would be totes okay with me.)


Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 18/20] completion: support `git range-diff`
  2018-07-06 22:46       ` Junio C Hamano
@ 2018-07-07 11:38         ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-07 11:38 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Schindelin via GitGitGadget, git

Hi Junio,

On Fri, 6 Jul 2018, Junio C Hamano wrote:

> "Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
> writes:
> 
> > From: Johannes Schindelin <johannes.schindelin@gmx.de>
> >
> > Tab completion of `git range-diff` is very convenient, especially
> > given that the revision arguments to specify the commit ranges to
> > compare are typically more complex than, say, your grandfather's `git
> > log` arguments.
> >
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> 
> Have three-dash lines here, or perhaps have some validation hook in
> the garden-shears tool to notice these leftoer bits we see below?

This is just a simple case of me overlooking the `squash!` while rebasing.
The shears were not involved, as it is not a branch thicket, it is a
simple, linear branch.

I guess I could install some sort of post-rewrite hook, but then, I'd
rather have this as a more generally useful feature, directly in `rebase
-i`: if running in autosquash mode, when offering to edit squashed commit
messages, `git rebase -i` should refuse by default to accept a commit
message that contains a line that starts with `squash! `.

Would make for a nice micro-project, methinks.

> > squash! WIP completion: support `git range-diff`
> >
> > Revert "WIP completion: support `git range-diff`"
> >
> > This reverts commit 2e7af652af9e53a19fd947f8ebe37a78043afa49.
> > ---

I will fix this in my branch, of course, and force-push, but will wait
with sending out a new revision of the patch series in case more reviews
roll in.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-07 11:34         ` Johannes Schindelin
@ 2018-07-07 16:34           ` Junio C Hamano
  2018-07-07 19:27             ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-07-07 16:34 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Johannes Schindelin via GitGitGadget, git

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

>> Does the "gitgitgadget" thing lie on the Date: e-mail header?
>
> No, GitGitGadget takes the literal output from `git format-patch`, as far
> as I can tell. So if at all, it is `format-patch` that is lying.

format-patch faithfully records the fact about the commit that is
made into the patch.  How pieces of information should (or should
not) be used depends on the purpose of the application that uses
its output.

I'd suggest to match what send-email does, which is to notice but
use the current date when adding a Date: header.  An option to lie
to SMTP servers may be OK but I do not think we want to encourage
such a behaviour by making it the default.

What is missing in the core-git tools is an ability to tell
send-email to optionaly add an in-body header to record the author
date of the original.  We add an in-body header that records the
real author when it is different from the sender automatically, and
it is OK to have an option to allow doing so (but not encouraged
around here---it is easier to reason about the resulting history for
everybody, perhaps other than the original author, to record the
first time you show the change to the public as the author time).



^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-07 16:34           ` Junio C Hamano
@ 2018-07-07 19:27             ` Johannes Schindelin
  2018-07-07 22:23               ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-07 19:27 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Schindelin via GitGitGadget, git

Hi Junio,

On Sat, 7 Jul 2018, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> >> Does the "gitgitgadget" thing lie on the Date: e-mail header?
> >
> > No, GitGitGadget takes the literal output from `git format-patch`, as far
> > as I can tell. So if at all, it is `format-patch` that is lying.
> 
> format-patch faithfully records the fact about the commit that is
> made into the patch.  How pieces of information should (or should
> not) be used depends on the purpose of the application that uses
> its output.

I guess this is one of the fallouts for abusing the `format-patch|am`
dance for `rebase--am`.

> I'd suggest to match what send-email does, which is to notice but
> use the current date when adding a Date: header.  An option to lie
> to SMTP servers may be OK but I do not think we want to encourage
> such a behaviour by making it the default.

I opened a PR to add a TODO:

	https://github.com/gitgitgadget/gitgitgadget/pull/15

> What is missing in the core-git tools is an ability to tell
> send-email to optionaly add an in-body header to record the author
> date of the original.  We add an in-body header that records the
> real author when it is different from the sender automatically, and
> it is OK to have an option to allow doing so (but not encouraged
> around here---it is easier to reason about the resulting history for
> everybody, perhaps other than the original author, to record the
> first time you show the change to the public as the author time).

Pull Request-based workflows keep the original author date all the time.
If that is not desired, we need to do more than paper over it by adjusting
`send-email`.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-07 19:27             ` Johannes Schindelin
@ 2018-07-07 22:23               ` Johannes Schindelin
  2018-07-09 22:08                 ` refs/notes/amlog problems, was " Johannes Schindelin
  2018-07-09 22:23                 ` Junio C Hamano
  0 siblings, 2 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-07 22:23 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Schindelin via GitGitGadget, git

Hi Junio,

On Sat, 7 Jul 2018, Johannes Schindelin wrote:

> On Sat, 7 Jul 2018, Junio C Hamano wrote:
> 
> > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> > 
> > >> Does the "gitgitgadget" thing lie on the Date: e-mail header?
> > >
> > > No, GitGitGadget takes the literal output from `git format-patch`, as far
> > > as I can tell. So if at all, it is `format-patch` that is lying.
> > 
> > format-patch faithfully records the fact about the commit that is
> > made into the patch.  How pieces of information should (or should
> > not) be used depends on the purpose of the application that uses
> > its output.
> 
> I guess this is one of the fallouts for abusing the `format-patch|am`
> dance for `rebase--am`.

Speaking of GitGitGadget: I just encoutered a problem with your
`refs/notes/amlog` and I hope you can help me with that.

Concretely, I want GitGitGadget to be able to identify the commit that
corresponds to a given mail that contained a patch (if it ever made it
into `pu`), to automate all kinds of tedious things that I currently have
to perform manually.

And here I hit a block: I am looking for the commit corresponding to
aca087479b35cbcbd7c84c7ca3bcf556133d0548.1530274571.git.gitgitgadget@gmail.com

When I ask `git notes --ref=refs/notes/gitster-amlog show
4cec3986f017d84c8d6a2c4233d2eba4a3ffa60d` (the SHA-1 is the one
corresponding to `Message-Id: <...>` for that mail), it insists on
outputting

	5902152ab02291af4454f24a8ccaf2adddefc306

However, I cannot find that commit anywhere.

When I look for the commit in the same manual, tedious way that I want to
automate, I find that it *is* in `pu`, but as

	5cf8e064747be2026bb23be37f84f2f0b2a31781

Even curiouser: when I now ask for the commit notes for both of those
SHA-1s, I get back the correct, same Message-Id *for both of them*, which
makes me think that it was recorded correctly, but then overwritten due to
some process I don't understand.

Would you be able to shed light into this?

Thank you,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 17/20] range-diff: add a man page
  2018-05-03 13:50     ` [PATCH v3 17/20] range-diff: add a man page Johannes Schindelin via GitGitGadget
@ 2018-07-09 18:20       ` Stefan Beller
  2018-07-09 20:00         ` Johannes Schindelin
  2018-07-16  8:01       ` Eric Sunshine
  1 sibling, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-07-09 18:20 UTC (permalink / raw)
  To: gitgitgadget; +Cc: git, Junio C Hamano, Johannes Schindelin

On Tue, Jul 3, 2018 at 4:26 AM Johannes Schindelin via GitGitGadget
<gitgitgadget@gmail.com> wrote:

> +'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]
> +       [--dual-color] [--creation-factor=<factor>]
> +       ( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
> +
> +DESCRIPTION
> +-----------
> +
> +This command shows the differences between two versions of a patch
> +series, or more generally, two commit ranges (ignoring merges).

Does it completely ignore merges or does it die("not supported"), how is the
user expected to cope with the accidental merge in the given range?

> +To that end, it first finds pairs of commits from both commit ranges
> +that correspond with each other. Two commits are said to correspond when
> +the diff between their patches (i.e. the author information, the commit
> +message and the commit diff) is reasonably small compared to the
> +patches' size. See ``Algorithm` below for details.
> +
> +Finally, the list of matching commits is shown in the order of the
> +second commit range, with unmatched commits being inserted just after
> +all of their ancestors have been shown.
> +
> +
> +OPTIONS
> +-------
> +--dual-color::
> +       When the commit diffs differ, recreate the original diffs'
> +       coloring, and add outer -/+ diff markers with the *background*
> +       being red/green to make it easier to see e.g. when there was a
> +       change in what exact lines were added.

I presume this is a boolean option, and can be turned off with
--no-dual-color, but not with --dual-color=no. Would it be worth to
give the --no-option here as well.
The more pressing question I had when reading this, is whether this
is the default.

> +--creation-factor=<percent>::
> +       Set the creation/deletion cost fudge factor to `<percent>`.
> +       Defaults to 60. Try a larger value if `git range-diff` erroneously
> +       considers a large change a total rewrite (deletion of one commit
> +       and addition of another), and a smaller one in the reverse case.
> +       See the ``Algorithm`` section below for an explanation why this is
> +       needed.
> +
> +<range1> <range2>::
> +       Compare the commits specified by the two ranges, where
> +       `<range1>` is considered an older version of `<range2>`.

Is it really older? How does that help the user?
I think this comes from the notion of e.g. patch 4 ("range-diff: improve the
order of the shown commits "), that assume the user wants the range-diff
to be expressed with range2 as its "base range".

> +<rev1>...<rev2>::
> +       Equivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.

That is cool.

> +Algorithm
> +---------
> +
> +The general idea is this: we generate a cost matrix between the commits
> +in both commit ranges, then solve the least-cost assignment.

Can you say more about the generation of the cost matrix?
I assume that it counts the number of lines added/deleted to make
one patch into the other patch.

If that assumption was correct, an edit of a commit message adding one
line is just as costly as adding one line in the diff.

Further I would assume that the context lines are ignored?

I think this is worth spelling out.

Another spot to look at is further metadata, such as author and author-date,
which are kept the same in a rebase workflow.

Maybe worth noting that this algorithm doesn't pay special attention to these,
but a change in them would be strong signal that the two patches compared are
not the same?

I like the example below, thanks!
Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 14/20] diff: add an internal option to dual-color diffs of diffs
  2018-05-03  0:17     ` [PATCH v3 14/20] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin via GitGitGadget
@ 2018-07-09 19:29       ` Stefan Beller
  2018-07-10 17:45         ` [PATCH 0/2] " Stefan Beller
  0 siblings, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-07-09 19:29 UTC (permalink / raw)
  To: gitgitgadget; +Cc: git, Junio C Hamano, Johannes Schindelin

On Tue, Jul 3, 2018 at 4:27 AM Johannes Schindelin via GitGitGadget
<gitgitgadget@gmail.com> wrote:
>
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
>
> When diffing diffs, it can be quite daunting to figure out what the heck
> is going on, as there are nested +/- signs.
>
> Let's make this easier by adding a flag in diff_options that allows
> color-coding the outer diff sign with inverted colors, so that the
> preimage and postimage is colored like the diff it is.
>
> Of course, this really only makes sense when the preimage and postimage
> *are* diffs. So let's not expose this flag via a command-line option for
> now.
>
> This is a feature that was invented by git-tbdiff, and it will be used
> by `git range-diff` in the next commit, by offering it via a new option:
> `--dual-color`.
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  diff.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++-----------
>  diff.h |  1 +
>  2 files changed, 69 insertions(+), 15 deletions(-)
>
> diff --git a/diff.c b/diff.c
> index 8c568cbe0..26445ffa1 100644
> --- a/diff.c
> +++ b/diff.c
> @@ -562,14 +562,18 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,
>         ecbdata->blank_at_eof_in_postimage = (at - l2) + 1;
>  }
>
> -static void emit_line_0(struct diff_options *o, const char *set, const char *reset,
> +static void emit_line_0(struct diff_options *o,
> +                       const char *set, unsigned reverse, const char *reset,
>                         int first, const char *line, int len)
>  {
>         int has_trailing_newline, has_trailing_carriage_return;
>         int nofirst;
>         FILE *file = o->file;
>
> -       fputs(diff_line_prefix(o), file);
> +       if (first)
> +               fputs(diff_line_prefix(o), file);
> +       else if (!len)
> +               return;

This case is not a problem for empty lines in e.g. "git-log --line-prefix"
because first would contain the LF.

>         if (len == 0) {
>                 has_trailing_newline = (first == '\n');
> @@ -587,8 +591,10 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res
>         }
>
>         if (len || !nofirst) {
> +               if (reverse && want_color(o->use_color))
> +                       fputs(GIT_COLOR_REVERSE, file);

Would it make sense to have the function signature take a char* for reverse
and we pass in diff_get_color(o, GIT_COLOR_REVERSE), that would align
with the set and reset color passed in?

>                 fputs(set, file);
> -               if (!nofirst)
> +               if (first && !nofirst)
>                         fputc(first, file);

'first' is line[0] and comes from user data, so I think this could change
the output of diffs that has lines with the NUL character first in a line
as then that character would be silently eaten?

The 'nofirst' (which is a bad name) is used to detect if we do not
want to double print the first character in case of an empty line.
(Before this series we always had 'first' as a valid character, now we also
have 0 encoded for "do not print anything?"

> @@ -962,7 +968,8 @@ static void dim_moved_lines(struct diff_options *o)
>
>  static void emit_line_ws_markup(struct diff_options *o,
>                                 const char *set, const char *reset,
> -                               const char *line, int len, char sign,
> +                               const char *line, int len,
> +                               const char *set_sign, char sign,
>                                 unsigned ws_rule, int blank_at_eof)
>  {
>         const char *ws = NULL;
> @@ -973,14 +980,20 @@ static void emit_line_ws_markup(struct diff_options *o,
>                         ws = NULL;
>         }
>
> -       if (!ws)
> -               emit_line_0(o, set, reset, sign, line, len);
> -       else if (blank_at_eof)
> +       if (!ws && !set_sign)
> +               emit_line_0(o, set, 0, reset, sign, line, len);
> +       else if (!ws) {
> +               /* Emit just the prefix, then the rest. */
> +               emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
> +                           sign, "", 0);
> +               emit_line_0(o, set, 0, reset, 0, line, len);

(FYI:)
My long term vision for the emit_line_* functions was to have them actually
line oriented, and here we observe that the preimage already breaks
this assumption but just uses it as it sees fit.
I added that wart when refactoring the diff code to use the emit_ functionality
as I wanted to stay backwards compatible.

The actual issue is that each emit_line_0 will encapsulate its content
with its designated color and then end it with a reset. Looking at t4015
a common occurrence is output like:

  <GREEN>+<RESET><GREEN>{<RESET>

which we'd add one more layer to it now when set_sign is set.

I think this is ok for now (I value having this series land over insisting
on the perfect code), but just wanted to note my concern.

Ideally we'd refactor to only call emit_line once per line and when
the set sign (and the newly introduced set_sign) are the same as the
line color we'd avoid the intermediate RESET-and-SAME_COLOR
pattern that the test suite expects a lot currently.

> @@ -990,7 +1003,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
>                                          struct emitted_diff_symbol *eds)
>  {
>         static const char *nneof = " No newline at end of file\n";
> -       const char *context, *reset, *set, *meta, *fraginfo;
> +       const char *context, *reset, *set, *set_sign, *meta, *fraginfo;
>         struct strbuf sb = STRBUF_INIT;
>
>         enum diff_symbol s = eds->s;
> @@ -1003,7 +1016,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
>                 context = diff_get_color_opt(o, DIFF_CONTEXT);
>                 reset = diff_get_color_opt(o, DIFF_RESET);
>                 putc('\n', o->file);
> -               emit_line_0(o, context, reset, '\\',
> +               emit_line_0(o, context, 0, reset, '\\',



> @@ -1030,7 +1043,18 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
>         case DIFF_SYMBOL_CONTEXT:
>                 set = diff_get_color_opt(o, DIFF_CONTEXT);
>                 reset = diff_get_color_opt(o, DIFF_RESET);
> -               emit_line_ws_markup(o, set, reset, line, len, ' ',
> +               set_sign = NULL;
> +               if (o->flags.dual_color_diffed_diffs) {
> +                       char c = !len ? 0 : line[0];
> +
> +                       if (c == '+')
> +                               set = diff_get_color_opt(o, DIFF_FILE_NEW);
> +                       else if (c == '@')
> +                               set = diff_get_color_opt(o, DIFF_FRAGINFO);
> +                       else if (c == '-')
> +                               set = diff_get_color_opt(o, DIFF_FILE_OLD);
> +               }

This hunk is replicated below very similarly/
'set' depends on the initial symbol (the case we're in), and the first character
of line.

Would it make sense to have a function

  diff_get_color_set_sign(struct diffopt *, \
      struct emitted_diff_symbol *)

that takes care of all the computation and doesn't need
repetition in each case? For example @ always maps to DIFF_FRAGINFO,
and +/- have either DIFF_FILE_{OLD, NEW}, with the exception of
(sign == set_sign), when they become CONTEXT AFAICT?

> +               emit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',
>                                     flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);
>                 break;
>         case DIFF_SYMBOL_PLUS:
> @@ -1057,7 +1081,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
>                         set = diff_get_color_opt(o, DIFF_FILE_NEW);
>                 }
>                 reset = diff_get_color_opt(o, DIFF_RESET);
> -               emit_line_ws_markup(o, set, reset, line, len, '+',
> +               if (!o->flags.dual_color_diffed_diffs)
> +                       set_sign = NULL;
> +               else {
> +                       char c = !len ? 0 : line[0];
> +
> +                       set_sign = set;
> +                       if (c == '-')
> +                               set = diff_get_color_opt(o, DIFF_FILE_OLD);
> +                       else if (c == '@')
> +                               set = diff_get_color_opt(o, DIFF_FRAGINFO);
> +                       else if (c != '+')
> +                               set = diff_get_color_opt(o, DIFF_CONTEXT);
> +               }
> +               emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
>                                     flags & DIFF_SYMBOL_CONTENT_WS_MASK,
>                                     flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);
>                 break;
> @@ -1085,7 +1122,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
>                         set = diff_get_color_opt(o, DIFF_FILE_OLD);
>                 }
>                 reset = diff_get_color_opt(o, DIFF_RESET);
> -               emit_line_ws_markup(o, set, reset, line, len, '-',
> +               if (!o->flags.dual_color_diffed_diffs)
> +                       set_sign = NULL;
> +               else {
> +                       char c = !len ? 0 : line[0];
> +
> +                       set_sign = set;
> +                       if (c == '+')
> +                               set = diff_get_color_opt(o, DIFF_FILE_NEW);
> +                       else if (c == '@')
> +                               set = diff_get_color_opt(o, DIFF_FRAGINFO);
> +                       else if (c != '-')
> +                               set = diff_get_color_opt(o, DIFF_CONTEXT);
> +               }
> +               emit_line_ws_markup(o, set, reset, line, len, set_sign, '-',
>                                     flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);
>                 break;
>         case DIFF_SYMBOL_WORDS_PORCELAIN:
> @@ -1276,6 +1326,7 @@ static void emit_hunk_header(struct emit_callback *ecbdata,
>         const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
>         const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);
>         const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
> +       const char *reverse = ecbdata->color_diff ? GIT_COLOR_REVERSE : "";
>         static const char atat[2] = { '@', '@' };
>         const char *cp, *ep;
>         struct strbuf msgbuf = STRBUF_INIT;
> @@ -1296,6 +1347,8 @@ static void emit_hunk_header(struct emit_callback *ecbdata,
>         ep += 2; /* skip over @@ */
>
>         /* The hunk header in fraginfo color */
> +       if (ecbdata->opt->flags.dual_color_diffed_diffs)
> +               strbuf_addstr(&msgbuf, reverse);
>         strbuf_addstr(&msgbuf, frag);
>         strbuf_add(&msgbuf, line, ep - line);
>         strbuf_addstr(&msgbuf, reset);
> diff --git a/diff.h b/diff.h
> index 928f48995..79beb6eea 100644
> --- a/diff.h
> +++ b/diff.h
> @@ -95,6 +95,7 @@ struct diff_flags {
>         unsigned default_follow_renames:1;
>         unsigned stat_with_summary:1;
>         unsigned suppress_diff_headers:1;
> +       unsigned dual_color_diffed_diffs:1;
>  };
>
>  static inline void diff_flags_or(struct diff_flags *a,
> --
> gitgitgadget
>

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 16/20] range-diff --dual-color: work around bogus white-space warning
  2018-05-03  1:11     ` [PATCH v3 16/20] range-diff --dual-color: work around bogus white-space warning Johannes Schindelin via GitGitGadget
@ 2018-07-09 19:34       ` Stefan Beller
  2018-07-09 21:02         ` Junio C Hamano
  0 siblings, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-07-09 19:34 UTC (permalink / raw)
  To: gitgitgadget; +Cc: git, Junio C Hamano, Johannes Schindelin

On Tue, Jul 3, 2018 at 4:26 AM Johannes Schindelin via GitGitGadget
<gitgitgadget@gmail.com> wrote:
>
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
>
> When displaying a diff of diffs, it is possible that there is an outer
> `+` before a context line. That happens when the context changed between
> old and new commit. When that context line starts with a tab (after the
> space that marks it as context line), our diff machinery spits out a
> white-space error (space before tab), but in this case, that is
> incorrect.
>
> Work around this by detecting that situation and simply *not* printing
> the space in that case.

ok. If that is the workaround that you deem to be the right thing for now.
(I do not have an opinion if that is the right approach, or if we'd want
to s/<TAB>/<SPACE>/ for example.)

> This is slightly improper a fix because it is conceivable that an
> output_prefix might be configured with *just* the right length to let
> that tab jump to a different tab stop depending whether we emit that
> space or not.
>
> However, the proper fix would be relatively ugly and intrusive because
> it would have to *weaken* the WS_SPACE_BEFORE_TAB option in ws.c.
> Besides, we do not expose the --dual-color option in cases other than
> the `git range-diff` command, which only uses a hard-coded
> output_prefix of four spaces (which misses the problem by one
> column... ;-)).

That makes sense!

> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  diff.c | 6 ++++++
>  1 file changed, 6 insertions(+)
>
> diff --git a/diff.c b/diff.c
> index 26445ffa1..325007167 100644
> --- a/diff.c
> +++ b/diff.c
> @@ -1093,6 +1093,12 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
>                                 set = diff_get_color_opt(o, DIFF_FRAGINFO);
>                         else if (c != '+')
>                                 set = diff_get_color_opt(o, DIFF_CONTEXT);
> +                       /* Avoid space-before-tab warning */
> +                       if (c == ' ' && (len < 2 || line[1] == '\t' ||
> +                                        line[1] == '\r' || line[1] == '\n')) {
> +                               line++;
> +                               len--;
> +                       }
>                 }

And this is inside the check for 'o->flags.dual_color_diffed_diffs',
so that is protected against other diffs.

Thanks,
Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 17/20] range-diff: add a man page
  2018-07-09 18:20       ` Stefan Beller
@ 2018-07-09 20:00         ` Johannes Schindelin
  2018-07-09 20:25           ` Stefan Beller
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-09 20:00 UTC (permalink / raw)
  To: Stefan Beller; +Cc: gitgitgadget, git, Junio C Hamano

Hi Stefan,

On Mon, 9 Jul 2018, Stefan Beller wrote:

> On Tue, Jul 3, 2018 at 4:26 AM Johannes Schindelin via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
> 
> > +'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]
> > +       [--dual-color] [--creation-factor=<factor>]
> > +       ( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
> > +
> > +DESCRIPTION
> > +-----------
> > +
> > +This command shows the differences between two versions of a patch
> > +series, or more generally, two commit ranges (ignoring merges).
> 
> Does it completely ignore merges or does it die("not supported"), how is
> the user expected to cope with the accidental merge in the given range?

It ignores merges. It does not reject them. It simply ignores them and
won't talk about them as a consequence.

Could you suggest an improved way to say that?

> > +To that end, it first finds pairs of commits from both commit ranges
> > +that correspond with each other. Two commits are said to correspond when
> > +the diff between their patches (i.e. the author information, the commit
> > +message and the commit diff) is reasonably small compared to the
> > +patches' size. See ``Algorithm` below for details.
> > +
> > +Finally, the list of matching commits is shown in the order of the
> > +second commit range, with unmatched commits being inserted just after
> > +all of their ancestors have been shown.
> > +
> > +
> > +OPTIONS
> > +-------
> > +--dual-color::
> > +       When the commit diffs differ, recreate the original diffs'
> > +       coloring, and add outer -/+ diff markers with the *background*
> > +       being red/green to make it easier to see e.g. when there was a
> > +       change in what exact lines were added.
> 
> I presume this is a boolean option, and can be turned off with
> --no-dual-color, but not with --dual-color=no. Would it be worth to
> give the --no-option here as well.
> The more pressing question I had when reading this, is whether this
> is the default.

In the final patch (which I mulled about adding or not for a couple of
weeks), the `--dual-color` mode is the default, and the man page talks
about `--no-dual-color`.

Do you want me to change this intermediate commit, even if that change
will be reverted anyway?

> > +--creation-factor=<percent>::
> > +       Set the creation/deletion cost fudge factor to `<percent>`.
> > +       Defaults to 60. Try a larger value if `git range-diff` erroneously
> > +       considers a large change a total rewrite (deletion of one commit
> > +       and addition of another), and a smaller one in the reverse case.
> > +       See the ``Algorithm`` section below for an explanation why this is
> > +       needed.
> > +
> > +<range1> <range2>::
> > +       Compare the commits specified by the two ranges, where
> > +       `<range1>` is considered an older version of `<range2>`.
> 
> Is it really older? How does that help the user?

It is important to get your ducks in a row, so to speak, when looking at
range-diffs. They are even more unintuitive than diffs, so it makes sense
to have a very clear mental picture of what you are trying to compare
here.

The coloring gives a strong hint of "pre" vs "post", i.e. old vs new: the
changes that are only in the "old" patches are marked with a minus with a
red background color, which only really makes sense if you think about
these changes as "dropped" or "removed" from the "new" changes.

So yes, it is really considered an older version, in my mind.

Again, if you have suggestions how to improve my patch (giving rise to a
"new" patch :-)), let's hear them.

> I think this comes from the notion of e.g. patch 4 ("range-diff: improve the
> order of the shown commits "), that assume the user wants the range-diff
> to be expressed with range2 as its "base range".

No, it is motivated by the fact that we use -/+ markers to indicate
differences between the "old" and the "new" patches.

> > +Algorithm
> > +---------
> > +
> > +The general idea is this: we generate a cost matrix between the commits
> > +in both commit ranges, then solve the least-cost assignment.
> 
> Can you say more about the generation of the cost matrix?
> I assume that it counts the number of lines added/deleted to make
> one patch into the other patch.

I think that is correct.

*reading the patch*

Actually, no, I was wrong. For the cost matrix, the *length* of the diff
*of the diffs* is computed. Think of it as

	git diff --no-index <(git diff A^!) <(git diff B^!) | wc -l

> If that assumption was correct, an edit of a commit message adding one
> line is just as costly as adding one line in the diff.

Nope, editing a commit message does not have any influence on the
algorithm's idea whether the commit matches or not. Only the content
changes associated with the commit have any say over this.

> Further I would assume that the context lines are ignored?

No.

> I think this is worth spelling out.

Sure.

> Another spot to look at is further metadata, such as author and
> author-date, which are kept the same in a rebase workflow.

I encourage you to offer that as an add-on patch series. Because what you
suggest is not necessary for my use cases, so I'd rather not spend time on
it.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 17/20] range-diff: add a man page
  2018-07-09 20:00         ` Johannes Schindelin
@ 2018-07-09 20:25           ` Stefan Beller
  2018-07-09 20:38             ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-07-09 20:25 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: gitgitgadget, git, Junio C Hamano

On Mon, Jul 9, 2018 at 1:00 PM Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:
>
> Hi Stefan,
>
> On Mon, 9 Jul 2018, Stefan Beller wrote:
>
> > On Tue, Jul 3, 2018 at 4:26 AM Johannes Schindelin via GitGitGadget
> > <gitgitgadget@gmail.com> wrote:
> >
> > > +'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]
> > > +       [--dual-color] [--creation-factor=<factor>]
> > > +       ( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
> > > +
> > > +DESCRIPTION
> > > +-----------
> > > +
> > > +This command shows the differences between two versions of a patch
> > > +series, or more generally, two commit ranges (ignoring merges).
> >
> > Does it completely ignore merges or does it die("not supported"), how is
> > the user expected to cope with the accidental merge in the given range?
>
> It ignores merges. It does not reject them. It simply ignores them and
> won't talk about them as a consequence.
>
> Could you suggest an improved way to say that?

Well that is what the patch said already; I was just dense in reading.
I just tested it, and the commit with more than one parent itself is
ignored (not showing up in the output), but the commits that are merged
in are still considered. So giving range1 as
f7761a5a065..0a5677f6f68 with

  0a5677f6f68 (Merge branch 'js/branch-diff' into pu, 2018-07-06)
  f7761a5a065 (Merge branch 'jk/fsck-gitmodules-gently' into jch, 2018-07-06)

still produces cool output and with --word-diff it is even more amazing, as it
just tells me a large part was s/branch-diff/range-diff/ :-)

12:  cbc752c57ce ! 12:  7273cc64797 branch-diff: use color for the commit pairs
    @@ -1,31 +1,28 @@
    Author: Johannes Schindelin <johannes.schindelin@gmx.de>

        [-branch-diff:-]{+range-diff:+} use color for the commit pairs

        Arguably the most important part of [-branch-diff's-]{+`git
range-diff`'s+} output is the
        list of commits in the two branches, together with their relationships.

        For that reason, tbdiff introduced color-coding that is pretty
        intuitive, especially for unchanged patches (all dim yellow, like the
        first line in `git show`'s output) vs modified patches (old commit is
        red, new commit is green). Let's imitate that color scheme.

    [-    While at it, also copy tbdiff's change of the fragment color
to magenta.-]

        Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
    [-    Signed-off-by: Junio C Hamano <gitster@pobox.com>-]

Sorry for being offtopic here; I do not have a better suggestion than what
is already said.


> > I presume this is a boolean option, and can be turned off with
> > --no-dual-color, but not with --dual-color=no. Would it be worth to
> > give the --no-option here as well.
> > The more pressing question I had when reading this, is whether this
> > is the default.
>
> In the final patch (which I mulled about adding or not for a couple of
> weeks), the `--dual-color` mode is the default, and the man page talks
> about `--no-dual-color`.
>
> Do you want me to change this intermediate commit, even if that change
> will be reverted anyway?

No, I just wasn't aware of that part, yet, as I have seen some patch series
that add the man page/documentation as their final patch. This looked
so similar that I assumed this is the final man page. My bad!


> The coloring gives a strong hint of "pre" vs "post", i.e. old vs new: the
> changes that are only in the "old" patches are marked with a minus with a
> red background color, which only really makes sense if you think about
> these changes as "dropped" or "removed" from the "new" changes.
>
> So yes, it is really considered an older version, in my mind.
>
> Again, if you have suggestions how to improve my patch (giving rise to a
> "new" patch :-)), let's hear them.

I will send patches as I get more used to this new tool.

>
> > I think this comes from the notion of e.g. patch 4 ("range-diff: improve the
> > order of the shown commits "), that assume the user wants the range-diff
> > to be expressed with range2 as its "base range".
>
> No, it is motivated by the fact that we use -/+ markers to indicate
> differences between the "old" and the "new" patches.

And at this point in time we do not want to question the use of -/+ markers
for the next layer of abstraction. While +/- are well understood on
the patch level
we could argue for a different set of characters for diffs of diffs,
as that helps
to differentiate between the layers ("is it added in the diff or the
diff of the diff
due to e.g. different context?"), but then it would not recurse. (I am
not sure I
want to read diff of diffs of diffs)

Okay, for now I accept the terms of old and new patches, as I have no
better idea.

>
> > > +Algorithm
> > > +---------
> > > +
> > > +The general idea is this: we generate a cost matrix between the commits
> > > +in both commit ranges, then solve the least-cost assignment.
> >
> > Can you say more about the generation of the cost matrix?
> > I assume that it counts the number of lines added/deleted to make
> > one patch into the other patch.
>
> I think that is correct.
>
> *reading the patch*
>
> Actually, no, I was wrong. For the cost matrix, the *length* of the diff
> *of the diffs* is computed. Think of it as
>
>         git diff --no-index <(git diff A^!) <(git diff B^!) | wc -l

So the matching is based only on diffs, but the output still takes
the commit messages into account. So when diffing my series to the
series that Junio applies, (merely adding his sign off,) would be a
"cost of 0" in this context, but I still have output.

>
> > Another spot to look at is further metadata, such as author and
> > author-date, which are kept the same in a rebase workflow.
>
> I encourage you to offer that as an add-on patch series. Because what you
> suggest is not necessary for my use cases, so I'd rather not spend time on
> it.

Makes sense. When I stumble about this yet theoretical problem to materialize
in practice I will send a patch. In my mind this is not another use
case, but just
an improved matching, with the matching that this series provides being
good enough for now.

Thanks,
Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 17/20] range-diff: add a man page
  2018-07-09 20:25           ` Stefan Beller
@ 2018-07-09 20:38             ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-09 20:38 UTC (permalink / raw)
  To: Stefan Beller; +Cc: gitgitgadget, git, Junio C Hamano

Hi Stefan,

On Mon, 9 Jul 2018, Stefan Beller wrote:

> On Mon, Jul 9, 2018 at 1:00 PM Johannes Schindelin
> <Johannes.Schindelin@gmx.de> wrote:
> >
> > On Mon, 9 Jul 2018, Stefan Beller wrote:
> >
> > > On Tue, Jul 3, 2018 at 4:26 AM Johannes Schindelin via GitGitGadget
> > > <gitgitgadget@gmail.com> wrote:
> > >
> > > > +'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]
> > > > +       [--dual-color] [--creation-factor=<factor>]
> > > > +       ( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
> > > > +
> > > > +DESCRIPTION
> > > > +-----------
> > > > +
> > > > +This command shows the differences between two versions of a patch
> > > > +series, or more generally, two commit ranges (ignoring merges).
> > >
> > > Does it completely ignore merges or does it die("not supported"), how is
> > > the user expected to cope with the accidental merge in the given range?
> >
> > It ignores merges. It does not reject them. It simply ignores them and
> > won't talk about them as a consequence.
> >
> > Could you suggest an improved way to say that?
> 
> Well that is what the patch said already; I was just dense in reading.
> I just tested it, and the commit with more than one parent itself is
> ignored (not showing up in the output), but the commits that are merged
> in are still considered.

So a more accurate wording would be "ignoring merge commits" rather than
"ignoring merges".

Makes sense?

> So giving range1 as f7761a5a065..0a5677f6f68 with
> 
>   0a5677f6f68 (Merge branch 'js/branch-diff' into pu, 2018-07-06)
>   f7761a5a065 (Merge branch 'jk/fsck-gitmodules-gently' into jch, 2018-07-06)
> 
> still produces cool output and with --word-diff it is even more amazing, as it
> just tells me a large part was s/branch-diff/range-diff/ :-)

I never thought about using `--word-diff` with `range-diff`.

Sadly, for me it gives rather stupid output, e.g.

4:  6927c11a311 ! 4:  ebf3fea2517 range-diff: make --dual-color the default mode
    @@ -14,6 +14,15 @@
    diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
    --- a/Documentation/git-range-diff.txt
    +++ b/Documentation/git-range-diff.txt
    {+@@+}
    {+ --------+}
    {+ [verse]+}
    {+ 'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]+}
    {+- [--dual-color] [--creation-factor=<factor>]+}
    {++ [--no-dual-color] [--creation-factor=<factor>]+}
    {+  ( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )+}
    {+ +}
    {+ DESCRIPTION+}
    @@

     OPTIONS

> > > > +Algorithm
> > > > +---------
> > > > +
> > > > +The general idea is this: we generate a cost matrix between the commits
> > > > +in both commit ranges, then solve the least-cost assignment.
> > >
> > > Can you say more about the generation of the cost matrix?
> > > I assume that it counts the number of lines added/deleted to make
> > > one patch into the other patch.
> >
> > I think that is correct.
> >
> > *reading the patch*
> >
> > Actually, no, I was wrong. For the cost matrix, the *length* of the diff
> > *of the diffs* is computed. Think of it as
> >
> >         git diff --no-index <(git diff A^!) <(git diff B^!) | wc -l
> 
> So the matching is based only on diffs, but the output still takes
> the commit messages into account. So when diffing my series to the
> series that Junio applies, (merely adding his sign off,) would be a
> "cost of 0" in this context, but I still have output.

Exactly.

> > > Another spot to look at is further metadata, such as author and
> > > author-date, which are kept the same in a rebase workflow.
> >
> > I encourage you to offer that as an add-on patch series. Because what you
> > suggest is not necessary for my use cases, so I'd rather not spend time on
> > it.
> 
> Makes sense. When I stumble about this yet theoretical problem to
> materialize in practice I will send a patch. In my mind this is not
> another use case, but just an improved matching, with the matching that
> this series provides being good enough for now.

Sure.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 16/20] range-diff --dual-color: work around bogus white-space warning
  2018-07-09 19:34       ` Stefan Beller
@ 2018-07-09 21:02         ` Junio C Hamano
  2018-07-10 10:08           ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-07-09 21:02 UTC (permalink / raw)
  To: Stefan Beller; +Cc: gitgitgadget, git, Johannes Schindelin

Stefan Beller <sbeller@google.com> writes:

> On Tue, Jul 3, 2018 at 4:26 AM Johannes Schindelin via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
>>
>> From: Johannes Schindelin <johannes.schindelin@gmx.de>
>>
>> When displaying a diff of diffs, it is possible that there is an outer
>> `+` before a context line. That happens when the context changed between
>> old and new commit. When that context line starts with a tab (after the
>> space that marks it as context line), our diff machinery spits out a
>> white-space error (space before tab), but in this case, that is
>> incorrect.
>>
>> Work around this by detecting that situation and simply *not* printing
>> the space in that case.
>
> ok. If that is the workaround that you deem to be the right thing for now.
> (I do not have an opinion if that is the right approach, or if we'd want
> to s/<TAB>/<SPACE>/ for example.)
>
>> This is slightly improper a fix because it is conceivable that an
>> output_prefix might be configured with *just* the right length to let
>> that tab jump to a different tab stop depending whether we emit that
>> space or not.
>>
>> However, the proper fix would be relatively ugly and intrusive because
>> it would have to *weaken* the WS_SPACE_BEFORE_TAB option in ws.c.

I agree that weaking the error checking is a wrong solution.  Is the
root cause of this whole problem because for a diff of diff e.g.

	  context that did not change between iterations
	- context in old interation
	-+whatever new contents added by old iteration
	+ context in new interation updated by earlier step
	++whatever new contents added by new iteration

there needs to be a way to tell the ws.c whitespace breakage
checking logic that the very first column is not interesting at all,
and the "+" before "whatever" and " " before "context" should be
considered to actually sit at the first (or zero-th) column of the
diff output to be checked, but there is no interface to tell the
machinery that wish, because there is no such need when inspecting a
diff of contents?  If the word "context" above were indented with HT,
I can understand that the one common between iterations would
trigger SP+HT violation that way.  Is that what is happening here?

Adding a way to tell that the apparent first column is to be ignored
to ws.c machinery (or arranging the caller to skip the first column)
may be more intrusive than it is worth, only to support this tool.
Ignoring the problem altogether and live with an incorrectly colored
SP-before-HT might be a less noisy but still acceptable solution
from that point of view, though.

I also wonder if we should be feeding the context lines to ws.c
machinery in the first place though.  In the above hypothetical
diff-of-diff output, I _think_ the only two lines we want to check
for ws.c breakage are the ones that begin with "whatever".  We may
find that both iterations are trying to introduce a ws breakage, or
we may find that old one had violation which the new one corrected.
A whitespace breakage on "context" lines, whether they are the ones
being removed by the patch or the ones staying the same across the
patch, is not worth painting---the normal diff-of-contents do not
by default show them as violation, no?

^ permalink raw reply	[flat|nested] 387+ messages in thread

* refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-07 22:23               ` Johannes Schindelin
@ 2018-07-09 22:08                 ` Johannes Schindelin
  2018-07-11 16:12                   ` Junio C Hamano
  2018-07-09 22:23                 ` Junio C Hamano
  1 sibling, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-09 22:08 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Schindelin via GitGitGadget, git

Hi Junio,

On Sun, 8 Jul 2018, Johannes Schindelin wrote:

> I just encoutered a problem with your `refs/notes/amlog` and I hope you
> can help me with that.
> 
> Concretely, I want GitGitGadget to be able to identify the commit that
> corresponds to a given mail that contained a patch (if it ever made it
> into `pu`), to automate all kinds of tedious things that I currently have
> to perform manually.
> 
> And here I hit a block: I am looking for the commit corresponding to
> aca087479b35cbcbd7c84c7ca3bcf556133d0548.1530274571.git.gitgitgadget@gmail.com
> 
> When I ask `git notes --ref=refs/notes/gitster-amlog show
> 4cec3986f017d84c8d6a2c4233d2eba4a3ffa60d` (the SHA-1 is the one
> corresponding to `Message-Id: <...>` for that mail), it insists on
> outputting
> 
> 	5902152ab02291af4454f24a8ccaf2adddefc306
> 
> However, I cannot find that commit anywhere.
> 
> When I look for the commit in the same manual, tedious way that I want to
> automate, I find that it *is* in `pu`, but as
> 
> 	5cf8e064747be2026bb23be37f84f2f0b2a31781
> 
> Even curiouser: when I now ask for the commit notes for both of those
> SHA-1s, I get back the correct, same Message-Id *for both of them*, which
> makes me think that it was recorded correctly, but then overwritten due to
> some process I don't understand.
> 
> Would you be able to shed light into this?

I think I reconstructed the culprit:

In https://github.com/git/git/commit/a7cddab6e8, your post-applypatch hook
added the note for commit 5902152ab02291af4454f24a8ccaf2adddefc306 that it
was generated from Message-Id:
<aca087479b35cbcbd7c84c7ca3bcf556133d0548.1530274571.git.gitgitgadget@gmail.com>,
and then https://github.com/git/git/commit/ff28c8f9283 added the note to
map that Message-Id back to that commit.

So far, so good!

But then, https://github.com/git/git/commit/81b08c718e9 indicates that you
ran an interactive rebase and amended the commit
5902152ab02291af4454f24a8ccaf2adddefc306 and the result was a new commit
5cf8e064747be2026bb23be37f84f2f0b2a31781 that was then also mapped to that
Message-Id.

And obviously, you lack a post-rewrite hook a la

```sh
refopt=--ref=refs/notes/amlog
while read old new rest
do
	mid="$(git notes $refopt show $old 2>/dev/null)" &&
	git notes $refopt set -m "$mid" $new
done
```

I was pretty happy to figure that out all on my own, and already on my way
to come up with that post-rewrite hook and a script to parse all of the
commits in refs/notes/amlog whose commit message contains `commit --amend`
to fix those problems, but before starting, I wanted to sanity check the
oldest such commit: https://github.com/git/git/commit/49bc3858e3c

You will be readily able to verify that it maps the commit
73bfebd43e14bcc1502577c0933b6a16ad540b99 to Message-Id:
<20170619175605.27864-3-phillip.wood@talktalk.net>, but that 7c1a3dcf23e
(which corresponds to that Message-Id) maps to
f64760904766db662badf1256923532b9e1a6ebd. So yes, there is the same
problem with this mapping, and we need to fix it.

*However*. Neither https://github.com/git/git/commit/73bfebd43e1 nor
https://github.com/git/git/commit/f6476090476 show any commit!

Does that mean that the patch with that Message-Id never made it into
`master` and was simply dropped and gc'ed at some stage?

Actually, no:
https://public-inbox.org/git/20170619175605.27864-3-phillip.wood@talktalk.net/
corresponds quite clearly to
https://github.com/git/git/commit/1ceb9dfab7e

Now, that commit message was clearly edited by you (I note the capital "A"
in Phillip's "Add" vs your lower-case "a" in "add"), but the patch
quite obviously made it into our code based in its original shape.

So I looked for the commit notes for that commit, but there aren't any!

To summarize, there are two commits recorded for that Message-Id, the
later one not mapped back, and neither is the correct commit that made it
into `master`.

It would be nice to figure out what went wrong there, and how to fix it
for the future (and also to fix up the existing mis-mappings in `amlog`).

However, at this stage I really have not enough information at my hands,
even with as much effort as I spent so far to figure out where my patch
went (which started this bug hunt). Could you kindly spend some time on
that? Otherwise, `amlog` is a lot less useful than it could otherwise be.

Thanks,
Dscho

P.S.: funny side note: it would appear that the rewritten notes all get
the author of the patch author, look e.g. at the author of
https://github.com/git/git/commit/81b08c718e97

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-07 22:23               ` Johannes Schindelin
  2018-07-09 22:08                 ` refs/notes/amlog problems, was " Johannes Schindelin
@ 2018-07-09 22:23                 ` Junio C Hamano
  2018-07-10 10:47                   ` refs/notes/amlog woes, was " Johannes Schindelin
  1 sibling, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-07-09 22:23 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Johannes Schindelin via GitGitGadget, git

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> Speaking of GitGitGadget: I just encoutered a problem with your
> `refs/notes/amlog` and I hope you can help me with that.
> ...
> When I ask `git notes --ref=refs/notes/gitster-amlog show
> 4cec3986f017d84c8d6a2c4233d2eba4a3ffa60d` (the SHA-1 is the one
> corresponding to `Message-Id: <...>` for that mail), it insists on
> outputting
>
> 	5902152ab02291af4454f24a8ccaf2adddefc306

It is not uncommon for me to have to do "am" the same patch twice
when attempting to find the right branch/commit to base a change on,
so the reverse direction that abuses the notes mechanism to map
message id to resulting commits would be unreliable, especially
given that they may need to further go through "rebase -i" or manual
"cherry-pick <range>" depending on the situation.

I am kind of surprised that the message-to-commit mapping still
records any data that is remotely useful (these days, I only use it
to run "show --notes=amlog" for commit-to-message mapping).  I do
not think I have anything special when amending the commit, but
amlog notes should be updated in both diretions for its entries to
stay correct across amending, I would think.



^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 16/20] range-diff --dual-color: work around bogus white-space warning
  2018-07-09 21:02         ` Junio C Hamano
@ 2018-07-10 10:08           ` Johannes Schindelin
  2018-07-10 15:50             ` Junio C Hamano
  2018-07-10 16:32             ` Stefan Beller
  0 siblings, 2 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-10 10:08 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Stefan Beller, gitgitgadget, git

Hi Junio,

On Mon, 9 Jul 2018, Junio C Hamano wrote:

> I also wonder if we should be feeding the context lines to ws.c
> machinery in the first place though.

It *is* confusing, I know. The entire "diff of diffs" concept *is*
confusing. I just don't know about a better alternative.

So hear me out, because there is a big misconception here: there are *two*
levels of diffs. The outer one and the inner one.

Context lines of the outer diffs have no problem [*1*].

The problem arises when the outer diff shows a - or + line (i.e. the line
is present *either* in the old patch set or in the new patch set, but not
both), *and* that line is *not* a context line of the inner diff.

Let's illustrate this via an example. Let's assume that both the old patch
set and the new patch set add a comment to a statement, and that the
context of that statement changed between old and new patch set. Something
like this would be in the old patch set:

```diff
 	int quiet = 0;
+	/* This is only needed for the reflog message */
 	const char *branch = "HEAD";
```

And this would be in the new patch set:

```diff
 	int quiet = 0, try_harder = 0;
+	/* This is only needed for the reflog message */
 	const char *branch = "HEAD";
```

So as you see, both old and new revision of the same patch add that
comment, and it is just a context line that changed, which a regular
reviewer would want to *not* consider a "real" change between the patch
set iterations.

Now, let's look at the "diff of diffs":

```diff
- 	int quiet = 0;
+ 	int quiet = 0, try_harder = 0;
 +	/* This is only needed for the reflog message */
  	const char *branch = "HEAD";
```

Please understand that in the dual color mode:

- The first line's `-` would have a red background color, the rest of that
  line would be uncolored (because it is a context line of the inner
  diff),

- the second line's `+` would have a green background color, the rest
  would be just as uncolored as the rest of the first line,

- the third line would be a context line of the outer diff, but a `+` line
  of the inner diff, therefore that rest of the line would be green, and

- the fourth line is completely uncolored; It is a context line both of
  the inner and the outer diff.

That's it for the diff colors. Now for the white space: The first two
lines start with a `-` and a `+` respectively (outer diff marker), and
then most crucially continue with a space to indicate the inner diff's
context line, *and then continue with a horizontal tab*.

As far as the inner diff is concerned, this *is* a context line.

As far as the outer diff is concerned, this is *not* a context line.

And that is the conundrum: the whitespace checker is called because the
outer diff claims that the second line is a `+` line and the whitespace
checker has no idea that it should treat it as a context line instead.

I'll try to find some time this afternoon to study Stefan's reply, as I
have a hunch that there is a deep insight hidden that helps me to figure
out the proper path ahead (because I do not want to uglify the `diff.c`
code the way my current iteration does, and I'd rather have a way to color
the diff more intelligently myself, in a function in `range-diff.c`).

Ciao,
Dscho

Footnote *1*: Actually, that is only half the truth. In dual color mode,
if a line is a context line of the outer diff, but a - or + line of the
inner diff, *we still want it colored*. And of course, ideally we still
want whitespace checking for the + lines.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* refs/notes/amlog woes, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-09 22:23                 ` Junio C Hamano
@ 2018-07-10 10:47                   ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-10 10:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Schindelin via GitGitGadget, git

Hi Junio,

On Mon, 9 Jul 2018, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > Speaking of GitGitGadget: I just encoutered a problem with your
> > `refs/notes/amlog` and I hope you can help me with that.
> > ...
> > When I ask `git notes --ref=refs/notes/gitster-amlog show
> > 4cec3986f017d84c8d6a2c4233d2eba4a3ffa60d` (the SHA-1 is the one
> > corresponding to `Message-Id: <...>` for that mail), it insists on
> > outputting
> >
> > 	5902152ab02291af4454f24a8ccaf2adddefc306
> 
> It is not uncommon for me to have to do "am" the same patch twice
> when attempting to find the right branch/commit to base a change on,

But then the `post-applypatch` hook just kicks in twice, leaving the
correct mapping in place, no?

> so the reverse direction that abuses the notes mechanism to map
> message id to resulting commits would be unreliable, especially
> given that they may need to further go through "rebase -i" or manual
> "cherry-pick <range>" depending on the situation.

We already have a mechanism in place that rewrites notes in `rebase -i`'s
case. Not so sure about `cherry-pick`, but if it is missing, then that is
definitely something we will want to address.

In other words, let's not let shortcomings of our own software dictate
what we record and what we don't record.

This is highly important information that we willfully lose by using the
patch contribution process we are going with. And we *can* at least record
that information.

> I am kind of surprised that the message-to-commit mapping still
> records any data that is remotely useful (these days, I only use it
> to run "show --notes=amlog" for commit-to-message mapping).

Please do understand that this information is the only remotely sane way
to work around the limitations of the mailing list-based approach we use
here.

It costs me a ton of time to figure out these mappings manually, and I
think that others simply are not as tenacious as I am and simply drop the
ball, which is not good for the project.

> I do not think I have anything special when amending the commit, but
> amlog notes should be updated in both diretions for its entries to stay
> correct across amending, I would think.

Indeed. See my other mail about the `post-rewrite` hook I suggest you to
install (I did not test this code, of course, but you will probably be
able to validate/fix it without much trouble).

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 16/20] range-diff --dual-color: work around bogus white-space warning
  2018-07-10 10:08           ` Johannes Schindelin
@ 2018-07-10 15:50             ` Junio C Hamano
  2018-07-10 16:32             ` Stefan Beller
  1 sibling, 0 replies; 387+ messages in thread
From: Junio C Hamano @ 2018-07-10 15:50 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Stefan Beller, gitgitgadget, git

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> Hi Junio,
>
> On Mon, 9 Jul 2018, Junio C Hamano wrote:
>
>> I also wonder if we should be feeding the context lines to ws.c
>> machinery in the first place though.
>
> It *is* confusing, I know. The entire "diff of diffs" concept *is*
> confusing. I just don't know about a better alternative.
>
> So hear me out, because there is a big misconception here: there are *two*
> levels of diffs. The outer one and the inner one.
>
> Context lines of the outer diffs have no problem [*1*].
>
> The problem arises when the outer diff shows a - or + line (i.e. the line
> is present *either* in the old patch set or in the new patch set, but not
> both), *and* that line is *not* a context line of the inner diff.
>
> Let's illustrate this via an example. Let's assume that both the old patch
> set and the new patch set add a comment to a statement, and that the
> context of that statement changed between old and new patch set. Something
> like this would be in the old patch set:
>
> ```diff
>  	int quiet = 0;
> +	/* This is only needed for the reflog message */
>  	const char *branch = "HEAD";
> ```
>
> And this would be in the new patch set:
>
> ```diff
>  	int quiet = 0, try_harder = 0;
> +	/* This is only needed for the reflog message */
>  	const char *branch = "HEAD";
> ```
>
> So as you see, both old and new revision of the same patch add that
> comment, and it is just a context line that changed, which a regular
> reviewer would want to *not* consider a "real" change between the patch
> set iterations.
>
> Now, let's look at the "diff of diffs":
>
> ```diff
> - 	int quiet = 0;
> + 	int quiet = 0, try_harder = 0;
>  +	/* This is only needed for the reflog message */
>   	const char *branch = "HEAD";
> ```
>
> Please understand that in the dual color mode:
>
> - The first line's `-` would have a red background color, the rest of that
>   line would be uncolored (because it is a context line of the inner
>   diff),
>
> - the second line's `+` would have a green background color, the rest
>   would be just as uncolored as the rest of the first line,
>
> - the third line would be a context line of the outer diff, but a `+` line
>   of the inner diff, therefore that rest of the line would be green, and
>
> - the fourth line is completely uncolored; It is a context line both of
>   the inner and the outer diff.

All of the above about colouring I find sensible.

> That's it for the diff colors. Now for the white space: The first two
> lines start with a `-` and a `+` respectively (outer diff marker), and
> then most crucially continue with a space to indicate the inner diff's
> context line, *and then continue with a horizontal tab*.
>
> As far as the inner diff is concerned, this *is* a context line.
>
> As far as the outer diff is concerned, this is *not* a context line.

What I meant was that there is no point checking ws errors in the
outer diff.  The fact that the older and the newer revisions have an
unchanged context line (i.e. begins with two SPs in the outer diff)
or different one (i.e. begins with "- " and "+ ") are worth knowing
(and you have red and green leading "-/+" for that), but the fact
that the outer diff's new context line begins with "+ <HT>" has
nothing to do with the goodness of the new patch, as such a line
shows that the new patch touches a line near an unchanged [*1*] line
that happens to begin with a <HT>, which has no whitespace breakage
to begin with, and even if such a line had a trailing whitespace, it
is not something the new patch introduces.

	side note *1*; the fact that it has leading "+" means that
	there are differences in the previous steps between old and
	new patch series and that is why the context in this step is
	different between old and new series.  But in the context of
	applying the new series, the patch does *not* change that
	line.

> And that is the conundrum: the whitespace checker is called because the
> outer diff claims that the second line is a `+` line and the whitespace
> checker has no idea that it should treat it as a context line instead.

I think you are saying the same thing as I said in the previous
message.  Trying to reuse ws.c without first giving it a way to be
told that the caller does not care about the early columns (in the
"+ <HT>" example, you want to tell ws.c machinery that it is *not*
an added line that begins with SP + HT; you want it to know that it
is a context line whose contents begins with a HT) will of course
cause headaches.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 16/20] range-diff --dual-color: work around bogus white-space warning
  2018-07-10 10:08           ` Johannes Schindelin
  2018-07-10 15:50             ` Junio C Hamano
@ 2018-07-10 16:32             ` Stefan Beller
  2018-07-21 21:44               ` Johannes Schindelin
  1 sibling, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-07-10 16:32 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Junio C Hamano, gitgitgadget, git

On Tue, Jul 10, 2018 at 3:08 AM Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:
>
> Hi Junio,
>
> On Mon, 9 Jul 2018, Junio C Hamano wrote:
>
> > I also wonder if we should be feeding the context lines to ws.c
> > machinery in the first place though.
>
> It *is* confusing, I know. The entire "diff of diffs" concept *is*
> confusing. I just don't know about a better alternative.

I agree, but I am sure we'll get used to it quickly.

> So hear me out, because there is a big misconception here: there are *two*
> levels of diffs. The outer one and the inner one.

Yes, the inner diff is just input that was generated before because it is
so convenient to generate. Recently when using this too (back then
when it was called branch-diff), I came across the following:

Patch 1 looked like:

    line 1
+    new line
    line 2
    line 3

and in the next iteration it looked like:
    line 1
    line 2
+    new line
    line 3

such that the diff of diffs showed the move correctly, but as the inner diffs
had different context ranges, other lines looked like added/removed
in the outer diff, though it was both context.
So I wonder if eventually (not in this series) we want to tweak the context
lines, generate more than needed in the inner diffs and cut them off in
the outer diff "at the same line".

I digress again w.r.t. white space.

> Context lines of the outer diffs have no problem [*1*].
>
> The problem arises when the outer diff shows a - or + line (i.e. the line
> is present *either* in the old patch set or in the new patch set, but not
> both), *and* that line is *not* a context line of the inner diff.

So an actual change in the patches; an incremental reviewer would want
to spend most care on these.

>
> Let's illustrate this via an example. Let's assume that both the old patch
> set and the new patch set add a comment to a statement, and that the
> context of that statement changed between old and new patch set. Something
> like this would be in the old patch set:
>
> ```diff
>         int quiet = 0;
> +       /* This is only needed for the reflog message */
>         const char *branch = "HEAD";
> ```
>
> And this would be in the new patch set:
>
> ```diff
>         int quiet = 0, try_harder = 0;
> +       /* This is only needed for the reflog message */
>         const char *branch = "HEAD";
> ```
>
> So as you see, both old and new revision of the same patch add that
> comment, and it is just a context line that changed, which a regular
> reviewer would want to *not* consider a "real" change between the patch
> set iterations.
>
> Now, let's look at the "diff of diffs":
>
> ```diff
> -       int quiet = 0;
> +       int quiet = 0, try_harder = 0;
>  +      /* This is only needed for the reflog message */
>         const char *branch = "HEAD";
> ```
>
> Please understand that in the dual color mode:
>
> - The first line's `-` would have a red background color, the rest of that
>   line would be uncolored (because it is a context line of the inner
>   diff),
>
> - the second line's `+` would have a green background color, the rest
>   would be just as uncolored as the rest of the first line,
>
> - the third line would be a context line of the outer diff, but a `+` line
>   of the inner diff, therefore that rest of the line would be green, and
>
> - the fourth line is completely uncolored; It is a context line both of
>   the inner and the outer diff.
>
> That's it for the diff colors. Now for the white space: The first two
> lines start with a `-` and a `+` respectively (outer diff marker), and
> then most crucially continue with a space to indicate the inner diff's
> context line, *and then continue with a horizontal tab*.
>
> As far as the inner diff is concerned, this *is* a context line.
>
> As far as the outer diff is concerned, this is *not* a context line.
>
> And that is the conundrum: the whitespace checker is called because the
> outer diff claims that the second line is a `+` line and the whitespace
> checker has no idea that it should treat it as a context line instead.

Spelled out this way, we might want to add more symbols to
enum diff_symbol, such as
    DIFF_SYMBOL_DUAL_DIFF_PLUS_PLUS
    DIFF_SYMBOL_DUAL_DIFF_PLUS_MINUS
    DIFF_SYMBOL_PLUS_MINUS
or so.

These would need to get generated when we create the diff of diffs
in emit_{del,add,context}_line or even fn_out_consume; and then have
their own treatment regarding white spaces in emit_diff_symbol_from_struct.

I am not sure if that would help for the series as-is, as I am
thinking already how
to move these diff-diffs in-core (as that would help a lot with the context line
cutting mentioned above).

> I'll try to find some time this afternoon to study Stefan's reply, as I
> have a hunch that there is a deep insight hidden that helps me to figure
> out the proper path ahead (because I do not want to uglify the `diff.c`
> code the way my current iteration does, and I'd rather have a way to color
> the diff more intelligently myself, in a function in `range-diff.c`).

I considered trying a cleanup on top of your series as I had the impression
the move detection added some ugliness as well.

Thanks,
Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* [PATCH 0/2] Re: [PATCH v3 14/20] diff: add an internal option to dual-color diffs of diffs
  2018-07-09 19:29       ` Stefan Beller
@ 2018-07-10 17:45         ` Stefan Beller
  2018-07-10 17:45           ` [PATCH 1/2] diff.c: convert emit_line_ws_markup to take string for sign Stefan Beller
                             ` (2 more replies)
  0 siblings, 3 replies; 387+ messages in thread
From: Stefan Beller @ 2018-07-10 17:45 UTC (permalink / raw)
  To: sbeller; +Cc: git, gitgitgadget, gitster, johannes.schindelin

This is developed on top of 4a68b95ce2a6 (your series here)

This is an attempt to explain the previous email better,
specially the second (yet unfinished) patch, but the resulting
emit_line_0 is way clearer in my mind, dropping the 'first' character
and instead having a 'char *sign' that (a) we can color differently for
dual color and (b) can have multiple chars, the refactoring for the multiple
chars would need to happen at a slightly higher level.

Feel free to draw inspiration from here, but if not that is fine, too
(as I did not fully understand the word diffing problem yet, this may add a
burden instead of just taking these patches).
I can send up a cleanup series after yours lands, as well.

Thanks,
Stefan

Stefan Beller (2):
  diff.c: convert emit_line_ws_markup to take string for sign
  WIP diff.c: clarify emit_line_0

 diff.c | 84 ++++++++++++++++++++++++++++------------------------------
 1 file changed, 40 insertions(+), 44 deletions(-)

-- 
2.18.0.203.gfac676dfb9-goog


^ permalink raw reply	[flat|nested] 387+ messages in thread

* [PATCH 1/2] diff.c: convert emit_line_ws_markup to take string for sign
  2018-07-10 17:45         ` [PATCH 0/2] " Stefan Beller
@ 2018-07-10 17:45           ` Stefan Beller
  2018-07-10 17:45           ` [PATCH 2/2] WIP diff.c: clarify emit_line_0 Stefan Beller
  2018-07-21 21:13           ` [PATCH 0/2] Re: [PATCH v3 14/20] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin
  2 siblings, 0 replies; 387+ messages in thread
From: Stefan Beller @ 2018-07-10 17:45 UTC (permalink / raw)
  To: sbeller; +Cc: git, gitgitgadget, gitster, johannes.schindelin

For the diff of diffs, we have more than one character at the beginning
of the line with special meaning, so let's pass around a string that
contains all the markup for the line

Signed-off-by: Stefan Beller <sbeller@google.com>
---
 diff.c | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/diff.c b/diff.c
index 32500716740..028d7d9a59c 100644
--- a/diff.c
+++ b/diff.c
@@ -969,7 +969,7 @@ static void dim_moved_lines(struct diff_options *o)
 static void emit_line_ws_markup(struct diff_options *o,
 				const char *set, const char *reset,
 				const char *line, int len,
-				const char *set_sign, char sign,
+				const char *set_sign, const char *sign,
 				unsigned ws_rule, int blank_at_eof)
 {
 	const char *ws = NULL;
@@ -981,19 +981,19 @@ static void emit_line_ws_markup(struct diff_options *o,
 	}
 
 	if (!ws && !set_sign)
-		emit_line_0(o, set, 0, reset, sign, line, len);
+		emit_line_0(o, set, 0, reset, sign[0], line, len);
 	else if (!ws) {
 		/* Emit just the prefix, then the rest. */
 		emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
-			    sign, "", 0);
+			    sign[0], "", 0);
 		emit_line_0(o, set, 0, reset, 0, line, len);
 	} else if (blank_at_eof)
 		/* Blank line at EOF - paint '+' as well */
-		emit_line_0(o, ws, 0, reset, sign, line, len);
+		emit_line_0(o, ws, 0, reset, sign[0], line, len);
 	else {
 		/* Emit just the prefix, then the rest. */
 		emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
-			    sign, "", 0);
+			    sign[0], "", 0);
 		ws_check_emit(line, len, ws_rule,
 			      o->file, set, reset, ws);
 	}
@@ -1054,7 +1054,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 			else if (c == '-')
 				set = diff_get_color_opt(o, DIFF_FILE_OLD);
 		}
-		emit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, " ",
 				    flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);
 		break;
 	case DIFF_SYMBOL_PLUS:
@@ -1100,7 +1100,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 				len--;
 			}
 		}
-		emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, "+",
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK,
 				    flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);
 		break;
@@ -1141,7 +1141,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 			else if (c != '-')
 				set = diff_get_color_opt(o, DIFF_CONTEXT);
 		}
-		emit_line_ws_markup(o, set, reset, line, len, set_sign, '-',
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, "-",
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);
 		break;
 	case DIFF_SYMBOL_WORDS_PORCELAIN:
-- 
2.18.0.203.gfac676dfb9-goog


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 2/2] WIP diff.c: clarify emit_line_0
  2018-07-10 17:45         ` [PATCH 0/2] " Stefan Beller
  2018-07-10 17:45           ` [PATCH 1/2] diff.c: convert emit_line_ws_markup to take string for sign Stefan Beller
@ 2018-07-10 17:45           ` Stefan Beller
  2018-07-10 19:58             ` [PATCH 1/2] diff.c: convert emit_line_ws_markup to take string for sign Stefan Beller
  2018-07-10 19:59             ` [PATCH] diff.c: clarify emit_line_0 Stefan Beller
  2018-07-21 21:13           ` [PATCH 0/2] Re: [PATCH v3 14/20] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin
  2 siblings, 2 replies; 387+ messages in thread
From: Stefan Beller @ 2018-07-10 17:45 UTC (permalink / raw)
  To: sbeller; +Cc: git, gitgitgadget, gitster, johannes.schindelin

This breaks t4034 (word diffs), but all other tests pass.

emit_line_0 grew complicated again, so here is an attempt to make it
a bit simpler. emit_line_0 is called for all lines that are added,
removed or context lines, and it follows the format:

 <sign color> <sign> <main color> <content of length 'len'> <reset> <CR> <LF>

with each of the components optional. However a few rules apply:
* The CR/LF is passed to the function as part of line/len, so we have
  to figure out if we need to separate them
* As the sign color is a rather recent addition, we do not optimize it
  yet
* the main color insertion is new, as the color used to be inserted before
  the sign
* another follow up cleanup (that also touches the tests) could be
  a stricter check to consolidate with ws_check_emit (and not emit the
  color/reset twice)

Signed-off-by: Stefan Beller <sbeller@google.com>
---
 diff.c | 78 ++++++++++++++++++++++++++++------------------------------
 1 file changed, 37 insertions(+), 41 deletions(-)

diff --git a/diff.c b/diff.c
index 028d7d9a59c..7b649f57c27 100644
--- a/diff.c
+++ b/diff.c
@@ -563,44 +563,44 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,
 }
 
 static void emit_line_0(struct diff_options *o,
-			const char *set, unsigned reverse, const char *reset,
-			int first, const char *line, int len)
+			const char *maincolor, const char *signcolor,
+			const char *reset, const char *sign,
+			const char *line, int len)
 {
 	int has_trailing_newline, has_trailing_carriage_return;
-	int nofirst;
 	FILE *file = o->file;
 
-	if (first)
-		fputs(diff_line_prefix(o), file);
-	else if (!len)
-		return;
+	fputs(diff_line_prefix(o), file);
 
-	if (len == 0) {
-		has_trailing_newline = (first == '\n');
-		has_trailing_carriage_return = (!has_trailing_newline &&
-						(first == '\r'));
-		nofirst = has_trailing_newline || has_trailing_carriage_return;
-	} else {
-		has_trailing_newline = (len > 0 && line[len-1] == '\n');
-		if (has_trailing_newline)
-			len--;
-		has_trailing_carriage_return = (len > 0 && line[len-1] == '\r');
-		if (has_trailing_carriage_return)
-			len--;
-		nofirst = 0;
-	}
+	has_trailing_newline = (len > 0 && line[len-1] == '\n');
+	if (has_trailing_newline)
+		len--;
+	has_trailing_carriage_return = (len > 0 && line[len-1] == '\r');
+	if (has_trailing_carriage_return)
+		len--;
 
-	if (len || !nofirst) {
-		if (reverse && want_color(o->use_color))
-			fputs(GIT_COLOR_REVERSE, file);
-		fputs(set, file);
-		if (first && !nofirst)
-			fputc(first, file);
+	if (signcolor)
+		fputs(signcolor, file);
+	else if (maincolor)
+		fputs(maincolor, file);
+
+
+	if (sign)
+		fputs(sign, file);
+
+	/* only put main color here if it we did not color the sign the same way */
+	if (signcolor && maincolor && signcolor != maincolor)
+		fputs(maincolor, file);
+
+	if (len)
 		fwrite(line, len, 1, file);
+
+	if ((signcolor || maincolor) && reset)
 		fputs(reset, file);
-	}
+
 	if (has_trailing_carriage_return)
 		fputc('\r', file);
+
 	if (has_trailing_newline)
 		fputc('\n', file);
 }
@@ -608,7 +608,7 @@ static void emit_line_0(struct diff_options *o,
 static void emit_line(struct diff_options *o, const char *set, const char *reset,
 		      const char *line, int len)
 {
-	emit_line_0(o, set, 0, reset, line[0], line+1, len-1);
+	emit_line_0(o, set, NULL, reset, NULL, line, len);
 }
 
 enum diff_symbol {
@@ -980,20 +980,16 @@ static void emit_line_ws_markup(struct diff_options *o,
 			ws = NULL;
 	}
 
-	if (!ws && !set_sign)
-		emit_line_0(o, set, 0, reset, sign[0], line, len);
-	else if (!ws) {
-		/* Emit just the prefix, then the rest. */
-		emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
-			    sign[0], "", 0);
-		emit_line_0(o, set, 0, reset, 0, line, len);
-	} else if (blank_at_eof)
+	if (!ws)
+		emit_line_0(o, set, set_sign, reset,
+			    sign, line, len);
+	else if (blank_at_eof)
 		/* Blank line at EOF - paint '+' as well */
-		emit_line_0(o, ws, 0, reset, sign[0], line, len);
+		emit_line_0(o, ws, set_sign, reset, sign, line, len);
 	else {
 		/* Emit just the prefix, then the rest. */
-		emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
-			    sign[0], "", 0);
+		emit_line_0(o, set, set_sign, reset,
+			    sign, "", 0);
 		ws_check_emit(line, len, ws_rule,
 			      o->file, set, reset, ws);
 	}
@@ -1016,7 +1012,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 		context = diff_get_color_opt(o, DIFF_CONTEXT);
 		reset = diff_get_color_opt(o, DIFF_RESET);
 		putc('\n', o->file);
-		emit_line_0(o, context, 0, reset, '\\',
+		emit_line_0(o, context, 0, reset, "\\",
 			    nneof, strlen(nneof));
 		break;
 	case DIFF_SYMBOL_SUBMODULE_HEADER:
-- 
2.18.0.203.gfac676dfb9-goog


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH 1/2] diff.c: convert emit_line_ws_markup to take string for sign
  2018-07-10 17:45           ` [PATCH 2/2] WIP diff.c: clarify emit_line_0 Stefan Beller
@ 2018-07-10 19:58             ` Stefan Beller
  2018-07-10 19:59             ` [PATCH] diff.c: clarify emit_line_0 Stefan Beller
  1 sibling, 0 replies; 387+ messages in thread
From: Stefan Beller @ 2018-07-10 19:58 UTC (permalink / raw)
  To: sbeller; +Cc: git, gitgitgadget, gitster, johannes.schindelin

For the diff of diffs, we have more than one character at the beginning
of the line with special meaning, so let's pass around a string that
contains all the markup for the line

Signed-off-by: Stefan Beller <sbeller@google.com>
---
 diff.c | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/diff.c b/diff.c
index 32500716740..028d7d9a59c 100644
--- a/diff.c
+++ b/diff.c
@@ -969,7 +969,7 @@ static void dim_moved_lines(struct diff_options *o)
 static void emit_line_ws_markup(struct diff_options *o,
 				const char *set, const char *reset,
 				const char *line, int len,
-				const char *set_sign, char sign,
+				const char *set_sign, const char *sign,
 				unsigned ws_rule, int blank_at_eof)
 {
 	const char *ws = NULL;
@@ -981,19 +981,19 @@ static void emit_line_ws_markup(struct diff_options *o,
 	}
 
 	if (!ws && !set_sign)
-		emit_line_0(o, set, 0, reset, sign, line, len);
+		emit_line_0(o, set, 0, reset, sign[0], line, len);
 	else if (!ws) {
 		/* Emit just the prefix, then the rest. */
 		emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
-			    sign, "", 0);
+			    sign[0], "", 0);
 		emit_line_0(o, set, 0, reset, 0, line, len);
 	} else if (blank_at_eof)
 		/* Blank line at EOF - paint '+' as well */
-		emit_line_0(o, ws, 0, reset, sign, line, len);
+		emit_line_0(o, ws, 0, reset, sign[0], line, len);
 	else {
 		/* Emit just the prefix, then the rest. */
 		emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
-			    sign, "", 0);
+			    sign[0], "", 0);
 		ws_check_emit(line, len, ws_rule,
 			      o->file, set, reset, ws);
 	}
@@ -1054,7 +1054,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 			else if (c == '-')
 				set = diff_get_color_opt(o, DIFF_FILE_OLD);
 		}
-		emit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, " ",
 				    flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);
 		break;
 	case DIFF_SYMBOL_PLUS:
@@ -1100,7 +1100,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 				len--;
 			}
 		}
-		emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, "+",
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK,
 				    flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);
 		break;
@@ -1141,7 +1141,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 			else if (c != '-')
 				set = diff_get_color_opt(o, DIFF_CONTEXT);
 		}
-		emit_line_ws_markup(o, set, reset, line, len, set_sign, '-',
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, "-",
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);
 		break;
 	case DIFF_SYMBOL_WORDS_PORCELAIN:
-- 
2.18.0.203.gfac676dfb9-goog


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH] diff.c: clarify emit_line_0
  2018-07-10 17:45           ` [PATCH 2/2] WIP diff.c: clarify emit_line_0 Stefan Beller
  2018-07-10 19:58             ` [PATCH 1/2] diff.c: convert emit_line_ws_markup to take string for sign Stefan Beller
@ 2018-07-10 19:59             ` Stefan Beller
  2018-07-10 21:54               ` [PATCH] ws: do not reset and set color twice Stefan Beller
  1 sibling, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-07-10 19:59 UTC (permalink / raw)
  To: sbeller; +Cc: git, gitgitgadget, gitster, johannes.schindelin

emit_line_0 grew complicated again, so here is an attempt to make it
a bit simpler. emit_line_0 is called for all lines that are added,
removed or context lines, and it follows the format:

 <sign color> <sign> <main color> <content of length 'len'> <reset> \
    <CR> <LF>

with each of the components optional.

Another follow up cleanup (that also touches the tests) could be
a stricter check to consolidate with ws_check_emit (and not emit the
color/reset twice).

Signed-off-by: Stefan Beller <sbeller@google.com>
---
 
 oops wrong patch, this one should do.
 Now this passes all tests. 

 diff.c                     | 84 +++++++++++++++++++-------------------
 t/t4015-diff-whitespace.sh | 10 ++---
 2 files changed, 48 insertions(+), 46 deletions(-)

diff --git a/diff.c b/diff.c
index 028d7d9a59c..0b00df7b3c8 100644
--- a/diff.c
+++ b/diff.c
@@ -563,42 +563,48 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,
 }
 
 static void emit_line_0(struct diff_options *o,
-			const char *set, unsigned reverse, const char *reset,
-			int first, const char *line, int len)
+			const char *maincolor, const char *signcolor,
+			const char *reset, const char *sign,
+			const char *line, int len)
 {
 	int has_trailing_newline, has_trailing_carriage_return;
-	int nofirst;
 	FILE *file = o->file;
 
-	if (first)
-		fputs(diff_line_prefix(o), file);
-	else if (!len)
-		return;
+	fputs(diff_line_prefix(o), file);
 
-	if (len == 0) {
-		has_trailing_newline = (first == '\n');
-		has_trailing_carriage_return = (!has_trailing_newline &&
-						(first == '\r'));
-		nofirst = has_trailing_newline || has_trailing_carriage_return;
-	} else {
-		has_trailing_newline = (len > 0 && line[len-1] == '\n');
-		if (has_trailing_newline)
-			len--;
-		has_trailing_carriage_return = (len > 0 && line[len-1] == '\r');
-		if (has_trailing_carriage_return)
-			len--;
-		nofirst = 0;
-	}
+	has_trailing_newline = (len > 0 && line[len-1] == '\n');
+	if (has_trailing_newline)
+		len--;
+	has_trailing_carriage_return = (len > 0 && line[len-1] == '\r');
+	if (has_trailing_carriage_return)
+		len--;
+
+	/*
+	 * Color the sign differently if requested, otherwise use the main
+	 * color.
+	 */
+	if (signcolor)
+		fputs(signcolor, file);
+	else if (maincolor)
+		fputs(maincolor, file);
+
+	if (sign)
+		fputs(sign, file);
+
+	/*
+	 * Only put the main color here if it we did not color the sign the
+	 * same way already
+	 */
+	if (signcolor && maincolor && strcmp(signcolor, maincolor))
+		fputs(maincolor, file);
 
-	if (len || !nofirst) {
-		if (reverse && want_color(o->use_color))
-			fputs(GIT_COLOR_REVERSE, file);
-		fputs(set, file);
-		if (first && !nofirst)
-			fputc(first, file);
+	if (len)
 		fwrite(line, len, 1, file);
+
+	if (((maincolor && *maincolor) || (signcolor && *signcolor) || len > 0)
+	    && reset)
 		fputs(reset, file);
-	}
+
 	if (has_trailing_carriage_return)
 		fputc('\r', file);
 	if (has_trailing_newline)
@@ -608,7 +614,7 @@ static void emit_line_0(struct diff_options *o,
 static void emit_line(struct diff_options *o, const char *set, const char *reset,
 		      const char *line, int len)
 {
-	emit_line_0(o, set, 0, reset, line[0], line+1, len-1);
+	emit_line_0(o, set, NULL, reset, NULL, line, len);
 }
 
 enum diff_symbol {
@@ -980,20 +986,16 @@ static void emit_line_ws_markup(struct diff_options *o,
 			ws = NULL;
 	}
 
-	if (!ws && !set_sign)
-		emit_line_0(o, set, 0, reset, sign[0], line, len);
-	else if (!ws) {
-		/* Emit just the prefix, then the rest. */
-		emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
-			    sign[0], "", 0);
-		emit_line_0(o, set, 0, reset, 0, line, len);
-	} else if (blank_at_eof)
+	if (!ws)
+		emit_line_0(o, set, set_sign, reset,
+			    sign, line, len);
+	else if (blank_at_eof)
 		/* Blank line at EOF - paint '+' as well */
-		emit_line_0(o, ws, 0, reset, sign[0], line, len);
+		emit_line_0(o, ws, set_sign, reset, sign, line, len);
 	else {
 		/* Emit just the prefix, then the rest. */
-		emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
-			    sign[0], "", 0);
+		emit_line_0(o, set, set_sign, reset,
+			    sign, "", 0);
 		ws_check_emit(line, len, ws_rule,
 			      o->file, set, reset, ws);
 	}
@@ -1016,7 +1018,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 		context = diff_get_color_opt(o, DIFF_CONTEXT);
 		reset = diff_get_color_opt(o, DIFF_RESET);
 		putc('\n', o->file);
-		emit_line_0(o, context, 0, reset, '\\',
+		emit_line_0(o, context, 0, reset, "\\",
 			    nneof, strlen(nneof));
 		break;
 	case DIFF_SYMBOL_SUBMODULE_HEADER:
diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh
index 17df491a3ab..95baf237a83 100755
--- a/t/t4015-diff-whitespace.sh
+++ b/t/t4015-diff-whitespace.sh
@@ -945,7 +945,7 @@ test_expect_success 'ws-error-highlight test setup' '
 	<BOLD>--- a/x<RESET>
 	<BOLD>+++ b/x<RESET>
 	<CYAN>@@ -1,2 +1,3 @@<RESET>
-	 <RESET>0. blank-at-eol<RESET><BLUE> <RESET>
+	 0. blank-at-eol<RESET><BLUE> <RESET>
 	<RED>-<RESET><RED>1. blank-at-eol<RESET><BLUE> <RESET>
 	<GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET>
 	<GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>
@@ -1140,7 +1140,7 @@ test_expect_success 'detect malicious moved code, inside file' '
 	<CYAN>@@ -5,13 +5,6 @@<RESET> <RESET>printf("Hello ");<RESET>
 	 printf("World\n");<RESET>
 	 }<RESET>
-	 <RESET>
+	 
 	<BRED>-int secure_foo(struct user *u)<RESET>
 	<BRED>-{<RESET>
 	<BLUE>-if (!u->is_allowed_foo)<RESET>
@@ -1158,7 +1158,7 @@ test_expect_success 'detect malicious moved code, inside file' '
 	<CYAN>@@ -4,6 +4,13 @@<RESET> <RESET>int bar()<RESET>
 	 printf("Hello World, but different\n");<RESET>
 	 }<RESET>
-	 <RESET>
+	 
 	<BGREEN>+<RESET><BGREEN>int secure_foo(struct user *u)<RESET>
 	<BGREEN>+<RESET><BGREEN>{<RESET>
 	<GREEN>+<RESET><GREEN>foo(u);<RESET>
@@ -1189,7 +1189,7 @@ test_expect_success 'plain moved code, inside file' '
 	<CYAN>@@ -5,13 +5,6 @@<RESET> <RESET>printf("Hello ");<RESET>
 	 printf("World\n");<RESET>
 	 }<RESET>
-	 <RESET>
+	 
 	<BRED>-int secure_foo(struct user *u)<RESET>
 	<BRED>-{<RESET>
 	<BRED>-if (!u->is_allowed_foo)<RESET>
@@ -1207,7 +1207,7 @@ test_expect_success 'plain moved code, inside file' '
 	<CYAN>@@ -4,6 +4,13 @@<RESET> <RESET>int bar()<RESET>
 	 printf("Hello World, but different\n");<RESET>
 	 }<RESET>
-	 <RESET>
+	 
 	<BGREEN>+<RESET><BGREEN>int secure_foo(struct user *u)<RESET>
 	<BGREEN>+<RESET><BGREEN>{<RESET>
 	<BGREEN>+<RESET><BGREEN>foo(u);<RESET>
-- 
2.18.0.203.gfac676dfb9-goog


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH] ws: do not reset and set color twice
  2018-07-10 19:59             ` [PATCH] diff.c: clarify emit_line_0 Stefan Beller
@ 2018-07-10 21:54               ` Stefan Beller
  0 siblings, 0 replies; 387+ messages in thread
From: Stefan Beller @ 2018-07-10 21:54 UTC (permalink / raw)
  To: sbeller; +Cc: git, gitgitgadget, gitster, johannes.schindelin

When outputting lines that are checked for white space, we first use
emit_line_0 to emit the prefix, and then the ws specific code. The code
at each site carefully sets the color and then resets it, though it is
the same color.

Avoid setting the color twice by passing a newly introduced flag that
indicates if the color is already set.

Signed-off-by: Stefan Beller <sbeller@google.com>
---

  The whole series is also available via
  git fetch http://github.com/stefanbeller/git ws_cleanup-ontop-range-diff
  and concludes my cleanup on top of the range-diff series for now.
  
  What is left is to refactor the diff-diff from the range-diff series
  to utilize the marker at the beginning of the line that can be more than
  one character. I left that as I do not want to collide with your work.
  
  Thanks,
  Stefan
  

 cache.h                    |   2 +-
 diff.c                     |   8 +--
 t/t4015-diff-whitespace.sh | 122 ++++++++++++++++++-------------------
 ws.c                       |  28 +++++++--
 4 files changed, 89 insertions(+), 71 deletions(-)

diff --git a/cache.h b/cache.h
index d49092d94d1..8f53a65fa36 100644
--- a/cache.h
+++ b/cache.h
@@ -1807,7 +1807,7 @@ extern unsigned whitespace_rule_cfg;
 extern unsigned whitespace_rule(const char *);
 extern unsigned parse_whitespace_rule(const char *);
 extern unsigned ws_check(const char *line, int len, unsigned ws_rule);
-extern void ws_check_emit(const char *line, int len, unsigned ws_rule, FILE *stream, const char *set, const char *reset, const char *ws);
+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);
 extern char *whitespace_error_string(unsigned ws);
 extern void ws_fix_copy(struct strbuf *, const char *, int, unsigned, int *);
 extern int ws_blank_line(const char *line, int len, unsigned ws_rule);
diff --git a/diff.c b/diff.c
index 0b00df7b3c8..34d02f4095b 100644
--- a/diff.c
+++ b/diff.c
@@ -993,11 +993,11 @@ static void emit_line_ws_markup(struct diff_options *o,
 		/* Blank line at EOF - paint '+' as well */
 		emit_line_0(o, ws, set_sign, reset, sign, line, len);
 	else {
-		/* Emit just the prefix, then the rest. */
-		emit_line_0(o, set, set_sign, reset,
+		/* Emit just the prefix (with no RESET), then the rest. */
+		emit_line_0(o, set, set_sign, NULL,
 			    sign, "", 0);
 		ws_check_emit(line, len, ws_rule,
-			      o->file, set, reset, ws);
+			      o->file, set, reset, ws, 1);
 	}
 }
 
@@ -2918,7 +2918,7 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
 		free(err);
 		emit_line(data->o, set, reset, line, 1);
 		ws_check_emit(line + 1, len - 1, data->ws_rule,
-			      data->o->file, set, reset, ws);
+			      data->o->file, set, reset, ws, 0);
 	} else if (line[0] == ' ') {
 		data->lineno++;
 	} else if (line[0] == '@') {
diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh
index 95baf237a83..8834f2040c0 100755
--- a/t/t4015-diff-whitespace.sh
+++ b/t/t4015-diff-whitespace.sh
@@ -874,9 +874,9 @@ test_expect_success 'diff that introduces a line with only tabs' '
 	<BOLD>+++ b/x<RESET>
 	<CYAN>@@ -1 +1,4 @@<RESET>
 	 test<RESET>
-	<GREEN>+<RESET><GREEN>{<RESET>
+	<GREEN>+{<RESET>
 	<GREEN>+<RESET><BLUE>	<RESET>
-	<GREEN>+<RESET><GREEN>}<RESET>
+	<GREEN>+}<RESET>
 	EOF
 
 	test_cmp expected current
@@ -906,8 +906,8 @@ test_expect_success 'diff that introduces and removes ws breakages' '
 	<CYAN>@@ -1,2 +1,3 @@<RESET>
 	 0. blank-at-eol <RESET>
 	<RED>-1. blank-at-eol <RESET>
-	<GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET>
-	<GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>
+	<GREEN>+1. still-blank-at-eol<RESET><BLUE> <RESET>
+	<GREEN>+2. and a new line<RESET><BLUE> <RESET>
 	EOF
 
 	test_cmp expected current
@@ -934,9 +934,9 @@ test_expect_success 'ws-error-highlight test setup' '
 	<BOLD>+++ b/x<RESET>
 	<CYAN>@@ -1,2 +1,3 @@<RESET>
 	 0. blank-at-eol <RESET>
-	<RED>-<RESET><RED>1. blank-at-eol<RESET><BLUE> <RESET>
-	<GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET>
-	<GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>
+	<RED>-1. blank-at-eol<RESET><BLUE> <RESET>
+	<GREEN>+1. still-blank-at-eol<RESET><BLUE> <RESET>
+	<GREEN>+2. and a new line<RESET><BLUE> <RESET>
 	EOF
 
 	cat >expect.all <<-\EOF &&
@@ -946,9 +946,9 @@ test_expect_success 'ws-error-highlight test setup' '
 	<BOLD>+++ b/x<RESET>
 	<CYAN>@@ -1,2 +1,3 @@<RESET>
 	 0. blank-at-eol<RESET><BLUE> <RESET>
-	<RED>-<RESET><RED>1. blank-at-eol<RESET><BLUE> <RESET>
-	<GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET>
-	<GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>
+	<RED>-1. blank-at-eol<RESET><BLUE> <RESET>
+	<GREEN>+1. still-blank-at-eol<RESET><BLUE> <RESET>
+	<GREEN>+2. and a new line<RESET><BLUE> <RESET>
 	EOF
 
 	cat >expect.none <<-\EOF
@@ -1038,11 +1038,11 @@ test_expect_success 'detect moved code, complete file' '
 	<BOLD>--- /dev/null<RESET>
 	<BOLD>+++ b/main.c<RESET>
 	<CYAN>@@ -0,0 +1,5 @@<RESET>
-	<BGREEN>+<RESET><BGREEN>#include<stdio.h><RESET>
-	<BGREEN>+<RESET><BGREEN>main()<RESET>
-	<BGREEN>+<RESET><BGREEN>{<RESET>
-	<BGREEN>+<RESET><BGREEN>printf("Hello World");<RESET>
-	<BGREEN>+<RESET><BGREEN>}<RESET>
+	<BGREEN>+#include<stdio.h><RESET>
+	<BGREEN>+main()<RESET>
+	<BGREEN>+{<RESET>
+	<BGREEN>+printf("Hello World");<RESET>
+	<BGREEN>+}<RESET>
 	<BOLD>diff --git a/test.c b/test.c<RESET>
 	<BOLD>deleted file mode 100644<RESET>
 	<BOLD>index a986c57..0000000<RESET>
@@ -1159,12 +1159,12 @@ test_expect_success 'detect malicious moved code, inside file' '
 	 printf("Hello World, but different\n");<RESET>
 	 }<RESET>
 	 
-	<BGREEN>+<RESET><BGREEN>int secure_foo(struct user *u)<RESET>
-	<BGREEN>+<RESET><BGREEN>{<RESET>
-	<GREEN>+<RESET><GREEN>foo(u);<RESET>
-	<BGREEN>+<RESET><BGREEN>if (!u->is_allowed_foo)<RESET>
-	<BGREEN>+<RESET><BGREEN>return;<RESET>
-	<GREEN>+<RESET><GREEN>}<RESET>
+	<BGREEN>+int secure_foo(struct user *u)<RESET>
+	<BGREEN>+{<RESET>
+	<GREEN>+foo(u);<RESET>
+	<BGREEN>+if (!u->is_allowed_foo)<RESET>
+	<BGREEN>+return;<RESET>
+	<GREEN>+}<RESET>
 	<GREEN>+<RESET>
 	 int another_function()<RESET>
 	 {<RESET>
@@ -1208,12 +1208,12 @@ test_expect_success 'plain moved code, inside file' '
 	 printf("Hello World, but different\n");<RESET>
 	 }<RESET>
 	 
-	<BGREEN>+<RESET><BGREEN>int secure_foo(struct user *u)<RESET>
-	<BGREEN>+<RESET><BGREEN>{<RESET>
-	<BGREEN>+<RESET><BGREEN>foo(u);<RESET>
-	<BGREEN>+<RESET><BGREEN>if (!u->is_allowed_foo)<RESET>
-	<BGREEN>+<RESET><BGREEN>return;<RESET>
-	<BGREEN>+<RESET><BGREEN>}<RESET>
+	<BGREEN>+int secure_foo(struct user *u)<RESET>
+	<BGREEN>+{<RESET>
+	<BGREEN>+foo(u);<RESET>
+	<BGREEN>+if (!u->is_allowed_foo)<RESET>
+	<BGREEN>+return;<RESET>
+	<BGREEN>+}<RESET>
 	<BGREEN>+<RESET>
 	 int another_function()<RESET>
 	 {<RESET>
@@ -1288,12 +1288,12 @@ test_expect_success 'detect permutations inside moved code -- dimmed_zebra' '
 	 line 7<RESET>
 	 line 8<RESET>
 	 line 9<RESET>
-	<BCYAN>+<RESET><BCYAN>long line 1<RESET>
-	<BCYAN>+<RESET><BCYAN>long line 2<RESET>
-	<CYAN>+<RESET><CYAN>long line 3<RESET>
-	<YELLOW>+<RESET><YELLOW>long line 14<RESET>
-	<BYELLOW>+<RESET><BYELLOW>long line 15<RESET>
-	<BYELLOW>+<RESET><BYELLOW>long line 16<RESET>
+	<BCYAN>+long line 1<RESET>
+	<BCYAN>+long line 2<RESET>
+	<CYAN>+long line 3<RESET>
+	<YELLOW>+long line 14<RESET>
+	<BYELLOW>+long line 15<RESET>
+	<BYELLOW>+long line 16<RESET>
 	 line 10<RESET>
 	 line 11<RESET>
 	 line 12<RESET>
@@ -1332,12 +1332,12 @@ test_expect_success 'cmd option assumes configured colored-moved' '
 	 line 7<RESET>
 	 line 8<RESET>
 	 line 9<RESET>
-	<CYAN>+<RESET><CYAN>long line 1<RESET>
-	<CYAN>+<RESET><CYAN>long line 2<RESET>
-	<CYAN>+<RESET><CYAN>long line 3<RESET>
-	<YELLOW>+<RESET><YELLOW>long line 14<RESET>
-	<YELLOW>+<RESET><YELLOW>long line 15<RESET>
-	<YELLOW>+<RESET><YELLOW>long line 16<RESET>
+	<CYAN>+long line 1<RESET>
+	<CYAN>+long line 2<RESET>
+	<CYAN>+long line 3<RESET>
+	<YELLOW>+long line 14<RESET>
+	<YELLOW>+long line 15<RESET>
+	<YELLOW>+long line 16<RESET>
 	 line 10<RESET>
 	 line 11<RESET>
 	 line 12<RESET>
@@ -1467,10 +1467,10 @@ test_expect_success 'move detection ignoring whitespace changes' '
 	<BOLD>--- a/lines.txt<RESET>
 	<BOLD>+++ b/lines.txt<RESET>
 	<CYAN>@@ -1,9 +1,9 @@<RESET>
-	<GREEN>+<RESET><GREEN>long	line 6<RESET>
-	<GREEN>+<RESET><GREEN>long	line 7<RESET>
-	<GREEN>+<RESET><GREEN>long	line 8<RESET>
-	<GREEN>+<RESET><GREEN>long li	ne 9<RESET>
+	<GREEN>+long	line 6<RESET>
+	<GREEN>+long	line 7<RESET>
+	<GREEN>+long	line 8<RESET>
+	<GREEN>+long li	ne 9<RESET>
 	 line 1<RESET>
 	 line 2<RESET>
 	 line 3<RESET>
@@ -1491,10 +1491,10 @@ test_expect_success 'move detection ignoring whitespace changes' '
 	<BOLD>--- a/lines.txt<RESET>
 	<BOLD>+++ b/lines.txt<RESET>
 	<CYAN>@@ -1,9 +1,9 @@<RESET>
-	<CYAN>+<RESET><CYAN>long	line 6<RESET>
-	<CYAN>+<RESET><CYAN>long	line 7<RESET>
-	<CYAN>+<RESET><CYAN>long	line 8<RESET>
-	<GREEN>+<RESET><GREEN>long li	ne 9<RESET>
+	<CYAN>+long	line 6<RESET>
+	<CYAN>+long	line 7<RESET>
+	<CYAN>+long	line 8<RESET>
+	<GREEN>+long li	ne 9<RESET>
 	 line 1<RESET>
 	 line 2<RESET>
 	 line 3<RESET>
@@ -1534,10 +1534,10 @@ test_expect_success 'move detection ignoring whitespace at eol' '
 	<BOLD>--- a/lines.txt<RESET>
 	<BOLD>+++ b/lines.txt<RESET>
 	<CYAN>@@ -1,9 +1,9 @@<RESET>
-	<GREEN>+<RESET><GREEN>long line 6	<RESET>
-	<GREEN>+<RESET><GREEN>long line 7	<RESET>
-	<GREEN>+<RESET><GREEN>long line 8	<RESET>
-	<GREEN>+<RESET><GREEN>long	line 9	<RESET>
+	<GREEN>+long line 6	<RESET>
+	<GREEN>+long line 7	<RESET>
+	<GREEN>+long line 8	<RESET>
+	<GREEN>+long	line 9	<RESET>
 	 line 1<RESET>
 	 line 2<RESET>
 	 line 3<RESET>
@@ -1558,10 +1558,10 @@ test_expect_success 'move detection ignoring whitespace at eol' '
 	<BOLD>--- a/lines.txt<RESET>
 	<BOLD>+++ b/lines.txt<RESET>
 	<CYAN>@@ -1,9 +1,9 @@<RESET>
-	<CYAN>+<RESET><CYAN>long line 6	<RESET>
-	<CYAN>+<RESET><CYAN>long line 7	<RESET>
-	<CYAN>+<RESET><CYAN>long line 8	<RESET>
-	<GREEN>+<RESET><GREEN>long	line 9	<RESET>
+	<CYAN>+long line 6	<RESET>
+	<CYAN>+long line 7	<RESET>
+	<CYAN>+long line 8	<RESET>
+	<GREEN>+long	line 9	<RESET>
 	 line 1<RESET>
 	 line 2<RESET>
 	 line 3<RESET>
@@ -1605,7 +1605,7 @@ test_expect_success '--color-moved block at end of diff output respects MIN_ALNU
 	<BOLD>--- a/bar<RESET>
 	<BOLD>+++ b/bar<RESET>
 	<CYAN>@@ -0,0 +1 @@<RESET>
-	<GREEN>+<RESET><GREEN>line1<RESET>
+	<GREEN>+line1<RESET>
 	<BOLD>diff --git a/foo b/foo<RESET>
 	<BOLD>--- a/foo<RESET>
 	<BOLD>+++ b/foo<RESET>
@@ -1644,8 +1644,8 @@ test_expect_success '--color-moved respects MIN_ALNUM_COUNT' '
 	<BOLD>--- a/bar<RESET>
 	<BOLD>+++ b/bar<RESET>
 	<CYAN>@@ -0,0 +1,2 @@<RESET>
-	<BOLD;CYAN>+<RESET><BOLD;CYAN>twenty chars 234567890<RESET>
-	<GREEN>+<RESET><GREEN>nineteen chars 456789<RESET>
+	<BOLD;CYAN>+twenty chars 234567890<RESET>
+	<GREEN>+nineteen chars 456789<RESET>
 	<BOLD>diff --git a/foo b/foo<RESET>
 	<BOLD>--- a/foo<RESET>
 	<BOLD>+++ b/foo<RESET>
@@ -1685,9 +1685,9 @@ test_expect_success '--color-moved treats adjacent blocks as separate for MIN_AL
 	<BOLD>--- a/bar<RESET>
 	<BOLD>+++ b/bar<RESET>
 	<CYAN>@@ -0,0 +1,3 @@<RESET>
-	<GREEN>+<RESET><GREEN>7charsB<RESET>
-	<GREEN>+<RESET><GREEN>7charsC<RESET>
-	<GREEN>+<RESET><GREEN>7charsA<RESET>
+	<GREEN>+7charsB<RESET>
+	<GREEN>+7charsC<RESET>
+	<GREEN>+7charsA<RESET>
 	<BOLD>diff --git a/foo b/foo<RESET>
 	<BOLD>--- a/foo<RESET>
 	<BOLD>+++ b/foo<RESET>
diff --git a/ws.c b/ws.c
index a07caedd5a5..cb4a95c25dc 100644
--- a/ws.c
+++ b/ws.c
@@ -139,10 +139,19 @@ char *whitespace_error_string(unsigned ws)
 	return strbuf_detach(&err, NULL);
 }
 
+static inline void optional_reset(FILE *stream, const char *reset, int *already_set)
+{
+	if (*already_set) {
+		fputs(reset, stream);
+		*already_set = 0;
+	}
+}
+
 /* If stream is non-NULL, emits the line after checking. */
 static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,
 				FILE *stream, const char *set,
-				const char *reset, const char *ws)
+				const char *reset, const char *ws,
+				int already_set)
 {
 	unsigned result = 0;
 	int written = 0;
@@ -186,6 +195,7 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,
 		if ((ws_rule & WS_SPACE_BEFORE_TAB) && written < i) {
 			result |= WS_SPACE_BEFORE_TAB;
 			if (stream) {
+				optional_reset(stream, reset, &already_set);
 				fputs(ws, stream);
 				fwrite(line + written, i - written, 1, stream);
 				fputs(reset, stream);
@@ -194,12 +204,14 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,
 		} else if (ws_rule & WS_TAB_IN_INDENT) {
 			result |= WS_TAB_IN_INDENT;
 			if (stream) {
+				optional_reset(stream, reset, &already_set);
 				fwrite(line + written, i - written, 1, stream);
 				fputs(ws, stream);
 				fwrite(line + i, 1, 1, stream);
 				fputs(reset, stream);
 			}
 		} else if (stream) {
+			optional_reset(stream, reset, &already_set);
 			fwrite(line + written, i - written + 1, 1, stream);
 		}
 		written = i + 1;
@@ -209,6 +221,7 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,
 	if ((ws_rule & WS_INDENT_WITH_NON_TAB) && i - written >= ws_tab_width(ws_rule)) {
 		result |= WS_INDENT_WITH_NON_TAB;
 		if (stream) {
+			optional_reset(stream, reset, &already_set);
 			fputs(ws, stream);
 			fwrite(line + written, i - written, 1, stream);
 			fputs(reset, stream);
@@ -224,19 +237,24 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,
 
 		/* Emit non-highlighted (middle) segment. */
 		if (trailing_whitespace - written > 0) {
-			fputs(set, stream);
+			if (!already_set)
+				fputs(set, stream);
 			fwrite(line + written,
 			    trailing_whitespace - written, 1, stream);
 			fputs(reset, stream);
+			already_set = 0;
 		}
 
 		/* Highlight errors in trailing whitespace. */
 		if (trailing_whitespace != len) {
+			optional_reset(stream, reset, &already_set);
 			fputs(ws, stream);
 			fwrite(line + trailing_whitespace,
 			    len - trailing_whitespace, 1, stream);
 			fputs(reset, stream);
 		}
+		if (already_set)
+			fputs(reset, stream);
 		if (trailing_carriage_return)
 			fputc('\r', stream);
 		if (trailing_newline)
@@ -247,14 +265,14 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,
 
 void ws_check_emit(const char *line, int len, unsigned ws_rule,
 		   FILE *stream, const char *set,
-		   const char *reset, const char *ws)
+		   const char *reset, const char *ws, int already_set)
 {
-	(void)ws_check_emit_1(line, len, ws_rule, stream, set, reset, ws);
+	(void)ws_check_emit_1(line, len, ws_rule, stream, set, reset, ws, already_set);
 }
 
 unsigned ws_check(const char *line, int len, unsigned ws_rule)
 {
-	return ws_check_emit_1(line, len, ws_rule, NULL, NULL, NULL, NULL);
+	return ws_check_emit_1(line, len, ws_rule, NULL, NULL, NULL, NULL, 0);
 }
 
 int ws_blank_line(const char *line, int len, unsigned ws_rule)
-- 
2.18.0.203.gfac676dfb9-goog


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-04-30 21:54     ` [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems Johannes Schindelin via GitGitGadget
  2018-07-06 22:43       ` Junio C Hamano
@ 2018-07-11 10:07       ` SZEDER Gábor
  2018-07-12 15:11         ` Johannes Schindelin
  1 sibling, 1 reply; 387+ messages in thread
From: SZEDER Gábor @ 2018-07-11 10:07 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: SZEDER Gábor, git, Junio C Hamano, Johannes Schindelin

> diff --git a/linear-assignment.c b/linear-assignment.c
> new file mode 100644
> index 000000000..0b0344b5f
> --- /dev/null
> +++ b/linear-assignment.c
> @@ -0,0 +1,203 @@
> +/*
> + * Based on: Jonker, R., & Volgenant, A. (1987). <i>A shortest augmenting path
> + * algorithm for dense and sparse linear assignment problems</i>. Computing,
> + * 38(4), 325-340.
> + */
> +#include "cache.h"
> +#include "linear-assignment.h"
> +
> +#define COST(column, row) cost[(column) + column_count * (row)]
> +
> +/*
> + * The parameter `cost` is the cost matrix: the cost to assign column j to row
> + * i is `cost[j + column_count * i].
> + */
> +void compute_assignment(int column_count, int row_count, int *cost,
> +			int *column2row, int *row2column)
> +{

[...]

> +update:
> +		/* updating of the column pieces */
> +		for (k = 0; k < last; k++) {
> +			int j1 = col[k];
> +			v[j1] += d[j1] - min;
> +		}
> +
> +		/* augmentation */
> +		do {
> +			if (j < 0)
> +				BUG("negative j: %d", j);
> +			i = pred[j];
> +			column2row[j] = i;
> +			k = j;
> +			j = row2column[i];
> +			row2column[i] = k;

Coccinelle suggests using SWAP(j, row2column[i]) instead of the last
three lines above.
It's more idiomatic, and it avoids (ab)using the 'k' variable
(elsewhere used as loop variable) as a temporary variable.

> +		} while (i1 != i);
> +	}
> +
> +	free(col);
> +	free(pred);
> +	free(d);
> +	free(v);
> +	free(free_row);
> +}

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-09 22:08                 ` refs/notes/amlog problems, was " Johannes Schindelin
@ 2018-07-11 16:12                   ` Junio C Hamano
  2018-07-12 15:23                     ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-07-11 16:12 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Johannes Schindelin via GitGitGadget, git

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> To summarize, there are two commits recorded for that Message-Id, the
> later one not mapped back, and neither is the correct commit that made it
> into `master`.
>
> It would be nice to figure out what went wrong there, and how to fix it
> for the future (and also to fix up the existing mis-mappings in `amlog`).

I think what happened is that I used to have post-rewrite, but
because it did not solve the real issue of multiple commits existing
for the same message ID (either because of amending, or because of
running "am" multiple times while looking for the best base to
contruct a topic branch for the series that contains it) *and* the
one that will eventually used in the final history may not be the
last one (e.g. I may "am" twice to see if an older base I use in my
second attempt is a better one than the base I originally used, and
the patches may even apply cleanly to the older history, but may
turn out to need semantic adjustment, at which point I would discard
that second attempt and use the old commit from the first attempt
that built on a newer base), I stopped using it.

The mid-to-commit, for it to be relialble, needs to keep mapping for
all the commits created from a single message, instead of being the
last-one-survives mapping.  I just didn't have that much interest
back when I decided it was not worth and dropped the post-rewrite, I
think.


^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-11 10:07       ` SZEDER Gábor
@ 2018-07-12 15:11         ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-12 15:11 UTC (permalink / raw)
  To: SZEDER Gábor
  Cc: Johannes Schindelin via GitGitGadget, git, Junio C Hamano

[-- Attachment #1: Type: text/plain, Size: 1628 bytes --]

Hi Gábor,

On Wed, 11 Jul 2018, SZEDER Gábor wrote:

> > diff --git a/linear-assignment.c b/linear-assignment.c
> > new file mode 100644
> > index 000000000..0b0344b5f
> > --- /dev/null
> > +++ b/linear-assignment.c
> > @@ -0,0 +1,203 @@
> > +/*
> > + * Based on: Jonker, R., & Volgenant, A. (1987). <i>A shortest augmenting path
> > + * algorithm for dense and sparse linear assignment problems</i>. Computing,
> > + * 38(4), 325-340.
> > + */
> > +#include "cache.h"
> > +#include "linear-assignment.h"
> > +
> > +#define COST(column, row) cost[(column) + column_count * (row)]
> > +
> > +/*
> > + * The parameter `cost` is the cost matrix: the cost to assign column j to row
> > + * i is `cost[j + column_count * i].
> > + */
> > +void compute_assignment(int column_count, int row_count, int *cost,
> > +			int *column2row, int *row2column)
> > +{
> 
> [...]
> 
> > +update:
> > +		/* updating of the column pieces */
> > +		for (k = 0; k < last; k++) {
> > +			int j1 = col[k];
> > +			v[j1] += d[j1] - min;
> > +		}
> > +
> > +		/* augmentation */
> > +		do {
> > +			if (j < 0)
> > +				BUG("negative j: %d", j);
> > +			i = pred[j];
> > +			column2row[j] = i;
> > +			k = j;
> > +			j = row2column[i];
> > +			row2column[i] = k;
> 
> Coccinelle suggests using SWAP(j, row2column[i]) instead of the last
> three lines above.
> It's more idiomatic, and it avoids (ab)using the 'k' variable
> (elsewhere used as loop variable) as a temporary variable.

Good point.

I audited the rest of the code in this file, and there are no more swap
operations.

Thanks,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-11 16:12                   ` Junio C Hamano
@ 2018-07-12 15:23                     ` Johannes Schindelin
  2018-07-12 16:59                       ` Junio C Hamano
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-12 15:23 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Schindelin via GitGitGadget, git

Hi Junio,

On Wed, 11 Jul 2018, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > To summarize, there are two commits recorded for that Message-Id, the
> > later one not mapped back, and neither is the correct commit that made it
> > into `master`.
> >
> > It would be nice to figure out what went wrong there, and how to fix it
> > for the future (and also to fix up the existing mis-mappings in `amlog`).
> 
> I think what happened is that I used to have post-rewrite, but
> because it did not solve the real issue of multiple commits existing
> for the same message ID (either because of amending, or because of
> running "am" multiple times while looking for the best base to
> contruct a topic branch for the series that contains it) *and* the
> one that will eventually used in the final history may not be the
> last one (e.g. I may "am" twice to see if an older base I use in my
> second attempt is a better one than the base I originally used, and
> the patches may even apply cleanly to the older history, but may
> turn out to need semantic adjustment, at which point I would discard
> that second attempt and use the old commit from the first attempt
> that built on a newer base), I stopped using it.
> 
> The mid-to-commit, for it to be relialble, needs to keep mapping for
> all the commits created from a single message, instead of being the
> last-one-survives mapping.  I just didn't have that much interest
> back when I decided it was not worth and dropped the post-rewrite, I
> think.

I would like to ask you to reinstate the post-rewrite hook, as it still
improves the situation over the current one.

Of course, it would be nice to get the automation into a shape where
the mappings in `refs/notes/amlog` of commits that hit `next` are fixed,
if necessary, to stop referring to commits that did not make it into
`next`.

Because the *concept* of `amlog` is quite useful, to put back at least
*some* of the information we lost by transiting Git commits via mails
without any connection to their original commits. It is still the most
annoying thing when I contribute patches myself.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-12 15:23                     ` Johannes Schindelin
@ 2018-07-12 16:59                       ` Junio C Hamano
  2018-07-19 17:06                         ` Junio C Hamano
  0 siblings, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-07-12 16:59 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Johannes Schindelin via GitGitGadget, git

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> I would like to ask you to reinstate the post-rewrite hook, as it still
> improves the situation over the current one.

Without post-rewrite I seem to be getting correct amlog entries for
commits created by "git rebase"; do our rebase--am backend still
trigger post-applypatch hook in its "am" phase to apply the patches
created with "format-patch"?


^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 03/20] range-diff: first rudimentary implementation
  2018-05-02  0:34     ` [PATCH v3 03/20] range-diff: first rudimentary implementation Johannes Schindelin via GitGitGadget
@ 2018-07-16  6:55       ` Eric Sunshine
  2018-07-17  9:53         ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Eric Sunshine @ 2018-07-16  6:55 UTC (permalink / raw)
  To: gitgitgadget; +Cc: Git List, Junio C Hamano, Johannes Schindelin

On Tue, Jul 3, 2018 at 7:27 AM Johannes Schindelin via GitGitGadget
<gitgitgadget@gmail.com> wrote:
> At this stage, `git range-diff` can determine corresponding commits
> of two related commit ranges. This makes use of the recently introduced
> implementation of the Hungarian algorithm.

Did you want s/Hungarian/Jonker-Volgenant/ here? (Not worth a re-roll.)

> The core of this patch is a straight port of the ideas of tbdiff, the
> apparently dormant project at https://github.com/trast/tbdiff.
> [...]
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
> diff --git a/builtin/range-diff.c b/builtin/range-diff.c
> @@ -17,9 +18,49 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
> +       int res = 0;
> +       struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
>
> -       argc = parse_options(argc, argv, NULL, options,
> -                            builtin_range_diff_usage, 0);
> +       argc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,
> +                            0);

This parse_options() change appears to be merely a re-wrapping of the
line between patches 2 and 3.

> -       return 0;
> +       if (argc == 2) {
> +               if (!strstr(argv[0], ".."))
> +                       warning(_("no .. in range: '%s'"), argv[0]);
> +               strbuf_addstr(&range1, argv[0]);
> +
> +               if (!strstr(argv[1], ".."))
> +                       warning(_("no .. in range: '%s'"), argv[1]);
> +               strbuf_addstr(&range2, argv[1]);

Should these die() (like the "..." case below) rather than warning()?
Warning and continuing doesn't seem like intended behavior. When I
test this with on git.git and omit the "..", git sits for a long, long
time consuming the CPU. I guess it's git-log'ing pretty much the
entire history.

    % GIT_TRACE=1 git range-diff v1 v2
    warning: no .. in range: 'v1'
    warning: no .. in range: 'v2'
    trace: git log --no-color -p --no-merges --reverse \
        --date-order --decorate=no --no-abbrev-commit v1
    ^C
    %

> +       } else if (argc == 3) {
> +               strbuf_addf(&range1, "%s..%s", argv[0], argv[1]);
> +               strbuf_addf(&range2, "%s..%s", argv[0], argv[2]);
> +       } else if (argc == 1) {
> +               const char *b = strstr(argv[0], "..."), *a = argv[0];
> +               int a_len;
> +
> +               if (!b)
> +                       die(_("single arg format requires a symmetric range"));
> diff --git a/range-diff.c b/range-diff.c
> @@ -0,0 +1,307 @@
> +static int read_patches(const char *range, struct string_list *list)
> +{
> +       while (strbuf_getline(&line, in) != EOF) {
> +               if (skip_prefix(line.buf, "commit ", &p)) {
> +                       [...]
> +                       in_header = 1;
> +                       continue;
> +               }
> +               if (starts_with(line.buf, "diff --git")) {
> +                       in_header = 0;
> +                       [...]
> +               } else if (in_header) {
> +                       if (starts_with(line.buf, "Author: ")) {
> +                               [...]
> +                       } else if (starts_with(line.buf, "    ")) {
> +                               [...]
> +                       }
> +                       continue;
> +               } else if (starts_with(line.buf, "@@ "))
> +                       strbuf_addstr(&buf, "@@");
> +               else if (line.buf[0] && !starts_with(line.buf, "index "))
> +                       /*
> +                        * A completely blank (not ' \n', which is context)
> +                        * line is not valid in a diff.  We skip it
> +                        * silently, because this neatly handles the blank
> +                        * separator line between commits in git-log
> +                        * output.
> +                        */
> +                       strbuf_addbuf(&buf, &line);

This comment had me confused for a bit since it doesn't seem to agree
with the 'then' part of the 'if', but rather applies more to the
'else'.  Had it been split into two parts (one for 'then' and one for
'else'), it might have been easier to digest. That is, something like:

    else if (line.buf[0] && !starts_with(..., "index "))
        /* A line we wish to keep. */
        strbuf_addbuf(...);
    else
        /*
         * A completely blank line between commits or
         * or one in which we are otherwise not interested.
         */
        continue;

or something. Structuring it a bit differently might have helped, as well:

    else if (!line.buf[0])
        /* A completely blank line between commits. */
        continue;
    else if (starts_with(..., "index "))
        /* A line in which we are not interested. */
        continue;
    else
        strbuf_addbuf(&buf, &line);

Not at all worth a re-roll.

> +               else
> +                       continue;
> +       if (util)
> +               string_list_append(list, buf.buf)->util = util;

So, the parser is grabbing each commit and shoving all the
"interesting" information about the commit in a 'patch_util'. It grabs
the OID, author, the commit message (indented), the "diff --git",
"+++", "---" lines (but ignores "index" line), "@@" lines (but
ignoring the gunk after "@@"), and all context and patch lines.

Looks good.

> +       strbuf_release(&buf);
> +
> +       if (finish_command(&cp))
> +               return -1;
> +
> +       return 0;
> +}

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 09/20] range-diff: adjust the output of the commit pairs
  2018-05-02 21:35     ` [PATCH v3 09/20] range-diff: adjust the output of the commit pairs Johannes Schindelin via GitGitGadget
@ 2018-07-16  7:21       ` Eric Sunshine
  2018-07-17 16:24         ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Eric Sunshine @ 2018-07-16  7:21 UTC (permalink / raw)
  To: gitgitgadget; +Cc: Git List, Junio C Hamano, Johannes Schindelin

On Tue, Jul 3, 2018 at 7:26 AM Johannes Schindelin via GitGitGadget
<gitgitgadget@gmail.com> wrote:
> This change brings `git range-diff` yet another step closer to
> feature parity with tbdiff: it now shows the oneline, too, and indicates
> with `=` when the commits have identical diffs.
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
> diff --git a/range-diff.c b/range-diff.c
> @@ -251,9 +253,57 @@ static void get_correspondences(struct string_list *a, struct string_list *b,
> +static void output_pair_header(struct strbuf *buf,
> +                              struct patch_util *a_util,
> +                              struct patch_util *b_util)
>  {
> -       return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
> +       static char *dashes;
> +       struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
> +       struct commit *commit;
> +
> +       if (!dashes) {
> +               char *p;
> +
> +               dashes = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV));

It's nice to see that the bulk of the range-diff functionality has
been libified in this re-roll (residing in range-diff.c rather than
builtin/range-diff.c as in earlier versions), so it's somewhat
surprising to see libified code holding onto the 'dashes' buffer like
this in a static variable. An alternative would have been for the
caller to pass in the same buffer to output_pair_header() for re-use,
and then dispose of it at the end of processing.

> +               for (p = dashes; *p; p++)
> +                       *p = '-';
> +       }
> +
> +       strbuf_reset(buf);

...much like 'buf' is allocated by the caller, passed in and re-used
for each invocation, then released by the caller at the end.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 11/20] range-diff: add tests
  2018-05-02 15:19     ` [PATCH v3 11/20] range-diff: add tests Thomas Rast via GitGitGadget
@ 2018-07-16  7:28       ` Eric Sunshine
  2018-07-17 16:28         ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Eric Sunshine @ 2018-07-16  7:28 UTC (permalink / raw)
  To: gitgitgadget; +Cc: Git List, Junio C Hamano, Thomas Rast

On Tue, Jul 3, 2018 at 7:26 AM Thomas Rast via GitGitGadget
<gitgitgadget@gmail.com> wrote:
> These are essentially lifted from https://github.com/trast/tbdiff, with
> light touch-ups to account for the command now being an option of `git
> branch`.

The "option of `git branch`" mention is outdated. Perhaps just drop
everything after "...touch-ups" (or mention "range-diff" instead).

> Apart from renaming `tbdiff` to `range-diff`, only one test case needed
> to be adjusted: 11 - 'changed message'.
> [...]
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
> diff --git a/t/.gitattributes b/t/.gitattributes
> @@ -18,5 +18,6 @@ t[0-9][0-9][0-9][0-9]/* -whitespace
>  /t7500/* eol=lf
> +/t7910/* eol=lf

Does this need to be changed to t3206?

>  /t8005/*.txt eol=lf
> diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
> new file mode 100755
> index 000000000..2237c7f4a
> --- /dev/null
> +++ b/t/t3206-range-diff.sh

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 17/20] range-diff: add a man page
  2018-05-03 13:50     ` [PATCH v3 17/20] range-diff: add a man page Johannes Schindelin via GitGitGadget
  2018-07-09 18:20       ` Stefan Beller
@ 2018-07-16  8:01       ` Eric Sunshine
  2018-07-17 16:39         ` Johannes Schindelin
  1 sibling, 1 reply; 387+ messages in thread
From: Eric Sunshine @ 2018-07-16  8:01 UTC (permalink / raw)
  To: gitgitgadget; +Cc: Git List, Junio C Hamano, Johannes Schindelin

On Tue, Jul 3, 2018 at 7:27 AM Johannes Schindelin via GitGitGadget
<gitgitgadget@gmail.com> wrote:
> The bulk of this patch consists of a heavily butchered version of
> tbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from
> https://github.com/trast/tbdiff.
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
> diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
> @@ -0,0 +1,235 @@
> +To that end, it first finds pairs of commits from both commit ranges
> +that correspond with each other. Two commits are said to correspond when
> +the diff between their patches (i.e. the author information, the commit
> +message and the commit diff) is reasonably small compared to the
> +patches' size. See ``Algorithm` below for details.

Unbalanced number of backticks on "Algorithm".

> +Finally, the list of matching commits is shown in the order of the
> +second commit range, with unmatched commits being inserted just after
> +all of their ancestors have been shown.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 20/20] range-diff: make --dual-color the default mode
  2018-06-30 20:41     ` [PATCH v3 20/20] range-diff: make --dual-color the default mode Johannes Schindelin via GitGitGadget
@ 2018-07-16  8:06       ` Eric Sunshine
  2018-07-17 16:40         ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Eric Sunshine @ 2018-07-16  8:06 UTC (permalink / raw)
  To: gitgitgadget; +Cc: Git List, Junio C Hamano, Johannes Schindelin

On Tue, Jul 3, 2018 at 7:26 AM Johannes Schindelin via GitGitGadget
<gitgitgadget@gmail.com> wrote:
> After using this command extensively for the last two months, this
> developer came to the conclusion that even if the dual color mode still
> leaves a lot of room for confusion what was actually changed, the

"...confusion _about_ what..."

> non-dual color mode is substantially worse in that regard.
>
> Therefore, we really want to make the dual color mode the default.
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
> diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
> @@ -31,11 +31,14 @@ all of their ancestors have been shown.
> ---dual-color::
> -       When the commit diffs differ, recreate the original diffs'
> -       coloring, and add outer -/+ diff markers with the *background*
> -       being red/green to make it easier to see e.g. when there was a
> -       change in what exact lines were added.
> +--no-dual-color::
> +       When the commit diffs differ, `git range-diff` recreates the
> +       original diffs' coloring, and add outer -/+ diff markers with

s/add/adds/

> +       the *background* being red/green to make it easier to see e.g.
> +       when there was a change in what exact lines were added. This is
> +       known to `range-diff` as "dual coloring". Use `--no-dual-color`
> +       to revert to color all lines according to the outer diff markers
> +       (and completely ignore the inner diff when it comes to color).

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 03/20] range-diff: first rudimentary implementation
  2018-07-16  6:55       ` Eric Sunshine
@ 2018-07-17  9:53         ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-17  9:53 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: gitgitgadget, Git List, Junio C Hamano

Hi Eric,

On Mon, 16 Jul 2018, Eric Sunshine wrote:

> On Tue, Jul 3, 2018 at 7:27 AM Johannes Schindelin via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
> > At this stage, `git range-diff` can determine corresponding commits
> > of two related commit ranges. This makes use of the recently introduced
> > implementation of the Hungarian algorithm.
> 
> Did you want s/Hungarian/Jonker-Volgenant/ here? (Not worth a re-roll.)

It is worth a new iteration, and I'd rather say "linear assignment" than
either Hungarian or Jonker-Volgenant. Thanks for pointing this out.

> > The core of this patch is a straight port of the ideas of tbdiff, the
> > apparently dormant project at https://github.com/trast/tbdiff.
> > [...]
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> > diff --git a/builtin/range-diff.c b/builtin/range-diff.c
> > @@ -17,9 +18,49 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
> > +       int res = 0;
> > +       struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
> >
> > -       argc = parse_options(argc, argv, NULL, options,
> > -                            builtin_range_diff_usage, 0);
> > +       argc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,
> > +                            0);
> 
> This parse_options() change appears to be merely a re-wrapping of the
> line between patches 2 and 3.

True, and it is a bad change because it makes the line longer than 80
columns.

Fixed.

> > -       return 0;
> > +       if (argc == 2) {
> > +               if (!strstr(argv[0], ".."))
> > +                       warning(_("no .. in range: '%s'"), argv[0]);
> > +               strbuf_addstr(&range1, argv[0]);
> > +
> > +               if (!strstr(argv[1], ".."))
> > +                       warning(_("no .. in range: '%s'"), argv[1]);
> > +               strbuf_addstr(&range2, argv[1]);
> 
> Should these die() (like the "..." case below) rather than warning()?
> Warning and continuing doesn't seem like intended behavior. When I
> test this with on git.git and omit the "..", git sits for a long, long
> time consuming the CPU. I guess it's git-log'ing pretty much the
> entire history.

I had to go back to `git-tbdiff.py` to see how it handles this, and you
are right: it should die().

Fixed.

(Technically, it is conceivable that some user wants to compare two
independent commit histories, e.g. when a repository was imported from a
different SCM two times, independently. I guess when that happens, we can
always implement a `range-diff --root <tip1> <tip2>` or some such.)

>     % GIT_TRACE=1 git range-diff v1 v2
>     warning: no .. in range: 'v1'
>     warning: no .. in range: 'v2'
>     trace: git log --no-color -p --no-merges --reverse \
>         --date-order --decorate=no --no-abbrev-commit v1
>     ^C
>     %
> 
> > +       } else if (argc == 3) {
> > +               strbuf_addf(&range1, "%s..%s", argv[0], argv[1]);
> > +               strbuf_addf(&range2, "%s..%s", argv[0], argv[2]);
> > +       } else if (argc == 1) {
> > +               const char *b = strstr(argv[0], "..."), *a = argv[0];
> > +               int a_len;
> > +
> > +               if (!b)
> > +                       die(_("single arg format requires a symmetric range"));
> > diff --git a/range-diff.c b/range-diff.c
> > @@ -0,0 +1,307 @@
> > +static int read_patches(const char *range, struct string_list *list)
> > +{
> > +       while (strbuf_getline(&line, in) != EOF) {
> > +               if (skip_prefix(line.buf, "commit ", &p)) {
> > +                       [...]
> > +                       in_header = 1;
> > +                       continue;
> > +               }
> > +               if (starts_with(line.buf, "diff --git")) {
> > +                       in_header = 0;
> > +                       [...]
> > +               } else if (in_header) {
> > +                       if (starts_with(line.buf, "Author: ")) {
> > +                               [...]
> > +                       } else if (starts_with(line.buf, "    ")) {
> > +                               [...]
> > +                       }
> > +                       continue;
> > +               } else if (starts_with(line.buf, "@@ "))
> > +                       strbuf_addstr(&buf, "@@");
> > +               else if (line.buf[0] && !starts_with(line.buf, "index "))
> > +                       /*
> > +                        * A completely blank (not ' \n', which is context)
> > +                        * line is not valid in a diff.  We skip it
> > +                        * silently, because this neatly handles the blank
> > +                        * separator line between commits in git-log
> > +                        * output.
> > +                        */
> > +                       strbuf_addbuf(&buf, &line);
> 
> This comment had me confused for a bit since it doesn't seem to agree
> with the 'then' part of the 'if', but rather applies more to the
> 'else'.  Had it been split into two parts (one for 'then' and one for
> 'else'), it might have been easier to digest. That is, something like:
> 
>     else if (line.buf[0] && !starts_with(..., "index "))
>         /* A line we wish to keep. */
>         strbuf_addbuf(...);
>     else
>         /*
>          * A completely blank line between commits or
>          * or one in which we are otherwise not interested.
>          */
>         continue;
> 
> or something. Structuring it a bit differently might have helped, as well:
> 
>     else if (!line.buf[0])
>         /* A completely blank line between commits. */
>         continue;
>     else if (starts_with(..., "index "))
>         /* A line in which we are not interested. */
>         continue;
>     else
>         strbuf_addbuf(&buf, &line);

I like this much better, too.

> Not at all worth a re-roll.

I'll have to send a new iteration anyway, after digging into ws.c, I
think.

Also: I had this idea that dimming the "old" diff would make a ton of
sense, so I want to try that.

> > +               else
> > +                       continue;
> > +       if (util)
> > +               string_list_append(list, buf.buf)->util = util;
> 
> So, the parser is grabbing each commit and shoving all the
> "interesting" information about the commit in a 'patch_util'. It grabs
> the OID, author, the commit message (indented), the "diff --git",
> "+++", "---" lines (but ignores "index" line), "@@" lines (but
> ignoring the gunk after "@@"), and all context and patch lines.
> 
> Looks good.

Correct.

> > +       strbuf_release(&buf);
> > +
> > +       if (finish_command(&cp))
> > +               return -1;
> > +
> > +       return 0;
> > +}

Thank you for your suggestions!
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 09/20] range-diff: adjust the output of the commit pairs
  2018-07-16  7:21       ` Eric Sunshine
@ 2018-07-17 16:24         ` Johannes Schindelin
  2018-07-17 17:47           ` Stefan Beller
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-17 16:24 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: gitgitgadget, Git List, Junio C Hamano

Hi Eric,

On Mon, 16 Jul 2018, Eric Sunshine wrote:

> On Tue, Jul 3, 2018 at 7:26 AM Johannes Schindelin via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
> > This change brings `git range-diff` yet another step closer to
> > feature parity with tbdiff: it now shows the oneline, too, and indicates
> > with `=` when the commits have identical diffs.
> >
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> > diff --git a/range-diff.c b/range-diff.c
> > @@ -251,9 +253,57 @@ static void get_correspondences(struct string_list *a, struct string_list *b,
> > +static void output_pair_header(struct strbuf *buf,
> > +                              struct patch_util *a_util,
> > +                              struct patch_util *b_util)
> >  {
> > -       return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
> > +       static char *dashes;
> > +       struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
> > +       struct commit *commit;
> > +
> > +       if (!dashes) {
> > +               char *p;
> > +
> > +               dashes = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV));
> 
> It's nice to see that the bulk of the range-diff functionality has
> been libified in this re-roll (residing in range-diff.c rather than

Can we *please* stop calling it "re-roll"? Thanks.

(Or are you really "never gonna give you up, never gonna let you down"?)

> builtin/range-diff.c as in earlier versions), so it's somewhat
> surprising to see libified code holding onto the 'dashes' buffer like
> this in a static variable. An alternative would have been for the
> caller to pass in the same buffer to output_pair_header() for re-use,
> and then dispose of it at the end of processing.

Sure, to be honest, I had completely forgotten about what I did there, and
had to read up on it to fix it.

> 
> > +               for (p = dashes; *p; p++)
> > +                       *p = '-';
> > +       }
> > +
> > +       strbuf_reset(buf);
> 
> ...much like 'buf' is allocated by the caller, passed in and re-used
> for each invocation, then released by the caller at the end.

Yep, I now pass in another strbuf, `dashes`.

Thanks,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 11/20] range-diff: add tests
  2018-07-16  7:28       ` Eric Sunshine
@ 2018-07-17 16:28         ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-17 16:28 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: gitgitgadget, Git List, Junio C Hamano, Thomas Rast

Hi Eric,

On Mon, 16 Jul 2018, Eric Sunshine wrote:

> On Tue, Jul 3, 2018 at 7:26 AM Thomas Rast via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
> > These are essentially lifted from https://github.com/trast/tbdiff, with
> > light touch-ups to account for the command now being an option of `git
> > branch`.
> 
> The "option of `git branch`" mention is outdated. Perhaps just drop
> everything after "...touch-ups" (or mention "range-diff" instead).

Ah, the line break made my `grep` fail :-(

> > Apart from renaming `tbdiff` to `range-diff`, only one test case needed
> > to be adjusted: 11 - 'changed message'.
> > [...]
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> > diff --git a/t/.gitattributes b/t/.gitattributes
> > @@ -18,5 +18,6 @@ t[0-9][0-9][0-9][0-9]/* -whitespace
> >  /t7500/* eol=lf
> > +/t7910/* eol=lf
> 
> Does this need to be changed to t3206?

Absolutely.

> 
> >  /t8005/*.txt eol=lf
> > diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
> > new file mode 100755
> > index 000000000..2237c7f4a
> > --- /dev/null
> > +++ b/t/t3206-range-diff.sh

Thanks for your thorough review,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 17/20] range-diff: add a man page
  2018-07-16  8:01       ` Eric Sunshine
@ 2018-07-17 16:39         ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-17 16:39 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: gitgitgadget, Git List, Junio C Hamano

Hi Eric,

On Mon, 16 Jul 2018, Eric Sunshine wrote:

> On Tue, Jul 3, 2018 at 7:27 AM Johannes Schindelin via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
> > The bulk of this patch consists of a heavily butchered version of
> > tbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from
> > https://github.com/trast/tbdiff.
> >
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> > diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
> > @@ -0,0 +1,235 @@
> > +To that end, it first finds pairs of commits from both commit ranges
> > +that correspond with each other. Two commits are said to correspond when
> > +the diff between their patches (i.e. the author information, the commit
> > +message and the commit diff) is reasonably small compared to the
> > +patches' size. See ``Algorithm` below for details.
> 
> Unbalanced number of backticks on "Algorithm".

Of course!

Thanks,
Dscho

> 
> > +Finally, the list of matching commits is shown in the order of the
> > +second commit range, with unmatched commits being inserted just after
> > +all of their ancestors have been shown.
> 

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 20/20] range-diff: make --dual-color the default mode
  2018-07-16  8:06       ` Eric Sunshine
@ 2018-07-17 16:40         ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-17 16:40 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: gitgitgadget, Git List, Junio C Hamano

Hi Eric,

On Mon, 16 Jul 2018, Eric Sunshine wrote:

> On Tue, Jul 3, 2018 at 7:26 AM Johannes Schindelin via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
> > After using this command extensively for the last two months, this
> > developer came to the conclusion that even if the dual color mode still
> > leaves a lot of room for confusion what was actually changed, the
> 
> "...confusion _about_ what..."
> 
> > non-dual color mode is substantially worse in that regard.
> >
> > Therefore, we really want to make the dual color mode the default.
> >
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> > diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
> > @@ -31,11 +31,14 @@ all of their ancestors have been shown.
> > ---dual-color::
> > -       When the commit diffs differ, recreate the original diffs'
> > -       coloring, and add outer -/+ diff markers with the *background*
> > -       being red/green to make it easier to see e.g. when there was a
> > -       change in what exact lines were added.
> > +--no-dual-color::
> > +       When the commit diffs differ, `git range-diff` recreates the
> > +       original diffs' coloring, and add outer -/+ diff markers with
> 
> s/add/adds/
> 
> > +       the *background* being red/green to make it easier to see e.g.
> > +       when there was a change in what exact lines were added. This is
> > +       known to `range-diff` as "dual coloring". Use `--no-dual-color`
> > +       to revert to color all lines according to the outer diff markers
> > +       (and completely ignore the inner diff when it comes to color).

Yep, thank you very much!
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 09/20] range-diff: adjust the output of the commit pairs
  2018-07-17 16:24         ` Johannes Schindelin
@ 2018-07-17 17:47           ` Stefan Beller
  2018-07-20 18:57             ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-07-17 17:47 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Eric Sunshine, gitgitgadget, git, Junio C Hamano

Hi Johannes,

> > It's nice to see that the bulk of the range-diff functionality has
> > been libified in this re-roll (residing in range-diff.c rather than
>
> Can we *please* stop calling it "re-roll"? Thanks.

Fun fact of the day:

First appearance of "reroll" in the public archive is (09 Dec 2007)
https://public-inbox.org/git/7vy7c3ogu2.fsf@gitster.siamese.dyndns.org/
which is predated by "re-roll" (05 May 2006)
https://public-inbox.org/git/7vr738w8t4.fsf@assigned-by-dhcp.cox.net/

Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-12 16:59                       ` Junio C Hamano
@ 2018-07-19 17:06                         ` Junio C Hamano
  2018-07-20 18:51                           ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-07-19 17:06 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Johannes Schindelin via GitGitGadget, git

Junio C Hamano <gitster@pobox.com> writes:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>
>> I would like to ask you to reinstate the post-rewrite hook, as it still
>> improves the situation over the current one.
>
> Without post-rewrite I seem to be getting correct amlog entries for
> commits created by "git rebase"; do our rebase--am backend still
> trigger post-applypatch hook in its "am" phase to apply the patches
> created with "format-patch"?

That was a wrong line of thought that led to a dead end.  format-patch
won't recreate Message-Id to its output from notes/amlog, so even if
the "format-patch --stdout | am" pipeline inside rebase-am triggered
the post-applypatch hook, it would not have a chance to carry the
notes forward that way.

What was really happening was I have

	$ git config --list | grep amlog
	notes.rewriteref=refs/notes/amlog

and that ought to be sufficient to carry "commit-to-original-msg-id"
entries across rebases.  And it seems to correctly work.  I however
suspect that "cherry-pick A..B" may lose the notes, but I haven't
checked.





^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-19 17:06                         ` Junio C Hamano
@ 2018-07-20 18:51                           ` Johannes Schindelin
  2018-07-20 19:34                             ` Junio C Hamano
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-20 18:51 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Schindelin via GitGitGadget, git

Hi Junio,

On Thu, 19 Jul 2018, Junio C Hamano wrote:

> Junio C Hamano <gitster@pobox.com> writes:
> 
> > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> >
> >> I would like to ask you to reinstate the post-rewrite hook, as it still
> >> improves the situation over the current one.
> >
> > Without post-rewrite I seem to be getting correct amlog entries for
> > commits created by "git rebase"; do our rebase--am backend still
> > trigger post-applypatch hook in its "am" phase to apply the patches
> > created with "format-patch"?
> 
> That was a wrong line of thought that led to a dead end.  format-patch
> won't recreate Message-Id to its output from notes/amlog, so even if
> the "format-patch --stdout | am" pipeline inside rebase-am triggered
> the post-applypatch hook, it would not have a chance to carry the
> notes forward that way.
> 
> What was really happening was I have
> 
> 	$ git config --list | grep amlog
> 	notes.rewriteref=refs/notes/amlog
> 
> and that ought to be sufficient to carry "commit-to-original-msg-id"
> entries across rebases.  And it seems to correctly work.  I however
> suspect that "cherry-pick A..B" may lose the notes, but I haven't
> checked.

AFAICT there is at least one scenario where you run `rebase -i`, the notes
get updated, and of course the *reverse mapping* does *not* get updated:
you have a mapping both from commit to Message-Id *and crucially* from
Message-Id to commit. The automatic rewrite of commit notes in `rebase -i`
tackles only the commit notes, obviously, not the reverse.

Hence the post-rewrite hook I think I already suggested at least once in a
previous reply.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 09/20] range-diff: adjust the output of the commit pairs
  2018-07-17 17:47           ` Stefan Beller
@ 2018-07-20 18:57             ` Johannes Schindelin
  2018-07-20 19:16               ` Stefan Beller
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-20 18:57 UTC (permalink / raw)
  To: Stefan Beller; +Cc: Eric Sunshine, gitgitgadget, git, Junio C Hamano

[-- Attachment #1: Type: text/plain, Size: 1861 bytes --]

Hi Stefan,

On Tue, 17 Jul 2018, Stefan Beller wrote:

> > > It's nice to see that the bulk of the range-diff functionality has
> > > been libified in this re-roll (residing in range-diff.c rather than
> >
> > Can we *please* stop calling it "re-roll"? Thanks.
> 
> Fun fact of the day:
> 
> First appearance of "reroll" in the public archive is (09 Dec 2007)
> https://public-inbox.org/git/7vy7c3ogu2.fsf@gitster.siamese.dyndns.org/
> which is predated by "re-roll" (05 May 2006)
> https://public-inbox.org/git/7vr738w8t4.fsf@assigned-by-dhcp.cox.net/

Real fun fact of the day:

https://en.wiktionary.org/wiki/reroll says

Verb

reroll (third-person singular simple present rerolls, present participle
rerolling, simple past and past participle rerolled)

    1. To roll again.

        A player who rolls two sixes can reroll the dice for an additional
	turn.

    2. (programming) To convert (an unrolled instruction sequence) back into
       a loop. quotations ▼

Noun

reroll (plural rerolls)

    (dice games) A situation in the rules of certain dice games where a
    player is given the option to reroll an undesirable roll of the dice.


You will notice how this does not list *any* hint at referring to
something that Junio calls "reroll".

Likewise, I have to admit that Wiktionary's idea of an "iteration"
disagrees with *my* use of the term.

The correct term would be "revision"
(https://en.wiktionary.org/wiki/revision). But we, the core Git
contributors, in our collective infinite wisdom, chose to use that term
as yet another way to refer to a commit [*1*].

So we got it all wrong, believe it or not.

Ciao,
Dscho

Footnote *1*: https://en.wiktionary.org/wiki/commit#Noun does not even
bother to acknowledge our use of referring to a snapshot of a source code
base as a "commit".

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 09/20] range-diff: adjust the output of the commit pairs
  2018-07-20 18:57             ` Johannes Schindelin
@ 2018-07-20 19:16               ` Stefan Beller
  2018-07-21 22:07                 ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-07-20 19:16 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Eric Sunshine, gitgitgadget, git, Junio C Hamano

>     1. To roll again.
>
>         A player who rolls two sixes can reroll the dice for an additional
>         turn.

This is where I had my AHA moment!
(Consider my software development process as chaotic as a dice roll
So rerolling is really just rolling the dice again to "get my patch
accepted" ;-)

>     2. (programming) To convert (an unrolled instruction sequence) back into
>        a loop. quotations ▼

We do not have unrolled loops?
This was good back in the day where the cost of each instruction weighted
heavy on the CPU, such that the JMPs that are needed (and the loop
variable check that might have had a bad branch prediction) for the loop were
slowing down the execution.

Nowadays (when I was studying 5 years ago) the branch prediction and individual
instruction execution are really good, but the bottleneck that I measured
(when I had a lot of time at my disposal and attending a class/project on micro
architectures), was the CPU instruction cache size, i.e. loop unrolling made the
code *slower* than keeping tight loops loaded in memory.
https://stackoverflow.com/questions/24196076/is-gcc-loop-unrolling-flag-really-effective

> Noun
>
> reroll (plural rerolls)
>
>     (dice games) A situation in the rules of certain dice games where a
>     player is given the option to reroll an undesirable roll of the dice.
>
>
> You will notice how this does not list *any* hint at referring to
> something that Junio calls "reroll".

We have undesirable patches that were 'rolled' onto the mailing list,
so they have to be rerolled?

> Footnote *1*: https://en.wiktionary.org/wiki/commit#Noun does not even
> bother to acknowledge our use of referring to a snapshot of a source code
> base as a "commit".

When Git was a content addressable file system, a commit was precisely
"a database transaction, [...] making it a permanent change."

Side note:
I was just giving a talk to my colleagues about diff aglorithms
(and eventually describing a bug in the histogram diff algorithm)
and we got really riled up with "Longest Common Subsequence",
as the mathematical definition is different than what the code
or I (after studying the code) had in mind.

Naming things is hard, and sometimes the collective wisdom got
it wrong, but changing it would be very costly in the short/medium
term.

Another note about "rolling things": At $DAYJOB I review changes
that are committed to the another revision control system w.r.t. its
compliance of open source licenses (hence I am exposed to a lot
of different projects), and some of those changes are titled
"Roll up to version $X" which I found strange, but knew
what was meant.

Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-20 18:51                           ` Johannes Schindelin
@ 2018-07-20 19:34                             ` Junio C Hamano
  2018-07-20 21:20                               ` Stefan Beller
  2018-07-21 21:56                               ` Johannes Schindelin
  0 siblings, 2 replies; 387+ messages in thread
From: Junio C Hamano @ 2018-07-20 19:34 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Johannes Schindelin via GitGitGadget, git

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> AFAICT there is at least one scenario where you run `rebase -i`, the notes
> get updated, and of course the *reverse mapping* does *not* get updated:

It turns out that I never had a rewrite hook; the notes.rewriteref
mechanism is the only thing that has been used to maintain amlog.

I've stopped populating the reverse mapping, by the way.  The script
that I feed a message from gmane or public-inbox when I need to
learn the set of commits that resulted from the message instead uses
"git grep $message-id notes/amlog".  And that is fast enough for my
purpose.

There is no good reason to abuse the notes mechanism to map a random
object-name looking string (i.e. hash result of message id), other
than the ease of "quick access" when somebody is making a lot of
inquiry, but that "database" does not have to be stored in notes.
It certainly does not belong to cycles worth spending by me *while*
I work during the say with various history reshaping tools to record
and/or update the reverse mapping and that is why my post-applypatch
hook no longer has the "reverse map" hack.

It is not like anybody (including me) needs realtime up-to-date
reverse mapping from amlog while I run my "commit --amend", "rebase
-i", etc. and the reverse map is constructable by reversing the
forward map, obviously, with a postprocessing.  And I think that is
a reasonably way forward if anybody wants to have a reverse mapping.
The postprocessing can be done either by me before pushing out the
amlog ref, or done by any consumer after fetching the amlog ref from
me.  If I did the postprocessing and refuse to use rewrite hook you
wouldn't even know ;-)


^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-20 19:34                             ` Junio C Hamano
@ 2018-07-20 21:20                               ` Stefan Beller
  2018-07-20 21:24                                 ` Junio C Hamano
  2018-07-21 21:56                               ` Johannes Schindelin
  1 sibling, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-07-20 21:20 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Schindelin, gitgitgadget, git

On Fri, Jul 20, 2018 at 12:35 PM Junio C Hamano <gitster@pobox.com> wrote:

> It is not like anybody (including me) needs realtime up-to-date

I thought the same for a long time, but contributing to other projects
showed me that this is not necessarily the case. Having a real time
update, even if it would be just "your patch is labeled 'under discussion'"
is beneficial as I would know where it is "in the system".

In a way I'd compare our contribution process to having an
incredible fine grained paper map. Most of the world moved
on to digital maps, that zoom in on-demand.
(C.f. spelling out "See banned.h for banned functions" in
Documentation/CodingGuidelines is a fine grained detail
that is not relevant for *most* of the contributions, but just
burdens the bearer of the paper map with weight; if this hint
is given dynamically by the compiler or build system at relevant
times, it is much better;

Regarding the real time aspect here, it is also very good
comparison to maps: While I know how to read paper maps
(or offline maps) and how to navigate my way, it sure is easier
to just follow the online up-to-date navigation service, that
tells me what to do. )

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-20 21:20                               ` Stefan Beller
@ 2018-07-20 21:24                                 ` Junio C Hamano
       [not found]                                   ` <CAPc5daW-KoyUX3i7M5YbdQC2mFKAmVBS42-XT84hpm30VFcZ1g@mail.gmail.com>
  0 siblings, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-07-20 21:24 UTC (permalink / raw)
  To: Stefan Beller; +Cc: Johannes Schindelin, gitgitgadget, git

Stefan Beller <sbeller@google.com> writes:

> On Fri, Jul 20, 2018 at 12:35 PM Junio C Hamano <gitster@pobox.com> wrote:
>
>> It is not like anybody (including me) needs realtime up-to-date
>
> I thought the same for a long time, but contributing to other projects
> showed me that this is not necessarily the case. Having a real time
> update, even if it would be just "your patch is labeled 'under discussion'"
> is beneficial as I would know where it is "in the system".

Well, you wouldn't have an access to the up-to-date amlog maintained
by me *UNTIL* I push it out at the end of the day.  So by
definition, you do not have real-time access to the up-to-date
state.

And also by definition, you do not *NEED* such an access, because
you won't see newly created or rewritten commits, whose originating
Message-Id is not in the copy of amlog you have (yet), until I push
the day's integration result out *AND* you fetch what I pushed out.


^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
       [not found]                                   ` <CAPc5daW-KoyUX3i7M5YbdQC2mFKAmVBS42-XT84hpm30VFcZ1g@mail.gmail.com>
@ 2018-07-20 21:30                                     ` Stefan Beller
  2018-07-21 22:02                                       ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-07-20 21:30 UTC (permalink / raw)
  To: Junio C Hamano, git

+cc list
On Fri, Jul 20, 2018 at 2:29 PM Junio C Hamano <gitster@pobox.com> wrote:
> ... which means that it does not matter if I have an elaborate rewrite hook
> that constantly updates the reverse mapping or if the reverse mapping is
> made immediately before I push out. You wouldn't even be able to tell any
> difference.
>
> And for that matter, it could even be made on the receiving end by you
> after you fetch from me before you need the reverse map information.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH 0/2] Re: [PATCH v3 14/20] diff: add an internal option to dual-color diffs of diffs
  2018-07-10 17:45         ` [PATCH 0/2] " Stefan Beller
  2018-07-10 17:45           ` [PATCH 1/2] diff.c: convert emit_line_ws_markup to take string for sign Stefan Beller
  2018-07-10 17:45           ` [PATCH 2/2] WIP diff.c: clarify emit_line_0 Stefan Beller
@ 2018-07-21 21:13           ` Johannes Schindelin
  2 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-21 21:13 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, gitgitgadget, gitster

Hi Stefan,

On Tue, 10 Jul 2018, Stefan Beller wrote:

> This is developed on top of 4a68b95ce2a6 (your series here)
> 
> This is an attempt to explain the previous email better,
> specially the second (yet unfinished) patch, but the resulting
> emit_line_0 is way clearer in my mind, dropping the 'first' character
> and instead having a 'char *sign' that (a) we can color differently for
> dual color and (b) can have multiple chars, the refactoring for the multiple
> chars would need to happen at a slightly higher level.
> 
> Feel free to draw inspiration from here, but if not that is fine, too
> (as I did not fully understand the word diffing problem yet, this may add a
> burden instead of just taking these patches).
> I can send up a cleanup series after yours lands, as well.

We discussed this on IRC a couple of days ago, and I think that this patch
pair was designed under the assumption that the dual color mode would use
diff markers of the same color, always. However, the dual color mode is
about *inverting* the color of the first marker (and using the marker
itself to determine the color) and then using a potentially *different*
color on the second marker (using *that* marker to determine the color).
Example:

	<background red>-</background><foreground green>+ Hello</foreground>

So I allowed myself to focus on trying to wrap my head around the way the
whitespace flags work, and how to adjust the code in cache.h/diff.c/diff.h
to replace the relatively simple workaround by a full blown correct patch
(which is sadly a *lot* larger).

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 16/20] range-diff --dual-color: work around bogus white-space warning
  2018-07-10 16:32             ` Stefan Beller
@ 2018-07-21 21:44               ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-21 21:44 UTC (permalink / raw)
  To: Stefan Beller; +Cc: Junio C Hamano, gitgitgadget, git

Hi Stefan,

On Tue, 10 Jul 2018, Stefan Beller wrote:

> On Tue, Jul 10, 2018 at 3:08 AM Johannes Schindelin
> <Johannes.Schindelin@gmx.de> wrote:
> >
> > On Mon, 9 Jul 2018, Junio C Hamano wrote:
> >
> > > I also wonder if we should be feeding the context lines to ws.c
> > > machinery in the first place though.
> >
> > It *is* confusing, I know. The entire "diff of diffs" concept *is*
> > confusing. I just don't know about a better alternative.
> 
> I agree, but I am sure we'll get used to it quickly.

Maybe you. Not me, though, I use range-diff extensively, and I still got
confused quite a bit.

Until, that is, I implemented the change where the "old-only" changes are
dimmed and the "new-only" changes are displayed in bold.

(The colors stay the same, it's just that the brightness indicates whether
this is a change that was made obsolete, a change that stayed the same, or
a change that was introduced in the latest iteration.)

With this change, I am quite confident that I read the range-diffs
correctly all the time.

> > So hear me out, because there is a big misconception here: there are
> > *two* levels of diffs. The outer one and the inner one.
> 
> Yes, the inner diff is just input that was generated before because it
> is so convenient to generate. Recently when using this too (back then
> when it was called branch-diff), I came across the following:
> 
> Patch 1 looked like:
> 
>     line 1
> +    new line
>     line 2
>     line 3
> 
> and in the next iteration it looked like:
>     line 1
>     line 2
> +    new line
>     line 3
> 
> such that the diff of diffs showed the move correctly, but as the inner diffs
> had different context ranges, other lines looked like added/removed
> in the outer diff, though it was both context.
> So I wonder if eventually (not in this series) we want to tweak the context
> lines, generate more than needed in the inner diffs and cut them off in
> the outer diff "at the same line".

That would be a welcome improvement, although I fear that it will be
relatively intrusive. For one, you can forget about using different
diff consumers (such as word-diff) if you hack up the diff of diffs
generation at *such* a low level.

> I digress again w.r.t. white space.
> 
> > Context lines of the outer diffs have no problem [*1*].
> >
> > The problem arises when the outer diff shows a - or + line (i.e. the line
> > is present *either* in the old patch set or in the new patch set, but not
> > both), *and* that line is *not* a context line of the inner diff.
> 
> So an actual change in the patches; an incremental reviewer would want
> to spend most care on these.

Precisely.

With above-mentioned dimming/brightening, there is a strong visual cue to
focus on those parts.

> > Let's illustrate this via an example. Let's assume that both the old patch
> > set and the new patch set add a comment to a statement, and that the
> > context of that statement changed between old and new patch set. Something
> > like this would be in the old patch set:
> >
> > ```diff
> >         int quiet = 0;
> > +       /* This is only needed for the reflog message */
> >         const char *branch = "HEAD";
> > ```
> >
> > And this would be in the new patch set:
> >
> > ```diff
> >         int quiet = 0, try_harder = 0;
> > +       /* This is only needed for the reflog message */
> >         const char *branch = "HEAD";
> > ```
> >
> > So as you see, both old and new revision of the same patch add that
> > comment, and it is just a context line that changed, which a regular
> > reviewer would want to *not* consider a "real" change between the patch
> > set iterations.
> >
> > Now, let's look at the "diff of diffs":
> >
> > ```diff
> > -       int quiet = 0;
> > +       int quiet = 0, try_harder = 0;
> >  +      /* This is only needed for the reflog message */
> >         const char *branch = "HEAD";
> > ```
> >
> > Please understand that in the dual color mode:
> >
> > - The first line's `-` would have a red background color, the rest of that
> >   line would be uncolored (because it is a context line of the inner
> >   diff),
> >
> > - the second line's `+` would have a green background color, the rest
> >   would be just as uncolored as the rest of the first line,
> >
> > - the third line would be a context line of the outer diff, but a `+` line
> >   of the inner diff, therefore that rest of the line would be green, and
> >
> > - the fourth line is completely uncolored; It is a context line both of
> >   the inner and the outer diff.
> >
> > That's it for the diff colors. Now for the white space: The first two
> > lines start with a `-` and a `+` respectively (outer diff marker), and
> > then most crucially continue with a space to indicate the inner diff's
> > context line, *and then continue with a horizontal tab*.
> >
> > As far as the inner diff is concerned, this *is* a context line.
> >
> > As far as the outer diff is concerned, this is *not* a context line.
> >
> > And that is the conundrum: the whitespace checker is called because the
> > outer diff claims that the second line is a `+` line and the whitespace
> > checker has no idea that it should treat it as a context line instead.
> 
> Spelled out this way, we might want to add more symbols to
> enum diff_symbol, such as
>     DIFF_SYMBOL_DUAL_DIFF_PLUS_PLUS
>     DIFF_SYMBOL_DUAL_DIFF_PLUS_MINUS
>     DIFF_SYMBOL_PLUS_MINUS
> or so.
> 
> These would need to get generated when we create the diff of diffs
> in emit_{del,add,context}_line or even fn_out_consume; and then have
> their own treatment regarding white spaces in emit_diff_symbol_from_struct.
> 
> I am not sure if that would help for the series as-is, as I am thinking
> already how to move these diff-diffs in-core (as that would help a lot
> with the context line cutting mentioned above).

I settled on _DIM and _BOLD versions for CONTEXT, FILE_OLD and FILE_NEW.

> > I'll try to find some time this afternoon to study Stefan's reply, as I
> > have a hunch that there is a deep insight hidden that helps me to figure
> > out the proper path ahead (because I do not want to uglify the `diff.c`
> > code the way my current iteration does, and I'd rather have a way to color
> > the diff more intelligently myself, in a function in `range-diff.c`).
> 
> I considered trying a cleanup on top of your series as I had the impression
> the move detection added some ugliness as well.

I will be glad to review the patches after this coming week. Should I
forget, please remind me.

Thanks,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-20 19:34                             ` Junio C Hamano
  2018-07-20 21:20                               ` Stefan Beller
@ 2018-07-21 21:56                               ` Johannes Schindelin
  2018-07-23  1:25                                 ` Jeff King
  1 sibling, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-21 21:56 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Schindelin via GitGitGadget, git

Hi Junio,

On Fri, 20 Jul 2018, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > AFAICT there is at least one scenario where you run `rebase -i`, the notes
> > get updated, and of course the *reverse mapping* does *not* get updated:
> 
> It turns out that I never had a rewrite hook; the notes.rewriteref
> mechanism is the only thing that has been used to maintain amlog.
> 
> I've stopped populating the reverse mapping, by the way.

That's just great. I ask you to make my life easier by keeping the
information correct, and now you just drop it altogether? Just great.

Seriously, I am trying to *improve* something here, because I really do
care about contributors, and how hard we make it on them. I would not have
expected such a backlash against that.

> The script that I feed a message from gmane or public-inbox when I need
> to learn the set of commits that resulted from the message instead uses
> "git grep $message-id notes/amlog".  And that is fast enough for my
> purpose.

Awesome. You might want to make sure that Peff stops advertising the amlog
notes, then, though.

> There is no good reason to abuse the notes mechanism to map a random
> object-name looking string (i.e. hash result of message id), other
> than the ease of "quick access" when somebody is making a lot of
> inquiry, but that "database" does not have to be stored in notes.

Right. And it does not have to be stored anywhere, because nobody used it
anyway, right?

Well, I hate to break it to you: I just found a really excellent use case,
and you are making it very, very hard for me. Deliberately so. I don't
know how I deserve that.

> It certainly does not belong to cycles worth spending by me *while*
> I work during the say with various history reshaping tools to record
> and/or update the reverse mapping and that is why my post-applypatch
> hook no longer has the "reverse map" hack.
> 
> It is not like anybody (including me) needs realtime up-to-date
> reverse mapping from amlog while I run my "commit --amend", "rebase
> -i", etc. and the reverse map is constructable by reversing the
> forward map, obviously, with a postprocessing.  And I think that is
> a reasonably way forward if anybody wants to have a reverse mapping.
> The postprocessing can be done either by me before pushing out the
> amlog ref, or done by any consumer after fetching the amlog ref from
> me.  If I did the postprocessing and refuse to use rewrite hook you
> wouldn't even know ;-)

The idea that you publish the amlog notes just for your own use cases,
sounds a bit strange to me.

So to reiterate: the information you have in amlog is useful, if faulty.
Rather than "fixing" it by stopping the useful reverse-mapping, it would
make a ton more sense to instate that post-rewrite hook I already drafted
for you.

Besides, while you spent all of that time to make things harder for me,
you still did not look into the most worrisome of my findings: there are
apparently Message-Id mappings where *none* of the commits returned by
said `git grep` you mentioned above are valid. Not a single one. I will
dig out the mail for you on Monday, because I care that much, where I
provided one example of a Message-Id with two commits that match in amlog,
none of which is actually reachable from any of your public branches, and
I also provided the commit that *actually* corresponds to that Message-Id,
and it is not annotated.

So at least in this case *even you* should have a vested interest in
figuring out what goes wrong because even your own use case is affected by
it.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-20 21:30                                     ` Stefan Beller
@ 2018-07-21 22:02                                       ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-21 22:02 UTC (permalink / raw)
  To: Stefan Beller; +Cc: Junio C Hamano, git

Hi,

On Fri, 20 Jul 2018, Stefan Beller wrote:

> +cc list
> On Fri, Jul 20, 2018 at 2:29 PM Junio C Hamano <gitster@pobox.com> wrote:
> > ... which means that it does not matter if I have an elaborate rewrite hook
> > that constantly updates the reverse mapping or if the reverse mapping is
> > made immediately before I push out. You wouldn't even be able to tell any
> > difference.
> >
> > And for that matter, it could even be made on the receiving end by you
> > after you fetch from me before you need the reverse map information.

I refuse to believe that the suggestion to go back to the equivalent of
pencil and paper is sincere. We are developing a state of the art source
code management tool here, not some hodge podge project of somebody who is
trying to teach themselves C.

The current state is that there is no reliable "paper trail" of code
contributions. The solution to that is absolutely not to abolish what
little of a paper trail we *do* have. The solution is to step up the game
and correct that automated record.

And I would have expected a lot better from the inventor of the pickaxe
options: why care so much about source code "archeology" on the one hand,
and then burning the library on the other hand? It just does not make
sense.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike
  2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
                       ` (19 preceding siblings ...)
  2018-06-30 20:41     ` [PATCH v3 20/20] range-diff: make --dual-color the default mode Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:04     ` Johannes Schindelin via GitGitGadget
  2018-07-21 22:04       ` [PATCH v4 01/21] linear-assignment: a function to solve least-cost assignment problems Johannes Schindelin via GitGitGadget
                         ` (23 more replies)
  20 siblings, 24 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:04 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

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.

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.

While at it, I forward-ported AEvar's patch to force `--decorate=no` because `git -p tbdiff` would fail otherwise.

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
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:

```sh
        base=^{/Start.the.merging-rebase}
        tag=v2.17.0.windows.1
        pre=$tag$base^2
        git range-diff $pre$base..$pre $tag$base..$tag
```

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.

Changes since v3:

- 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).
- The documentation was adjusted a bit more in the patch that makes `--dual-color` the default.
- Clarified the calculation of the cost matrix, as per Stefan Beller's request.
- The man page now spells out that merge *commits* are ignored in the commit ranges (not merges per se).
- The code in `linear-assignment.c` was adjusted to use the `SWAP()` macro.
- 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.
- A bogus indentation change was backed out from the patch introducing the first rudimentary implementation.
- Instead of merely warning about missing `..` in the 2-parameter invocation, we now exit with the error message.
- 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).
- A lengthy `if...else if...else if...else` was simplified (from a logical point of view) by reordering it.
- The unnecessarily `static` variable `dashes` was turned into a local variable of the caller.
- The commit message talking about the new man page still referred to `git branch --diff`, which has been fixed.
- A forgotten t7910 reference was changed to t3206.
- An unbalanced double-tick was fixed in the man page.
- Fixed grammar both of the commit message and the description of the `--no-dual-color` option.
- 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).
- 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.
- 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".

Changes since v2:

- Right-aligned the patch numbers in the commit pairs.
- Used ALLOC_ARRAY() in hungarian.c instead of xmalloc(sizeof()*size).
- Changed compute_assignment()s return type from int to void, as it always succeeds.
- Changed the Hungarian Algorithm to use an integer cost matrix.
- Changed the --creation-weight <double> option to --creation-factor <percent> where <percent> is an integer.
- Retitled 1/19 and 2/19 to better conform with the current conventions, as pointed out (and suggested) by Junio.
- Shut up Coverity, and at the same time avoided passing the unnecessary `i` and `j` parameters to output_pair_header().
- Removed support for the `--no-patches` option: we inherit diff_options' support for `-s` already (and much more).
- 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.
- Instead of overriding the fragment header color, the dual color mode will now reverse the "outer" fragment headers, too.
- 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.
- Pretty much re-wrote the completion, to support the new --diff mode of git-branch. See below: it was reverted for range-diff.
- Renamed t7910 to t3206, to be closer to the git-branch tests.
- Ensured that git_diff_ui_config() gets called, and therefore color.diff.* respected.
- Avoided leaking `four_spaces`.
- Fixed a declaration in a for (;;) statement (which Junio had as a fixup! that I almost missed).
- Renamed `branch --diff`, which had been renamed from `branch-diff` (which was picked to avoid re-using `tbdiff`) to `range-diff`.
- Renamed `hungarian.c` and its header to `linear-assignment.c`
- Made `--dual-color` the default, and changed it to still auto-detect whether color should be used rather than forcing it

Johannes Schindelin (20):
  linear-assignment: a function to solve least-cost assignment problems
  Introduce `range-diff` to compare iterations of a topic branch
  range-diff: first rudimentary implementation
  range-diff: improve the order of the shown commits
  range-diff: also show the diff between patches
  range-diff: right-trim commit messages
  range-diff: indent the diffs just like tbdiff
  range-diff: suppress the diff headers
  range-diff: adjust the output of the commit pairs
  range-diff: do not show "function names" in hunk headers
  range-diff: use color for the commit pairs
  color: add the meta color GIT_COLOR_REVERSE
  diff: add an internal option to dual-color diffs of diffs
  range-diff: offer to dual-color the diffs
  range-diff --dual-color: fix bogus white-space warning
  range-diff: populate the man page
  completion: support `git range-diff`
  range-diff: left-pad patch numbers
  range-diff: make --dual-color the default mode
  range-diff: use dim/bold cues to improve dual color mode

Thomas Rast (1):
  range-diff: add tests

 .gitignore                             |   1 +
 Documentation/config.txt               |   6 +-
 Documentation/git-range-diff.txt       | 252 +++++++++++
 Makefile                               |   3 +
 builtin.h                              |   1 +
 builtin/range-diff.c                   | 106 +++++
 cache.h                                |   3 +-
 color.h                                |   7 +
 command-list.txt                       |   1 +
 contrib/completion/git-completion.bash |  14 +
 diff.c                                 | 119 ++++-
 diff.h                                 |  16 +-
 git.c                                  |   1 +
 linear-assignment.c                    | 201 ++++++++
 linear-assignment.h                    |  22 +
 range-diff.c                           | 440 ++++++++++++++++++
 range-diff.h                           |   9 +
 t/.gitattributes                       |   1 +
 t/t3206-range-diff.sh                  | 145 ++++++
 t/t3206/history.export                 | 604 +++++++++++++++++++++++++
 ws.c                                   |  11 +-
 21 files changed, 1932 insertions(+), 31 deletions(-)
 create mode 100644 Documentation/git-range-diff.txt
 create mode 100644 builtin/range-diff.c
 create mode 100644 linear-assignment.c
 create mode 100644 linear-assignment.h
 create mode 100644 range-diff.c
 create mode 100644 range-diff.h
 create mode 100755 t/t3206-range-diff.sh
 create mode 100644 t/t3206/history.export


base-commit: b7bd9486b055c3f967a870311e704e3bb0654e4f
Published-As: https://github.com/gitgitgadget/git/releases/tags/pr-1%2Fdscho%2Fbranch-diff-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1/dscho/branch-diff-v4
Pull-Request: https://github.com/gitgitgadget/git/pull/1

Range-diff vs v3:

  1:  39272eefc !  1:  f7e70689e linear-assignment: a function to solve least-cost assignment problems
     @@ -223,9 +223,7 @@
      +				BUG("negative j: %d", j);
      +			i = pred[j];
      +			column2row[j] = i;
     -+			k = j;
     -+			j = row2column[i];
     -+			row2column[i] = k;
     ++			SWAP(j, row2column[i]);
      +		} while (i1 != i);
      +	}
      +
  2:  7f15b26d4 !  2:  88134121d Introduce `range-diff` to compare iterations of a topic branch
     @@ -10,6 +10,10 @@
          At this point, we ignore tbdiff's color options, as they will all be
          implemented later using diff_options.
      
     +    Since f318d739159 (generate-cmds.sh: export all commands to
     +    command-list.h, 2018-05-10), every new command *requires* a man page to
     +    build right away, so let's also add a blank man page, too.
     +
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
      diff --git a/.gitignore b/.gitignore
     @@ -24,6 +28,22 @@
       /git-rebase
       /git-rebase--am
      
     +diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
     +new file mode 100644
     +--- /dev/null
     ++++ b/Documentation/git-range-diff.txt
     +@@
     ++git-range-diff(1)
     ++==================
     ++
     ++NAME
     ++----
     ++git-range-diff - Compare two commit ranges (e.g. two versions of a branch)
     ++
     ++GIT
     ++---
     ++Part of the linkgit:git[1] suite
     +
      diff --git a/Makefile b/Makefile
      --- a/Makefile
      +++ b/Makefile
  3:  076e1192d !  3:  4e3fb47a1 range-diff: first rudimentary implementation
     @@ -4,7 +4,7 @@
      
          At this stage, `git range-diff` can determine corresponding commits
          of two related commit ranges. This makes use of the recently introduced
     -    implementation of the Hungarian algorithm.
     +    implementation of the linear assignment algorithm.
      
          The core of this patch is a straight port of the ideas of tbdiff, the
          apparently dormant project at https://github.com/trast/tbdiff.
     @@ -51,19 +51,17 @@
      +	int res = 0;
      +	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
       
     --	argc = parse_options(argc, argv, NULL, options,
     --			     builtin_range_diff_usage, 0);
     -+	argc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,
     -+			     0);
     + 	argc = parse_options(argc, argv, NULL, options,
     + 			     builtin_range_diff_usage, 0);
       
      -	return 0;
      +	if (argc == 2) {
      +		if (!strstr(argv[0], ".."))
     -+			warning(_("no .. in range: '%s'"), argv[0]);
     ++			die(_("no .. in range: '%s'"), argv[0]);
      +		strbuf_addstr(&range1, argv[0]);
      +
      +		if (!strstr(argv[1], ".."))
     -+			warning(_("no .. in range: '%s'"), argv[1]);
     ++			die(_("no .. in range: '%s'"), argv[1]);
      +		strbuf_addstr(&range2, argv[1]);
      +	} else if (argc == 3) {
      +		strbuf_addf(&range1, "%s..%s", argv[0], argv[1]);
     @@ -195,17 +193,21 @@
      +			continue;
      +		} else if (starts_with(line.buf, "@@ "))
      +			strbuf_addstr(&buf, "@@");
     -+		else if (line.buf[0] && !starts_with(line.buf, "index "))
     ++		else if (!line.buf[0] || starts_with(line.buf, "index "))
      +			/*
      +			 * A completely blank (not ' \n', which is context)
      +			 * line is not valid in a diff.  We skip it
      +			 * silently, because this neatly handles the blank
      +			 * separator line between commits in git-log
      +			 * output.
     ++			 *
     ++			 * We also want to ignore the diff's `index` lines
     ++			 * because they contain exact blob hashes in which
     ++			 * we are not interested.
      +			 */
     -+			strbuf_addbuf(&buf, &line);
     -+		else
      +			continue;
     ++		else
     ++			strbuf_addbuf(&buf, &line);
      +
      +		strbuf_addch(&buf, '\n');
      +		util->diffsize++;
  4:  e98489c8c =  4:  47bee09b0 range-diff: improve the order of the shown commits
  5:  935cad180 !  5:  94afaeaf2 range-diff: also show the diff between patches
     @@ -55,21 +55,22 @@
      +	int i, j, res = 0;
       	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
       
     --	argc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,
     --			     0);
      +	git_config(git_diff_ui_config, NULL);
      +
      +	diff_setup(&diffopt);
      +	diffopt.output_format = DIFF_FORMAT_PATCH;
      +
     -+	argc = parse_options(argc, argv, NULL, options,
     -+			builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);
     + 	argc = parse_options(argc, argv, NULL, options,
     +-			     builtin_range_diff_usage, 0);
     ++			     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);
      +
     -+	for (i = j = 0; i < argc; i++) {
     ++	for (i = j = 0; i < argc; ) {
      +		int c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);
      +
      +		if (!c)
     -+			argv[j++] = argv[i];
     ++			argv[j++] = argv[i++];
     ++		else
     ++			i += c;
      +	}
      +	argc = j;
      +	diff_setup_done(&diffopt);
  6:  93ac1931d =  6:  41ab875a3 range-diff: right-trim commit messages
  7:  ca5282815 !  7:  a3dd99509 range-diff: indent the diffs just like tbdiff
     @@ -39,7 +39,7 @@
      +	diffopt.output_prefix_data = &four_spaces;
       
       	argc = parse_options(argc, argv, NULL, options,
     - 			builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);
     + 			     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);
      @@
       
       	strbuf_release(&range1);
  8:  80622685f =  8:  61b2ff2f7 range-diff: suppress the diff headers
  9:  6b31cbf72 !  9:  9641ab5c0 range-diff: adjust the output of the commit pairs
     @@ -26,25 +26,22 @@
       
      -static const char *short_oid(struct patch_util *util)
      +static void output_pair_header(struct strbuf *buf,
     ++			       struct strbuf *dashes,
      +			       struct patch_util *a_util,
      +			       struct patch_util *b_util)
       {
      -	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
     -+	static char *dashes;
      +	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
      +	struct commit *commit;
      +
     -+	if (!dashes) {
     -+		char *p;
     -+
     -+		dashes = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV));
     -+		for (p = dashes; *p; p++)
     -+			*p = '-';
     -+	}
     ++	if (!dashes->len)
     ++		strbuf_addchars(dashes, '-',
     ++				strlen(find_unique_abbrev(oid,
     ++							  DEFAULT_ABBREV)));
      +
      +	strbuf_reset(buf);
      +	if (!a_util)
     -+		strbuf_addf(buf, "-:  %s ", dashes);
     ++		strbuf_addf(buf, "-:  %s ", dashes->buf);
      +	else
      +		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
      +			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
     @@ -59,7 +56,7 @@
      +		strbuf_addch(buf, '=');
      +
      +	if (!b_util)
     -+		strbuf_addf(buf, " -:  %s", dashes);
     ++		strbuf_addf(buf, " -:  %s", dashes->buf);
      +	else
      +		strbuf_addf(buf, " %d:  %s", b_util->i + 1,
      +			    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));
     @@ -84,7 +81,7 @@
       static void output(struct string_list *a, struct string_list *b,
       		   struct diff_options *diffopt)
       {
     -+	struct strbuf buf = STRBUF_INIT;
     ++	struct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;
       	int i = 0, j = 0;
       
       	/*
     @@ -94,7 +91,7 @@
       		if (i < a->nr && a_util->matching < 0) {
      -			printf("%d: %s < -: --------\n",
      -			       i + 1, short_oid(a_util));
     -+			output_pair_header(&buf, a_util, NULL);
     ++			output_pair_header(&buf, &dashes, a_util, NULL);
       			i++;
       			continue;
       		}
     @@ -103,7 +100,7 @@
       		while (j < b->nr && b_util->matching < 0) {
      -			printf("-: -------- > %d: %s\n",
      -			       j + 1, short_oid(b_util));
     -+			output_pair_header(&buf, NULL, b_util);
     ++			output_pair_header(&buf, &dashes, NULL, b_util);
       			b_util = ++j < b->nr ? b->items[j].util : NULL;
       		}
       
     @@ -113,7 +110,7 @@
      -			printf("%d: %s ! %d: %s\n",
      -			       b_util->matching + 1, short_oid(a_util),
      -			       j + 1, short_oid(b_util));
     -+			output_pair_header(&buf, a_util, b_util);
     ++			output_pair_header(&buf, &dashes, a_util, b_util);
       			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
       				patch_diff(a->items[b_util->matching].string,
       					   b->items[j].string, diffopt);
     @@ -122,6 +119,7 @@
       		}
       	}
      +	strbuf_release(&buf);
     ++	strbuf_release(&dashes);
       }
       
       int show_range_diff(const char *range1, const char *range2,
 10:  ef997bb8b = 10:  0a52f8878 range-diff: do not show "function names" in hunk headers
 11:  3d9e5b0ba ! 11:  2b8d09020 range-diff: add tests
     @@ -3,8 +3,8 @@
          range-diff: add tests
      
          These are essentially lifted from https://github.com/trast/tbdiff, with
     -    light touch-ups to account for the command now being an option of `git
     -    branch`.
     +    light touch-ups to account for the command now being names `git
     +    range-diff`.
      
          Apart from renaming `tbdiff` to `range-diff`, only one test case needed
          to be adjusted: 11 - 'changed message'.
     @@ -22,12 +22,13 @@
      --- a/t/.gitattributes
      +++ b/t/.gitattributes
      @@
     - /t5515/* eol=lf
     - /t556x_common eol=lf
     - /t7500/* eol=lf
     -+/t7910/* eol=lf
     - /t8005/*.txt eol=lf
     - /t9*/*.dump eol=lf
     + t[0-9][0-9][0-9][0-9]/* -whitespace
     + /diff-lib/* eol=lf
     + /t0110/url-* binary
     ++/t3206/* eol=lf
     + /t3900/*.txt eol=lf
     + /t3901/*.txt eol=lf
     + /t4034/*/* eol=lf
      
      diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
      new file mode 100755
 12:  7273cc647 ! 12:  fb83ce71a range-diff: use color for the commit pairs
     @@ -20,11 +20,12 @@
       }
       
      -static void output_pair_header(struct strbuf *buf,
     -+static void output_pair_header(struct diff_options *diffopt, struct strbuf *buf,
     ++static void output_pair_header(struct diff_options *diffopt,
     ++			       struct strbuf *buf,
     + 			       struct strbuf *dashes,
       			       struct patch_util *a_util,
       			       struct patch_util *b_util)
       {
     - 	static char *dashes;
       	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
       	struct commit *commit;
      +	char status;
     @@ -34,11 +35,10 @@
      +	const char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT);
      +	const char *color;
       
     - 	if (!dashes) {
     - 		char *p;
     -@@
     - 			*p = '-';
     - 	}
     + 	if (!dashes->len)
     + 		strbuf_addchars(dashes, '-',
     + 				strlen(find_unique_abbrev(oid,
     + 							  DEFAULT_ABBREV)));
       
      +	if (!b_util) {
      +		color = color_old;
     @@ -57,7 +57,7 @@
       	strbuf_reset(buf);
      +	strbuf_addstr(buf, status == '!' ? color_old : color);
       	if (!a_util)
     - 		strbuf_addf(buf, "-:  %s ", dashes);
     + 		strbuf_addf(buf, "-:  %s ", dashes->buf);
       	else
       		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
       			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
     @@ -77,7 +77,7 @@
      +		strbuf_addf(buf, "%s%s", color_reset, color_new);
       
       	if (!b_util)
     - 		strbuf_addf(buf, " -:  %s", dashes);
     + 		strbuf_addf(buf, " -:  %s", dashes->buf);
      @@
       		const char *commit_buffer = get_commit_buffer(commit, NULL);
       		const char *subject;
     @@ -99,24 +99,27 @@
       
       		/* Show unmatched LHS commit whose predecessors were shown. */
       		if (i < a->nr && a_util->matching < 0) {
     --			output_pair_header(&buf, a_util, NULL);
     -+			output_pair_header(diffopt, &buf, a_util, NULL);
     +-			output_pair_header(&buf, &dashes, a_util, NULL);
     ++			output_pair_header(diffopt,
     ++					   &buf, &dashes, a_util, NULL);
       			i++;
       			continue;
       		}
       
       		/* Show unmatched RHS commits. */
       		while (j < b->nr && b_util->matching < 0) {
     --			output_pair_header(&buf, NULL, b_util);
     -+			output_pair_header(diffopt, &buf, NULL, b_util);
     +-			output_pair_header(&buf, &dashes, NULL, b_util);
     ++			output_pair_header(diffopt,
     ++					   &buf, &dashes, NULL, b_util);
       			b_util = ++j < b->nr ? b->items[j].util : NULL;
       		}
       
       		/* Show matching LHS/RHS pair. */
       		if (j < b->nr) {
       			a_util = a->items[b_util->matching].util;
     --			output_pair_header(&buf, a_util, b_util);
     -+			output_pair_header(diffopt, &buf, a_util, b_util);
     +-			output_pair_header(&buf, &dashes, a_util, b_util);
     ++			output_pair_header(diffopt,
     ++					   &buf, &dashes, a_util, b_util);
       			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
       				patch_diff(a->items[b_util->matching].string,
       					   b->items[j].string, diffopt);
 13:  96a3073fb = 13:  9ccb9516a color: add the meta color GIT_COLOR_REVERSE
 14:  6be4baf60 = 14:  9de5bd229 diff: add an internal option to dual-color diffs of diffs
 15:  02e13c0c6 ! 15:  21b2f9e4b range-diff: offer to dual-color the diffs
     @@ -40,4 +40,4 @@
      +
       	if (argc == 2) {
       		if (!strstr(argv[0], ".."))
     - 			warning(_("no .. in range: '%s'"), argv[0]);
     + 			die(_("no .. in range: '%s'"), argv[0]);
 16:  dfa7b1e71 <  -:  --------- range-diff --dual-color: work around bogus white-space warning
  -:  --------- > 16:  f4252f2b2 range-diff --dual-color: fix bogus white-space warning
 17:  799da25ef ! 17:  9e09c6be6 range-diff: add a man page
     @@ -1,6 +1,6 @@
      Author: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -    range-diff: add a man page
     +    range-diff: populate the man page
      
          The bulk of this patch consists of a heavily butchered version of
          tbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from
     @@ -9,17 +9,12 @@
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
      diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
     -new file mode 100644
     ---- /dev/null
     +--- a/Documentation/git-range-diff.txt
      +++ b/Documentation/git-range-diff.txt
      @@
     -+git-range-diff(1)
     -+==================
     -+
     -+NAME
     -+----
     -+git-range-diff - Compare two commit ranges (e.g. two versions of a branch)
     -+
     + ----
     + git-range-diff - Compare two commit ranges (e.g. two versions of a branch)
     + 
      +SYNOPSIS
      +--------
      +[verse]
     @@ -31,13 +26,13 @@
      +-----------
      +
      +This command shows the differences between two versions of a patch
     -+series, or more generally, two commit ranges (ignoring merges).
     ++series, or more generally, two commit ranges (ignoring merge commits).
      +
      +To that end, it first finds pairs of commits from both commit ranges
      +that correspond with each other. Two commits are said to correspond when
      +the diff between their patches (i.e. the author information, the commit
      +message and the commit diff) is reasonably small compared to the
     -+patches' size. See ``Algorithm` below for details.
     ++patches' size. See ``Algorithm`` below for details.
      +
      +Finally, the list of matching commits is shown in the order of the
      +second commit range, with unmatched commits being inserted just after
     @@ -150,6 +145,10 @@
      +The general idea is this: we generate a cost matrix between the commits
      +in both commit ranges, then solve the least-cost assignment.
      +
     ++The cost matrix is populated thusly: for each pair of commits, both
     ++diffs are generated and the "diff of diffs" is generated, with 3 context
     ++lines, then the number of lines in that diff is used as cost.
     ++
      +To avoid false positives (e.g. when a patch has been removed, and an
      +unrelated patch has been added between two iterations of the same patch
      +series), the cost matrix is extended to allow for that, by adding
     @@ -245,6 +244,6 @@
      +--------
      +linkgit:git-log[1]
      +
     -+GIT
     -+---
     -+Part of the linkgit:git[1] suite
     + GIT
     + ---
     + Part of the linkgit:git[1] suite
 18:  d05b54c60 ! 18:  9b3632324 completion: support `git range-diff`
     @@ -4,17 +4,11 @@
      
          Tab completion of `git range-diff` is very convenient, especially
          given that the revision arguments to specify the commit ranges to
     -    compare are typically more complex than, say, your grandfather's `git
     -    log` arguments.
     +    compare are typically more complex than, say, what is normally passed
     +    to `git log`.
      
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     -    squash! WIP completion: support `git range-diff`
     -
     -    Revert "WIP completion: support `git range-diff`"
     -
     -    This reverts commit 2e7af652af9e53a19fd947f8ebe37a78043afa49.
     -
      diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
      --- a/contrib/completion/git-completion.bash
      +++ b/contrib/completion/git-completion.bash
 19:  144363006 <  -:  --------- range-diff: left-pad patch numbers
  -:  --------- > 19:  07ec215e8 range-diff: left-pad patch numbers
 20:  4a68b95ce ! 20:  b370468e7 range-diff: make --dual-color the default mode
     @@ -4,7 +4,7 @@
      
          After using this command extensively for the last two months, this
          developer came to the conclusion that even if the dual color mode still
     -    leaves a lot of room for confusion what was actually changed, the
     +    leaves a lot of room for confusion about what was actually changed, the
          non-dual color mode is substantially worse in that regard.
      
          Therefore, we really want to make the dual color mode the default.
     @@ -14,6 +14,15 @@
      diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
      --- a/Documentation/git-range-diff.txt
      +++ b/Documentation/git-range-diff.txt
     +@@
     + --------
     + [verse]
     + 'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]
     +-	[--dual-color] [--creation-factor=<factor>]
     ++	[--no-dual-color] [--creation-factor=<factor>]
     + 	( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
     + 
     + DESCRIPTION
      @@
       
       OPTIONS
     @@ -25,7 +34,7 @@
      -	change in what exact lines were added.
      +--no-dual-color::
      +	When the commit diffs differ, `git range-diff` recreates the
     -+	original diffs' coloring, and add outer -/+ diff markers with
     ++	original diffs' coloring, and adds outer -/+ diff markers with
      +	the *background* being red/green to make it easier to see e.g.
      +	when there was a change in what exact lines were added. This is
      +	known to `range-diff` as "dual coloring". Use `--no-dual-color`
     @@ -34,6 +43,31 @@
       
       --creation-factor=<percent>::
       	Set the creation/deletion cost fudge factor to `<percent>`.
     +@@
     + show`'s output, and the third line colors the old commit red, the new
     + one green and the rest like `git show`'s commit header.
     + 
     +-The color-coded diff is actually a bit hard to read, though, as it
     +-colors the entire lines red or green. The line that added "What is
     +-unexpected" in the old commit, for example, is completely red, even if
     +-the intent of the old commit was to add something.
     ++A naive color-coded diff of diffs is actually a bit hard to read,
     ++though, as it colors the entire lines red or green. The line that added
     ++"What is unexpected" in the old commit, for example, is completely red,
     ++even if the intent of the old commit was to add something.
     + 
     +-To help with that, use the `--dual-color` mode. In this mode, the diff
     +-of diffs will retain the original diff colors, and prefix the lines with
     +--/+ markers that have their *background* red or green, to make it more
     +-obvious that they describe how the diff itself changed.
     ++To help with that, `range` uses the `--dual-color` mode by default. In
     ++this mode, the diff of diffs will retain the original diff colors, and
     ++prefix the lines with -/+ markers that have their *background* red or
     ++green, to make it more obvious that they describe how the diff itself
     ++changed.
     + 
     + 
     + Algorithm
      
      diff --git a/builtin/range-diff.c b/builtin/range-diff.c
      --- a/builtin/range-diff.c
  -:  --------- > 21:  d8498fb32 range-diff: use dim/bold cues to improve dual color mode

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 387+ messages in thread

* [PATCH v4 01/21] linear-assignment: a function to solve least-cost assignment problems
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:04       ` Johannes Schindelin via GitGitGadget
  2018-07-28  8:46         ` Thomas Gummerer
  2018-07-21 22:04       ` [PATCH v4 02/21] Introduce `range-diff` to compare iterations of a topic branch Johannes Schindelin via GitGitGadget
                         ` (22 subsequent siblings)
  23 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:04 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

The problem solved by the code introduced in this commit goes like this:
given two sets of items, and a cost matrix which says how much it
"costs" to assign any given item of the first set to any given item of
the second, assign all items (except when the sets have different size)
in the cheapest way.

We use the Jonker-Volgenant algorithm to solve the assignment problem to
answer questions such as: given two different versions of a topic branch
(or iterations of a patch series), what is the best pairing of
commits/patches between the different versions?

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Makefile            |   1 +
 linear-assignment.c | 201 ++++++++++++++++++++++++++++++++++++++++++++
 linear-assignment.h |  22 +++++
 3 files changed, 224 insertions(+)
 create mode 100644 linear-assignment.c
 create mode 100644 linear-assignment.h

diff --git a/Makefile b/Makefile
index 08e5c5454..56326ab2b 100644
--- a/Makefile
+++ b/Makefile
@@ -868,6 +868,7 @@ LIB_OBJS += gpg-interface.o
 LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hashmap.o
+LIB_OBJS += linear-assignment.o
 LIB_OBJS += help.o
 LIB_OBJS += hex.o
 LIB_OBJS += ident.o
diff --git a/linear-assignment.c b/linear-assignment.c
new file mode 100644
index 000000000..9b3e56e28
--- /dev/null
+++ b/linear-assignment.c
@@ -0,0 +1,201 @@
+/*
+ * Based on: Jonker, R., & Volgenant, A. (1987). <i>A shortest augmenting path
+ * algorithm for dense and sparse linear assignment problems</i>. Computing,
+ * 38(4), 325-340.
+ */
+#include "cache.h"
+#include "linear-assignment.h"
+
+#define COST(column, row) cost[(column) + column_count * (row)]
+
+/*
+ * The parameter `cost` is the cost matrix: the cost to assign column j to row
+ * i is `cost[j + column_count * i].
+ */
+void compute_assignment(int column_count, int row_count, int *cost,
+			int *column2row, int *row2column)
+{
+	int *v, *d;
+	int *free_row, free_count = 0, saved_free_count, *pred, *col;
+	int i, j, phase;
+
+	memset(column2row, -1, sizeof(int) * column_count);
+	memset(row2column, -1, sizeof(int) * row_count);
+	ALLOC_ARRAY(v, column_count);
+
+	/* column reduction */
+	for (j = column_count - 1; j >= 0; j--) {
+		int i1 = 0;
+
+		for (i = 1; i < row_count; i++)
+			if (COST(j, i1) > COST(j, i))
+				i1 = i;
+		v[j] = COST(j, i1);
+		if (row2column[i1] == -1) {
+			/* row i1 unassigned */
+			row2column[i1] = j;
+			column2row[j] = i1;
+		} else {
+			if (row2column[i1] >= 0)
+				row2column[i1] = -2 - row2column[i1];
+			column2row[j] = -1;
+		}
+	}
+
+	/* reduction transfer */
+	ALLOC_ARRAY(free_row, row_count);
+	for (i = 0; i < row_count; i++) {
+		int j1 = row2column[i];
+		if (j1 == -1)
+			free_row[free_count++] = i;
+		else if (j1 < -1)
+			row2column[i] = -2 - j1;
+		else {
+			int min = COST(!j1, i) - v[!j1];
+			for (j = 1; j < column_count; j++)
+				if (j != j1 && min > COST(j, i) - v[j])
+					min = COST(j, i) - v[j];
+			v[j1] -= min;
+		}
+	}
+
+	if (free_count ==
+	    (column_count < row_count ? row_count - column_count : 0)) {
+		free(v);
+		free(free_row);
+		return;
+	}
+
+	/* augmenting row reduction */
+	for (phase = 0; phase < 2; phase++) {
+		int k = 0;
+
+		saved_free_count = free_count;
+		free_count = 0;
+		while (k < saved_free_count) {
+			int u1, u2;
+			int j1 = 0, j2, i0;
+
+			i = free_row[k++];
+			u1 = COST(j1, i) - v[j1];
+			j2 = -1;
+			u2 = INT_MAX;
+			for (j = 1; j < column_count; j++) {
+				int c = COST(j, i) - v[j];
+				if (u2 > c) {
+					if (u1 < c) {
+						u2 = c;
+						j2 = j;
+					} else {
+						u2 = u1;
+						u1 = c;
+						j2 = j1;
+						j1 = j;
+					}
+				}
+			}
+			if (j2 < 0) {
+				j2 = j1;
+				u2 = u1;
+			}
+
+			i0 = column2row[j1];
+			if (u1 < u2)
+				v[j1] -= u2 - u1;
+			else if (i0 >= 0) {
+				j1 = j2;
+				i0 = column2row[j1];
+			}
+
+			if (i0 >= 0) {
+				if (u1 < u2)
+					free_row[--k] = i0;
+				else
+					free_row[free_count++] = i0;
+			}
+			row2column[i] = j1;
+			column2row[j1] = i;
+		}
+	}
+
+	/* augmentation */
+	saved_free_count = free_count;
+	ALLOC_ARRAY(d, column_count);
+	ALLOC_ARRAY(pred, column_count);
+	ALLOC_ARRAY(col, column_count);
+	for (free_count = 0; free_count < saved_free_count; free_count++) {
+		int i1 = free_row[free_count], low = 0, up = 0, last, k;
+		int min, c, u1;
+
+		for (j = 0; j < column_count; j++) {
+			d[j] = COST(j, i1) - v[j];
+			pred[j] = i1;
+			col[j] = j;
+		}
+
+		j = -1;
+		do {
+			last = low;
+			min = d[col[up++]];
+			for (k = up; k < column_count; k++) {
+				j = col[k];
+				c = d[j];
+				if (c <= min) {
+					if (c < min) {
+						up = low;
+						min = c;
+					}
+					col[k] = col[up];
+					col[up++] = j;
+				}
+			}
+			for (k = low; k < up; k++)
+				if (column2row[col[k]] == -1)
+					goto update;
+
+			/* scan a row */
+			do {
+				int j1 = col[low++];
+
+				i = column2row[j1];
+				u1 = COST(j1, i) - v[j1] - min;
+				for (k = up; k < column_count; k++) {
+					j = col[k];
+					c = COST(j, i) - v[j] - u1;
+					if (c < d[j]) {
+						d[j] = c;
+						pred[j] = i;
+						if (c == min) {
+							if (column2row[j] == -1)
+								goto update;
+							col[k] = col[up];
+							col[up++] = j;
+						}
+					}
+				}
+			} while (low != up);
+		} while (low == up);
+
+update:
+		/* updating of the column pieces */
+		for (k = 0; k < last; k++) {
+			int j1 = col[k];
+			v[j1] += d[j1] - min;
+		}
+
+		/* augmentation */
+		do {
+			if (j < 0)
+				BUG("negative j: %d", j);
+			i = pred[j];
+			column2row[j] = i;
+			SWAP(j, row2column[i]);
+		} while (i1 != i);
+	}
+
+	free(col);
+	free(pred);
+	free(d);
+	free(v);
+	free(free_row);
+}
diff --git a/linear-assignment.h b/linear-assignment.h
new file mode 100644
index 000000000..fc4c502c8
--- /dev/null
+++ b/linear-assignment.h
@@ -0,0 +1,22 @@
+#ifndef HUNGARIAN_H
+#define HUNGARIAN_H
+
+/*
+ * Compute an assignment of columns -> rows (and vice versa) such that every
+ * column is assigned to at most one row (and vice versa) minimizing the
+ * overall cost.
+ *
+ * The parameter `cost` is the cost matrix: the cost to assign column j to row
+ * i is `cost[j + column_count * i].
+ *
+ * The arrays column2row and row2column will be populated with the respective
+ * assignments (-1 for unassigned, which can happen only if column_count !=
+ * row_count).
+ */
+void compute_assignment(int column_count, int row_count, int *cost,
+			int *column2row, int *row2column);
+
+/* The maximal cost in the cost matrix (to prevent integer overflows). */
+#define COST_MAX (1<<16)
+
+#endif
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 02/21] Introduce `range-diff` to compare iterations of a topic branch
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
  2018-07-21 22:04       ` [PATCH v4 01/21] linear-assignment: a function to solve least-cost assignment problems Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:04       ` Johannes Schindelin via GitGitGadget
  2018-07-21 22:04       ` [PATCH v4 03/21] range-diff: first rudimentary implementation Johannes Schindelin via GitGitGadget
                         ` (21 subsequent siblings)
  23 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:04 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

This command does not do a whole lot so far, apart from showing a usage
that is oddly similar to that of `git tbdiff`. And for a good reason:
the next commits will turn `range-branch` into a full-blown replacement
for `tbdiff`.

At this point, we ignore tbdiff's color options, as they will all be
implemented later using diff_options.

Since f318d739159 (generate-cmds.sh: export all commands to
command-list.h, 2018-05-10), every new command *requires* a man page to
build right away, so let's also add a blank man page, too.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitignore                       |  1 +
 Documentation/git-range-diff.txt | 10 ++++++++++
 Makefile                         |  1 +
 builtin.h                        |  1 +
 builtin/range-diff.c             | 25 +++++++++++++++++++++++++
 command-list.txt                 |  1 +
 git.c                            |  1 +
 7 files changed, 40 insertions(+)
 create mode 100644 Documentation/git-range-diff.txt
 create mode 100644 builtin/range-diff.c

diff --git a/.gitignore b/.gitignore
index 3284a1e9b..cc0ad74b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -113,6 +113,7 @@
 /git-pull
 /git-push
 /git-quiltimport
+/git-range-diff
 /git-read-tree
 /git-rebase
 /git-rebase--am
diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
new file mode 100644
index 000000000..de0ca5df4
--- /dev/null
+++ b/Documentation/git-range-diff.txt
@@ -0,0 +1,10 @@
+git-range-diff(1)
+==================
+
+NAME
+----
+git-range-diff - Compare two commit ranges (e.g. two versions of a branch)
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Makefile b/Makefile
index 56326ab2b..45c9dea1b 100644
--- a/Makefile
+++ b/Makefile
@@ -1059,6 +1059,7 @@ BUILTIN_OBJS += builtin/prune-packed.o
 BUILTIN_OBJS += builtin/prune.o
 BUILTIN_OBJS += builtin/pull.o
 BUILTIN_OBJS += builtin/push.o
+BUILTIN_OBJS += builtin/range-diff.o
 BUILTIN_OBJS += builtin/read-tree.o
 BUILTIN_OBJS += builtin/rebase--helper.o
 BUILTIN_OBJS += builtin/receive-pack.o
diff --git a/builtin.h b/builtin.h
index 0362f1ce2..99206df4b 100644
--- a/builtin.h
+++ b/builtin.h
@@ -201,6 +201,7 @@ extern int cmd_prune(int argc, const char **argv, const char *prefix);
 extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
 extern int cmd_pull(int argc, const char **argv, const char *prefix);
 extern int cmd_push(int argc, const char **argv, const char *prefix);
+extern int cmd_range_diff(int argc, const char **argv, const char *prefix);
 extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix);
 extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);
diff --git a/builtin/range-diff.c b/builtin/range-diff.c
new file mode 100644
index 000000000..36788ea4f
--- /dev/null
+++ b/builtin/range-diff.c
@@ -0,0 +1,25 @@
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+
+static const char * const builtin_range_diff_usage[] = {
+N_("git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
+N_("git range-diff [<options>] <old-tip>...<new-tip>"),
+N_("git range-diff [<options>] <base> <old-tip> <new-tip>"),
+NULL
+};
+
+int cmd_range_diff(int argc, const char **argv, const char *prefix)
+{
+	int creation_factor = 60;
+	struct option options[] = {
+		OPT_INTEGER(0, "creation-factor", &creation_factor,
+			    N_("Percentage by which creation is weighted")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, NULL, options,
+			     builtin_range_diff_usage, 0);
+
+	return 0;
+}
diff --git a/command-list.txt b/command-list.txt
index e1c26c1bb..a9dda3b8a 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -139,6 +139,7 @@ git-prune-packed                        plumbingmanipulators
 git-pull                                mainporcelain           remote
 git-push                                mainporcelain           remote
 git-quiltimport                         foreignscminterface
+git-range-diff                          mainporcelain
 git-read-tree                           plumbingmanipulators
 git-rebase                              mainporcelain           history
 git-receive-pack                        synchelpers
diff --git a/git.c b/git.c
index 3fded7451..6901cf328 100644
--- a/git.c
+++ b/git.c
@@ -517,6 +517,7 @@ static struct cmd_struct commands[] = {
 	{ "prune-packed", cmd_prune_packed, RUN_SETUP },
 	{ "pull", cmd_pull, RUN_SETUP | NEED_WORK_TREE },
 	{ "push", cmd_push, RUN_SETUP },
+	{ "range-diff", cmd_range_diff, RUN_SETUP | USE_PAGER },
 	{ "read-tree", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX},
 	{ "rebase--helper", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE },
 	{ "receive-pack", cmd_receive_pack },
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 03/21] range-diff: first rudimentary implementation
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
  2018-07-21 22:04       ` [PATCH v4 01/21] linear-assignment: a function to solve least-cost assignment problems Johannes Schindelin via GitGitGadget
  2018-07-21 22:04       ` [PATCH v4 02/21] Introduce `range-diff` to compare iterations of a topic branch Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:04       ` Johannes Schindelin via GitGitGadget
  2018-07-29 18:36         ` Thomas Gummerer
  2018-07-21 22:04       ` [PATCH v4 04/21] range-diff: improve the order of the shown commits Johannes Schindelin via GitGitGadget
                         ` (20 subsequent siblings)
  23 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:04 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

At this stage, `git range-diff` can determine corresponding commits
of two related commit ranges. This makes use of the recently introduced
implementation of the linear assignment algorithm.

The core of this patch is a straight port of the ideas of tbdiff, the
apparently dormant project at https://github.com/trast/tbdiff.

The output does not at all match `tbdiff`'s output yet, as this patch
really concentrates on getting the patch matching part right.

Note: due to differences in the diff algorithm (`tbdiff` uses the Python
module `difflib`, Git uses its xdiff fork), the cost matrix calculated
by `range-diff` is different (but very similar) to the one calculated
by `tbdiff`. Therefore, it is possible that they find different matching
commits in corner cases (e.g. when a patch was split into two patches of
roughly equal length).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Makefile             |   1 +
 builtin/range-diff.c |  43 +++++-
 range-diff.c         | 311 +++++++++++++++++++++++++++++++++++++++++++
 range-diff.h         |   7 +
 4 files changed, 361 insertions(+), 1 deletion(-)
 create mode 100644 range-diff.c
 create mode 100644 range-diff.h

diff --git a/Makefile b/Makefile
index 45c9dea1b..41b93689a 100644
--- a/Makefile
+++ b/Makefile
@@ -921,6 +921,7 @@ LIB_OBJS += progress.o
 LIB_OBJS += prompt.o
 LIB_OBJS += protocol.o
 LIB_OBJS += quote.o
+LIB_OBJS += range-diff.o
 LIB_OBJS += reachable.o
 LIB_OBJS += read-cache.o
 LIB_OBJS += reflog-walk.o
diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 36788ea4f..3881da246 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "builtin.h"
 #include "parse-options.h"
+#include "range-diff.h"
 
 static const char * const builtin_range_diff_usage[] = {
 N_("git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
@@ -17,9 +18,49 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 			    N_("Percentage by which creation is weighted")),
 		OPT_END()
 	};
+	int res = 0;
+	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
 
 	argc = parse_options(argc, argv, NULL, options,
 			     builtin_range_diff_usage, 0);
 
-	return 0;
+	if (argc == 2) {
+		if (!strstr(argv[0], ".."))
+			die(_("no .. in range: '%s'"), argv[0]);
+		strbuf_addstr(&range1, argv[0]);
+
+		if (!strstr(argv[1], ".."))
+			die(_("no .. in range: '%s'"), argv[1]);
+		strbuf_addstr(&range2, argv[1]);
+	} else if (argc == 3) {
+		strbuf_addf(&range1, "%s..%s", argv[0], argv[1]);
+		strbuf_addf(&range2, "%s..%s", argv[0], argv[2]);
+	} else if (argc == 1) {
+		const char *b = strstr(argv[0], "..."), *a = argv[0];
+		int a_len;
+
+		if (!b)
+			die(_("single arg format requires a symmetric range"));
+
+		a_len = (int)(b - a);
+		if (!a_len) {
+			a = "HEAD";
+			a_len = strlen(a);
+		}
+		b += 3;
+		if (!*b)
+			b = "HEAD";
+		strbuf_addf(&range1, "%s..%.*s", b, a_len, a);
+		strbuf_addf(&range2, "%.*s..%s", a_len, a, b);
+	} else {
+		error(_("need two commit ranges"));
+		usage_with_options(builtin_range_diff_usage, options);
+	}
+
+	res = show_range_diff(range1.buf, range2.buf, creation_factor);
+
+	strbuf_release(&range1);
+	strbuf_release(&range2);
+
+	return res;
 }
diff --git a/range-diff.c b/range-diff.c
new file mode 100644
index 000000000..15d418afa
--- /dev/null
+++ b/range-diff.c
@@ -0,0 +1,311 @@
+#include "cache.h"
+#include "range-diff.h"
+#include "string-list.h"
+#include "run-command.h"
+#include "argv-array.h"
+#include "hashmap.h"
+#include "xdiff-interface.h"
+#include "linear-assignment.h"
+
+struct patch_util {
+	/* For the search for an exact match */
+	struct hashmap_entry e;
+	const char *diff, *patch;
+
+	int i;
+	int diffsize;
+	size_t diff_offset;
+	/* the index of the matching item in the other branch, or -1 */
+	int matching;
+	struct object_id oid;
+};
+
+/*
+ * Reads the patches into a string list, with the `util` field being populated
+ * as struct object_id (will need to be free()d).
+ */
+static int read_patches(const char *range, struct string_list *list)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+	FILE *in;
+	struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;
+	struct patch_util *util = NULL;
+	int in_header = 1;
+
+	argv_array_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
+			"--reverse", "--date-order", "--decorate=no",
+			"--no-abbrev-commit", range,
+			NULL);
+	cp.out = -1;
+	cp.no_stdin = 1;
+	cp.git_cmd = 1;
+
+	if (start_command(&cp))
+		return error_errno(_("could not start `log`"));
+	in = fdopen(cp.out, "r");
+	if (!in) {
+		error_errno(_("could not read `log` output"));
+		finish_command(&cp);
+		return -1;
+	}
+
+	while (strbuf_getline(&line, in) != EOF) {
+		const char *p;
+
+		if (skip_prefix(line.buf, "commit ", &p)) {
+			if (util) {
+				string_list_append(list, buf.buf)->util = util;
+				strbuf_reset(&buf);
+			}
+			util = xcalloc(sizeof(*util), 1);
+			if (get_oid(p, &util->oid)) {
+				error(_("could not parse commit '%s'"), p);
+				free(util);
+				string_list_clear(list, 1);
+				strbuf_release(&buf);
+				strbuf_release(&line);
+				fclose(in);
+				finish_command(&cp);
+				return -1;
+			}
+			util->matching = -1;
+			in_header = 1;
+			continue;
+		}
+
+		if (starts_with(line.buf, "diff --git")) {
+			in_header = 0;
+			strbuf_addch(&buf, '\n');
+			if (!util->diff_offset)
+				util->diff_offset = buf.len;
+			strbuf_addbuf(&buf, &line);
+		} else if (in_header) {
+			if (starts_with(line.buf, "Author: ")) {
+				strbuf_addbuf(&buf, &line);
+				strbuf_addstr(&buf, "\n\n");
+			} else if (starts_with(line.buf, "    ")) {
+				strbuf_addbuf(&buf, &line);
+				strbuf_addch(&buf, '\n');
+			}
+			continue;
+		} else if (starts_with(line.buf, "@@ "))
+			strbuf_addstr(&buf, "@@");
+		else if (!line.buf[0] || starts_with(line.buf, "index "))
+			/*
+			 * A completely blank (not ' \n', which is context)
+			 * line is not valid in a diff.  We skip it
+			 * silently, because this neatly handles the blank
+			 * separator line between commits in git-log
+			 * output.
+			 *
+			 * We also want to ignore the diff's `index` lines
+			 * because they contain exact blob hashes in which
+			 * we are not interested.
+			 */
+			continue;
+		else
+			strbuf_addbuf(&buf, &line);
+
+		strbuf_addch(&buf, '\n');
+		util->diffsize++;
+	}
+	fclose(in);
+	strbuf_release(&line);
+
+	if (util)
+		string_list_append(list, buf.buf)->util = util;
+	strbuf_release(&buf);
+
+	if (finish_command(&cp))
+		return -1;
+
+	return 0;
+}
+
+static int patch_util_cmp(const void *dummy, const struct patch_util *a,
+		     const struct patch_util *b, const char *keydata)
+{
+	return strcmp(a->diff, keydata ? keydata : b->diff);
+}
+
+static void find_exact_matches(struct string_list *a, struct string_list *b)
+{
+	struct hashmap map;
+	int i;
+
+	hashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);
+
+	/* First, add the patches of a to a hash map */
+	for (i = 0; i < a->nr; i++) {
+		struct patch_util *util = a->items[i].util;
+
+		util->i = i;
+		util->patch = a->items[i].string;
+		util->diff = util->patch + util->diff_offset;
+		hashmap_entry_init(util, strhash(util->diff));
+		hashmap_add(&map, util);
+	}
+
+	/* Now try to find exact matches in b */
+	for (i = 0; i < b->nr; i++) {
+		struct patch_util *util = b->items[i].util, *other;
+
+		util->i = i;
+		util->patch = b->items[i].string;
+		util->diff = util->patch + util->diff_offset;
+		hashmap_entry_init(util, strhash(util->diff));
+		other = hashmap_remove(&map, util, NULL);
+		if (other) {
+			if (other->matching >= 0)
+				BUG("already assigned!");
+
+			other->matching = i;
+			util->matching = other->i;
+		}
+	}
+
+	hashmap_free(&map, 0);
+}
+
+static void diffsize_consume(void *data, char *line, unsigned long len)
+{
+	(*(int *)data)++;
+}
+
+static int diffsize(const char *a, const char *b)
+{
+	xpparam_t pp = { 0 };
+	xdemitconf_t cfg = { 0 };
+	mmfile_t mf1, mf2;
+	int count = 0;
+
+	mf1.ptr = (char *)a;
+	mf1.size = strlen(a);
+	mf2.ptr = (char *)b;
+	mf2.size = strlen(b);
+
+	cfg.ctxlen = 3;
+	if (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))
+		return count;
+
+	error(_("failed to generate diff"));
+	return COST_MAX;
+}
+
+static void get_correspondences(struct string_list *a, struct string_list *b,
+				int creation_factor)
+{
+	int n = a->nr + b->nr;
+	int *cost, c, *a2b, *b2a;
+	int i, j;
+
+	ALLOC_ARRAY(cost, st_mult(n, n));
+	ALLOC_ARRAY(a2b, n);
+	ALLOC_ARRAY(b2a, n);
+
+	for (i = 0; i < a->nr; i++) {
+		struct patch_util *a_util = a->items[i].util;
+
+		for (j = 0; j < b->nr; j++) {
+			struct patch_util *b_util = b->items[j].util;
+
+			if (a_util->matching == j)
+				c = 0;
+			else if (a_util->matching < 0 && b_util->matching < 0)
+				c = diffsize(a_util->diff, b_util->diff);
+			else
+				c = COST_MAX;
+			cost[i + n * j] = c;
+		}
+
+		c = a_util->matching < 0 ?
+			a_util->diffsize * creation_factor / 100 : COST_MAX;
+		for (j = b->nr; j < n; j++)
+			cost[i + n * j] = c;
+	}
+
+	for (j = 0; j < b->nr; j++) {
+		struct patch_util *util = b->items[j].util;
+
+		c = util->matching < 0 ?
+			util->diffsize * creation_factor / 100 : COST_MAX;
+		for (i = a->nr; i < n; i++)
+			cost[i + n * j] = c;
+	}
+
+	for (i = a->nr; i < n; i++)
+		for (j = b->nr; j < n; j++)
+			cost[i + n * j] = 0;
+
+	compute_assignment(n, n, cost, a2b, b2a);
+
+	for (i = 0; i < a->nr; i++)
+		if (a2b[i] >= 0 && a2b[i] < b->nr) {
+			struct patch_util *a_util = a->items[i].util;
+			struct patch_util *b_util = b->items[a2b[i]].util;
+
+			a_util->matching = a2b[i];
+			b_util->matching = i;
+		}
+
+	free(cost);
+	free(a2b);
+	free(b2a);
+}
+
+static const char *short_oid(struct patch_util *util)
+{
+	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
+}
+
+static void output(struct string_list *a, struct string_list *b)
+{
+	int i;
+
+	for (i = 0; i < b->nr; i++) {
+		struct patch_util *util = b->items[i].util, *prev;
+
+		if (util->matching < 0)
+			printf("-: -------- > %d: %s\n",
+					i + 1, short_oid(util));
+		else {
+			prev = a->items[util->matching].util;
+			printf("%d: %s ! %d: %s\n",
+			       util->matching + 1, short_oid(prev),
+			       i + 1, short_oid(util));
+		}
+	}
+
+	for (i = 0; i < a->nr; i++) {
+		struct patch_util *util = a->items[i].util;
+
+		if (util->matching < 0)
+			printf("%d: %s < -: --------\n",
+			       i + 1, short_oid(util));
+	}
+}
+
+int show_range_diff(const char *range1, const char *range2,
+		    int creation_factor)
+{
+	int res = 0;
+
+	struct string_list branch1 = STRING_LIST_INIT_DUP;
+	struct string_list branch2 = STRING_LIST_INIT_DUP;
+
+	if (read_patches(range1, &branch1))
+		res = error(_("could not parse log for '%s'"), range1);
+	if (!res && read_patches(range2, &branch2))
+		res = error(_("could not parse log for '%s'"), range2);
+
+	if (!res) {
+		find_exact_matches(&branch1, &branch2);
+		get_correspondences(&branch1, &branch2, creation_factor);
+		output(&branch1, &branch2);
+	}
+
+	string_list_clear(&branch1, 1);
+	string_list_clear(&branch2, 1);
+
+	return res;
+}
diff --git a/range-diff.h b/range-diff.h
new file mode 100644
index 000000000..dd30449c4
--- /dev/null
+++ b/range-diff.h
@@ -0,0 +1,7 @@
+#ifndef BRANCH_DIFF_H
+#define BRANCH_DIFF_H
+
+int show_range_diff(const char *range1, const char *range2,
+		    int creation_factor);
+
+#endif
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 04/21] range-diff: improve the order of the shown commits
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (2 preceding siblings ...)
  2018-07-21 22:04       ` [PATCH v4 03/21] range-diff: first rudimentary implementation Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:04       ` Johannes Schindelin via GitGitGadget
  2018-07-21 22:04       ` [PATCH v4 05/21] range-diff: also show the diff between patches Johannes Schindelin via GitGitGadget
                         ` (19 subsequent siblings)
  23 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:04 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

This patch lets `git range-diff` use the same order as tbdiff.

The idea is simple: for left-to-right readers, it is natural to assume
that the `git range-diff` is performed between an older vs a newer
version of the branch. As such, the user is probably more interested in
the question "where did this come from?" rather than "where did that one
go?".

To that end, we list the commits in the order of the second commit range
("the newer version"), inserting the unmatched commits of the first
commit range as soon as all their predecessors have been shown.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 59 +++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 40 insertions(+), 19 deletions(-)

diff --git a/range-diff.c b/range-diff.c
index 15d418afa..2d94200d3 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -12,7 +12,7 @@ struct patch_util {
 	struct hashmap_entry e;
 	const char *diff, *patch;
 
-	int i;
+	int i, shown;
 	int diffsize;
 	size_t diff_offset;
 	/* the index of the matching item in the other branch, or -1 */
@@ -260,28 +260,49 @@ static const char *short_oid(struct patch_util *util)
 
 static void output(struct string_list *a, struct string_list *b)
 {
-	int i;
-
-	for (i = 0; i < b->nr; i++) {
-		struct patch_util *util = b->items[i].util, *prev;
+	int i = 0, j = 0;
+
+	/*
+	 * We assume the user is really more interested in the second argument
+	 * ("newer" version). To that end, we print the output in the order of
+	 * the RHS (the `b` parameter). To put the LHS (the `a` parameter)
+	 * commits that are no longer in the RHS into a good place, we place
+	 * them once we have shown all of their predecessors in the LHS.
+	 */
+
+	while (i < a->nr || j < b->nr) {
+		struct patch_util *a_util, *b_util;
+		a_util = i < a->nr ? a->items[i].util : NULL;
+		b_util = j < b->nr ? b->items[j].util : NULL;
+
+		/* Skip all the already-shown commits from the LHS. */
+		while (i < a->nr && a_util->shown)
+			a_util = ++i < a->nr ? a->items[i].util : NULL;
+
+		/* Show unmatched LHS commit whose predecessors were shown. */
+		if (i < a->nr && a_util->matching < 0) {
+			printf("%d: %s < -: --------\n",
+			       i + 1, short_oid(a_util));
+			i++;
+			continue;
+		}
 
-		if (util->matching < 0)
+		/* Show unmatched RHS commits. */
+		while (j < b->nr && b_util->matching < 0) {
 			printf("-: -------- > %d: %s\n",
-					i + 1, short_oid(util));
-		else {
-			prev = a->items[util->matching].util;
-			printf("%d: %s ! %d: %s\n",
-			       util->matching + 1, short_oid(prev),
-			       i + 1, short_oid(util));
+			       j + 1, short_oid(b_util));
+			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
-	}
-
-	for (i = 0; i < a->nr; i++) {
-		struct patch_util *util = a->items[i].util;
 
-		if (util->matching < 0)
-			printf("%d: %s < -: --------\n",
-			       i + 1, short_oid(util));
+		/* Show matching LHS/RHS pair. */
+		if (j < b->nr) {
+			a_util = a->items[b_util->matching].util;
+			printf("%d: %s ! %d: %s\n",
+			       b_util->matching + 1, short_oid(a_util),
+			       j + 1, short_oid(b_util));
+			a_util->shown = 1;
+			j++;
+		}
 	}
 }
 
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 05/21] range-diff: also show the diff between patches
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (3 preceding siblings ...)
  2018-07-21 22:04       ` [PATCH v4 04/21] range-diff: improve the order of the shown commits Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:04       ` Johannes Schindelin via GitGitGadget
  2018-07-29 19:03         ` Thomas Gummerer
  2018-07-21 22:04       ` [PATCH v4 06/21] range-diff: right-trim commit messages Johannes Schindelin via GitGitGadget
                         ` (18 subsequent siblings)
  23 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:04 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

Just like tbdiff, we now show the diff between matching patches. This is
a "diff of two diffs", so it can be a bit daunting to read for the
beginner.

An alternative would be to display an interdiff, i.e. the hypothetical
diff which is the result of first reverting the old diff and then
applying the new diff.

Especially when rebasing often, an interdiff is often not feasible,
though: if the old diff cannot be applied in reverse (due to a moving
upstream), an interdiff can simply not be inferred.

This commit brings `range-diff` closer to feature parity with regard
to tbdiff.

To make `git range-diff` respect e.g. color.diff.* settings, we have
to adjust git_branch_config() accordingly.

Note: while we now parse diff options such as --color, the effect is not
yet the same as in tbdiff, where also the commit pairs would be colored.
This is left for a later commit.

Note also: while tbdiff accepts the `--no-patches` option to suppress
these diffs between patches, we prefer the `-s` option that is
automatically supported via our use of diff_opt_parse().

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/range-diff.c | 25 ++++++++++++++++++++++---
 range-diff.c         | 34 +++++++++++++++++++++++++++++++---
 range-diff.h         |  4 +++-
 3 files changed, 56 insertions(+), 7 deletions(-)

diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 3881da246..093202117 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -2,6 +2,7 @@
 #include "builtin.h"
 #include "parse-options.h"
 #include "range-diff.h"
+#include "config.h"
 
 static const char * const builtin_range_diff_usage[] = {
 N_("git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
@@ -13,16 +14,33 @@ NULL
 int cmd_range_diff(int argc, const char **argv, const char *prefix)
 {
 	int creation_factor = 60;
+	struct diff_options diffopt = { NULL };
 	struct option options[] = {
 		OPT_INTEGER(0, "creation-factor", &creation_factor,
 			    N_("Percentage by which creation is weighted")),
 		OPT_END()
 	};
-	int res = 0;
+	int i, j, res = 0;
 	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
 
+	git_config(git_diff_ui_config, NULL);
+
+	diff_setup(&diffopt);
+	diffopt.output_format = DIFF_FORMAT_PATCH;
+
 	argc = parse_options(argc, argv, NULL, options,
-			     builtin_range_diff_usage, 0);
+			     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);
+
+	for (i = j = 0; i < argc; ) {
+		int c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);
+
+		if (!c)
+			argv[j++] = argv[i++];
+		else
+			i += c;
+	}
+	argc = j;
+	diff_setup_done(&diffopt);
 
 	if (argc == 2) {
 		if (!strstr(argv[0], ".."))
@@ -57,7 +75,8 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 		usage_with_options(builtin_range_diff_usage, options);
 	}
 
-	res = show_range_diff(range1.buf, range2.buf, creation_factor);
+	res = show_range_diff(range1.buf, range2.buf, creation_factor,
+			      &diffopt);
 
 	strbuf_release(&range1);
 	strbuf_release(&range2);
diff --git a/range-diff.c b/range-diff.c
index 2d94200d3..71883a4b7 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -6,6 +6,7 @@
 #include "hashmap.h"
 #include "xdiff-interface.h"
 #include "linear-assignment.h"
+#include "diffcore.h"
 
 struct patch_util {
 	/* For the search for an exact match */
@@ -258,7 +259,31 @@ static const char *short_oid(struct patch_util *util)
 	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
 }
 
-static void output(struct string_list *a, struct string_list *b)
+static struct diff_filespec *get_filespec(const char *name, const char *p)
+{
+	struct diff_filespec *spec = alloc_filespec(name);
+
+	fill_filespec(spec, &null_oid, 0, 0644);
+	spec->data = (char *)p;
+	spec->size = strlen(p);
+	spec->should_munmap = 0;
+	spec->is_stdin = 1;
+
+	return spec;
+}
+
+static void patch_diff(const char *a, const char *b,
+			      struct diff_options *diffopt)
+{
+	diff_queue(&diff_queued_diff,
+		   get_filespec("a", a), get_filespec("b", b));
+
+	diffcore_std(diffopt);
+	diff_flush(diffopt);
+}
+
+static void output(struct string_list *a, struct string_list *b,
+		   struct diff_options *diffopt)
 {
 	int i = 0, j = 0;
 
@@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)
 			printf("%d: %s ! %d: %s\n",
 			       b_util->matching + 1, short_oid(a_util),
 			       j + 1, short_oid(b_util));
+			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
+				patch_diff(a->items[b_util->matching].string,
+					   b->items[j].string, diffopt);
 			a_util->shown = 1;
 			j++;
 		}
@@ -307,7 +335,7 @@ static void output(struct string_list *a, struct string_list *b)
 }
 
 int show_range_diff(const char *range1, const char *range2,
-		    int creation_factor)
+		    int creation_factor, struct diff_options *diffopt)
 {
 	int res = 0;
 
@@ -322,7 +350,7 @@ int show_range_diff(const char *range1, const char *range2,
 	if (!res) {
 		find_exact_matches(&branch1, &branch2);
 		get_correspondences(&branch1, &branch2, creation_factor);
-		output(&branch1, &branch2);
+		output(&branch1, &branch2, diffopt);
 	}
 
 	string_list_clear(&branch1, 1);
diff --git a/range-diff.h b/range-diff.h
index dd30449c4..aea9d43f3 100644
--- a/range-diff.h
+++ b/range-diff.h
@@ -1,7 +1,9 @@
 #ifndef BRANCH_DIFF_H
 #define BRANCH_DIFF_H
 
+#include "diff.h"
+
 int show_range_diff(const char *range1, const char *range2,
-		    int creation_factor);
+		    int creation_factor, struct diff_options *diffopt);
 
 #endif
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 06/21] range-diff: right-trim commit messages
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (4 preceding siblings ...)
  2018-07-21 22:04       ` [PATCH v4 05/21] range-diff: also show the diff between patches Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:04       ` Johannes Schindelin via GitGitGadget
  2018-07-21 22:04       ` [PATCH v4 07/21] range-diff: indent the diffs just like tbdiff Johannes Schindelin via GitGitGadget
                         ` (17 subsequent siblings)
  23 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:04 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When comparing commit messages, we need to keep in mind that they are
indented by four spaces. That is, empty lines are no longer empty, but
have "trailing whitespace". When displaying them in color, that results
in those nagging red lines.

Let's just right-trim the lines in the commit message, it's not like
trailing white-space in the commit messages are important enough to care
about in `git range-diff`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/range-diff.c b/range-diff.c
index 71883a4b7..1ecee2c09 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -85,6 +85,7 @@ static int read_patches(const char *range, struct string_list *list)
 				strbuf_addbuf(&buf, &line);
 				strbuf_addstr(&buf, "\n\n");
 			} else if (starts_with(line.buf, "    ")) {
+				strbuf_rtrim(&line);
 				strbuf_addbuf(&buf, &line);
 				strbuf_addch(&buf, '\n');
 			}
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 07/21] range-diff: indent the diffs just like tbdiff
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (5 preceding siblings ...)
  2018-07-21 22:04       ` [PATCH v4 06/21] range-diff: right-trim commit messages Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:04       ` Johannes Schindelin via GitGitGadget
  2018-07-21 22:04       ` [PATCH v4 08/21] range-diff: suppress the diff headers Johannes Schindelin via GitGitGadget
                         ` (16 subsequent siblings)
  23 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:04 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

The main information in the `range-diff` view comes from the list of
matching and non-matching commits, the diffs are additional information.
Indenting them helps with the reading flow.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/range-diff.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 093202117..96e8bf841 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -11,6 +11,11 @@ N_("git range-diff [<options>] <base> <old-tip> <new-tip>"),
 NULL
 };
 
+static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)
+{
+	return data;
+}
+
 int cmd_range_diff(int argc, const char **argv, const char *prefix)
 {
 	int creation_factor = 60;
@@ -21,12 +26,16 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 	int i, j, res = 0;
+	struct strbuf four_spaces = STRBUF_INIT;
 	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
 
 	git_config(git_diff_ui_config, NULL);
 
 	diff_setup(&diffopt);
 	diffopt.output_format = DIFF_FORMAT_PATCH;
+	diffopt.output_prefix = output_prefix_cb;
+	strbuf_addstr(&four_spaces, "    ");
+	diffopt.output_prefix_data = &four_spaces;
 
 	argc = parse_options(argc, argv, NULL, options,
 			     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);
@@ -80,6 +89,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 
 	strbuf_release(&range1);
 	strbuf_release(&range2);
+	strbuf_release(&four_spaces);
 
 	return res;
 }
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 08/21] range-diff: suppress the diff headers
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (6 preceding siblings ...)
  2018-07-21 22:04       ` [PATCH v4 07/21] range-diff: indent the diffs just like tbdiff Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:04       ` Johannes Schindelin via GitGitGadget
  2018-07-21 22:04       ` [PATCH v4 09/21] range-diff: adjust the output of the commit pairs Johannes Schindelin via GitGitGadget
                         ` (15 subsequent siblings)
  23 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:04 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When showing the diff between corresponding patches of the two branch
versions, we have to make up a fake filename to run the diff machinery.

That filename does not carry any meaningful information, hence tbdiff
suppresses it. So we should, too.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/range-diff.c | 1 +
 diff.c               | 5 ++++-
 diff.h               | 1 +
 3 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 96e8bf841..10065315d 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -33,6 +33,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 
 	diff_setup(&diffopt);
 	diffopt.output_format = DIFF_FORMAT_PATCH;
+	diffopt.flags.suppress_diff_headers = 1;
 	diffopt.output_prefix = output_prefix_cb;
 	strbuf_addstr(&four_spaces, "    ");
 	diffopt.output_prefix_data = &four_spaces;
diff --git a/diff.c b/diff.c
index dc53a19ba..a94a8214f 100644
--- a/diff.c
+++ b/diff.c
@@ -3190,13 +3190,16 @@ static void builtin_diff(const char *name_a,
 		memset(&xpp, 0, sizeof(xpp));
 		memset(&xecfg, 0, sizeof(xecfg));
 		memset(&ecbdata, 0, sizeof(ecbdata));
+		if (o->flags.suppress_diff_headers)
+			lbl[0] = NULL;
 		ecbdata.label_path = lbl;
 		ecbdata.color_diff = want_color(o->use_color);
 		ecbdata.ws_rule = whitespace_rule(name_b);
 		if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
 			check_blank_at_eof(&mf1, &mf2, &ecbdata);
 		ecbdata.opt = o;
-		ecbdata.header = header.len ? &header : NULL;
+		if (header.len && !o->flags.suppress_diff_headers)
+			ecbdata.header = &header;
 		xpp.flags = o->xdl_opts;
 		xpp.anchors = o->anchors;
 		xpp.anchors_nr = o->anchors_nr;
diff --git a/diff.h b/diff.h
index dedac472c..928f48995 100644
--- a/diff.h
+++ b/diff.h
@@ -94,6 +94,7 @@ struct diff_flags {
 	unsigned funccontext:1;
 	unsigned default_follow_renames:1;
 	unsigned stat_with_summary:1;
+	unsigned suppress_diff_headers:1;
 };
 
 static inline void diff_flags_or(struct diff_flags *a,
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 09/21] range-diff: adjust the output of the commit pairs
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (7 preceding siblings ...)
  2018-07-21 22:04       ` [PATCH v4 08/21] range-diff: suppress the diff headers Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:04       ` Johannes Schindelin via GitGitGadget
  2018-07-29 19:38         ` Thomas Gummerer
  2018-07-29 21:28         ` Thomas Gummerer
  2018-07-21 22:04       ` [PATCH v4 10/21] range-diff: do not show "function names" in hunk headers Johannes Schindelin via GitGitGadget
                         ` (14 subsequent siblings)
  23 siblings, 2 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:04 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

This change brings `git range-diff` yet another step closer to
feature parity with tbdiff: it now shows the oneline, too, and indicates
with `=` when the commits have identical diffs.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 64 ++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 55 insertions(+), 9 deletions(-)

diff --git a/range-diff.c b/range-diff.c
index 1ecee2c09..8329f52e7 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -7,6 +7,8 @@
 #include "xdiff-interface.h"
 #include "linear-assignment.h"
 #include "diffcore.h"
+#include "commit.h"
+#include "pretty.h"
 
 struct patch_util {
 	/* For the search for an exact match */
@@ -255,9 +257,54 @@ static void get_correspondences(struct string_list *a, struct string_list *b,
 	free(b2a);
 }
 
-static const char *short_oid(struct patch_util *util)
+static void output_pair_header(struct strbuf *buf,
+			       struct strbuf *dashes,
+			       struct patch_util *a_util,
+			       struct patch_util *b_util)
 {
-	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
+	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
+	struct commit *commit;
+
+	if (!dashes->len)
+		strbuf_addchars(dashes, '-',
+				strlen(find_unique_abbrev(oid,
+							  DEFAULT_ABBREV)));
+
+	strbuf_reset(buf);
+	if (!a_util)
+		strbuf_addf(buf, "-:  %s ", dashes->buf);
+	else
+		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
+			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
+
+	if (!a_util)
+		strbuf_addch(buf, '>');
+	else if (!b_util)
+		strbuf_addch(buf, '<');
+	else if (strcmp(a_util->patch, b_util->patch))
+		strbuf_addch(buf, '!');
+	else
+		strbuf_addch(buf, '=');
+
+	if (!b_util)
+		strbuf_addf(buf, " -:  %s", dashes->buf);
+	else
+		strbuf_addf(buf, " %d:  %s", b_util->i + 1,
+			    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));
+
+	commit = lookup_commit_reference(oid);
+	if (commit) {
+		const char *commit_buffer = get_commit_buffer(commit, NULL);
+		const char *subject;
+
+		find_commit_subject(commit_buffer, &subject);
+		strbuf_addch(buf, ' ');
+		format_subject(buf, subject, " ");
+		unuse_commit_buffer(commit, commit_buffer);
+	}
+	strbuf_addch(buf, '\n');
+
+	fwrite(buf->buf, buf->len, 1, stdout);
 }
 
 static struct diff_filespec *get_filespec(const char *name, const char *p)
@@ -286,6 +333,7 @@ static void patch_diff(const char *a, const char *b,
 static void output(struct string_list *a, struct string_list *b,
 		   struct diff_options *diffopt)
 {
+	struct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;
 	int i = 0, j = 0;
 
 	/*
@@ -307,25 +355,21 @@ static void output(struct string_list *a, struct string_list *b,
 
 		/* Show unmatched LHS commit whose predecessors were shown. */
 		if (i < a->nr && a_util->matching < 0) {
-			printf("%d: %s < -: --------\n",
-			       i + 1, short_oid(a_util));
+			output_pair_header(&buf, &dashes, a_util, NULL);
 			i++;
 			continue;
 		}
 
 		/* Show unmatched RHS commits. */
 		while (j < b->nr && b_util->matching < 0) {
-			printf("-: -------- > %d: %s\n",
-			       j + 1, short_oid(b_util));
+			output_pair_header(&buf, &dashes, NULL, b_util);
 			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
 
 		/* Show matching LHS/RHS pair. */
 		if (j < b->nr) {
 			a_util = a->items[b_util->matching].util;
-			printf("%d: %s ! %d: %s\n",
-			       b_util->matching + 1, short_oid(a_util),
-			       j + 1, short_oid(b_util));
+			output_pair_header(&buf, &dashes, a_util, b_util);
 			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
 				patch_diff(a->items[b_util->matching].string,
 					   b->items[j].string, diffopt);
@@ -333,6 +377,8 @@ static void output(struct string_list *a, struct string_list *b,
 			j++;
 		}
 	}
+	strbuf_release(&buf);
+	strbuf_release(&dashes);
 }
 
 int show_range_diff(const char *range1, const char *range2,
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 10/21] range-diff: do not show "function names" in hunk headers
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (8 preceding siblings ...)
  2018-07-21 22:04       ` [PATCH v4 09/21] range-diff: adjust the output of the commit pairs Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:04       ` Johannes Schindelin via GitGitGadget
  2018-07-29 20:52         ` Thomas Gummerer
  2018-07-21 22:05       ` [PATCH v4 11/21] range-diff: add tests Thomas Rast via GitGitGadget
                         ` (13 subsequent siblings)
  23 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:04 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

We are comparing complete, formatted commit messages with patches. There
are no function names here, so stop looking for them.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/range-diff.c b/range-diff.c
index 8329f52e7..3fc3a4018 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -9,6 +9,7 @@
 #include "diffcore.h"
 #include "commit.h"
 #include "pretty.h"
+#include "userdiff.h"
 
 struct patch_util {
 	/* For the search for an exact match */
@@ -307,6 +308,10 @@ static void output_pair_header(struct strbuf *buf,
 	fwrite(buf->buf, buf->len, 1, stdout);
 }
 
+static struct userdiff_driver no_func_name = {
+	.funcname = { "$^", 0 }
+};
+
 static struct diff_filespec *get_filespec(const char *name, const char *p)
 {
 	struct diff_filespec *spec = alloc_filespec(name);
@@ -316,6 +321,7 @@ static struct diff_filespec *get_filespec(const char *name, const char *p)
 	spec->size = strlen(p);
 	spec->should_munmap = 0;
 	spec->is_stdin = 1;
+	spec->driver = &no_func_name;
 
 	return spec;
 }
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 11/21] range-diff: add tests
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (9 preceding siblings ...)
  2018-07-21 22:04       ` [PATCH v4 10/21] range-diff: do not show "function names" in hunk headers Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:05       ` Thomas Rast via GitGitGadget
  2018-07-22  5:04         ` Eric Sunshine
  2018-07-23 21:25         ` Stefan Beller
  2018-07-21 22:05       ` [PATCH v4 12/21] range-diff: use color for the commit pairs Johannes Schindelin via GitGitGadget
                         ` (12 subsequent siblings)
  23 siblings, 2 replies; 387+ messages in thread
From: Thomas Rast via GitGitGadget @ 2018-07-21 22:05 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Thomas Rast

From: Thomas Rast <tr@thomasrast.ch>

These are essentially lifted from https://github.com/trast/tbdiff, with
light touch-ups to account for the command now being names `git
range-diff`.

Apart from renaming `tbdiff` to `range-diff`, only one test case needed
to be adjusted: 11 - 'changed message'.

The underlying reason it had to be adjusted is that diff generation is
sometimes ambiguous. In this case, a comment line and an empty line are
added, but it is ambiguous whether they were added after the existing
empty line, or whether an empty line and the comment line are added
*before* the existing empty line. And apparently xdiff picks a different
option here than Python's difflib.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/.gitattributes       |   1 +
 t/t3206-range-diff.sh  | 145 ++++++++++
 t/t3206/history.export | 604 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 750 insertions(+)
 create mode 100755 t/t3206-range-diff.sh
 create mode 100644 t/t3206/history.export

diff --git a/t/.gitattributes b/t/.gitattributes
index 3bd959ae5..b17bf71b8 100644
--- a/t/.gitattributes
+++ b/t/.gitattributes
@@ -1,6 +1,7 @@
 t[0-9][0-9][0-9][0-9]/* -whitespace
 /diff-lib/* eol=lf
 /t0110/url-* binary
+/t3206/* eol=lf
 /t3900/*.txt eol=lf
 /t3901/*.txt eol=lf
 /t4034/*/* eol=lf
diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
new file mode 100755
index 000000000..2237c7f4a
--- /dev/null
+++ b/t/t3206-range-diff.sh
@@ -0,0 +1,145 @@
+#!/bin/sh
+
+test_description='range-diff tests'
+
+. ./test-lib.sh
+
+# Note that because of the range-diff's heuristics, test_commit does more
+# harm than good.  We need some real history.
+
+test_expect_success 'setup' '
+	git fast-import < "$TEST_DIRECTORY"/t3206/history.export
+'
+
+test_expect_success 'simple A..B A..C (unmodified)' '
+	git range-diff --no-color master..topic master..unmodified \
+		>actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  35b9b25 s/5/A/
+	2:  fccce22 = 2:  de345ab s/4/A/
+	3:  147e64e = 3:  9af6654 s/11/B/
+	4:  a63e992 = 4:  2901f77 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'simple B...C (unmodified)' '
+	git range-diff --no-color topic...unmodified >actual &&
+	# same "expected" as above
+	test_cmp expected actual
+'
+
+test_expect_success 'simple A B C (unmodified)' '
+	git range-diff --no-color master topic unmodified >actual &&
+	# same "expected" as above
+	test_cmp expected actual
+'
+
+test_expect_success 'trivial reordering' '
+	git range-diff --no-color master topic reordered >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  aca177a s/5/A/
+	3:  147e64e = 2:  14ad629 s/11/B/
+	4:  a63e992 = 3:  ee58208 s/12/B/
+	2:  fccce22 = 4:  307b27a s/4/A/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'removed a commit' '
+	git range-diff --no-color master topic removed >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  7657159 s/5/A/
+	2:  fccce22 < -:  ------- s/4/A/
+	3:  147e64e = 2:  43d84d3 s/11/B/
+	4:  a63e992 = 3:  a740396 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'added a commit' '
+	git range-diff --no-color master topic added >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  2716022 s/5/A/
+	2:  fccce22 = 2:  b62accd s/4/A/
+	-:  ------- > 3:  df46cfa s/6/A/
+	3:  147e64e = 4:  3e64548 s/11/B/
+	4:  a63e992 = 5:  12b4063 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'new base, A B C' '
+	git range-diff --no-color master topic rebased >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  cc9c443 s/5/A/
+	2:  fccce22 = 2:  c5d9641 s/4/A/
+	3:  147e64e = 3:  28cc2b6 s/11/B/
+	4:  a63e992 = 4:  5628ab7 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'new base, B...C' '
+	# this syntax includes the commits from master!
+	git range-diff --no-color topic...rebased >actual &&
+	cat >expected <<-EOF &&
+	-:  ------- > 1:  a31b12e unrelated
+	1:  4de457d = 2:  cc9c443 s/5/A/
+	2:  fccce22 = 3:  c5d9641 s/4/A/
+	3:  147e64e = 4:  28cc2b6 s/11/B/
+	4:  a63e992 = 5:  5628ab7 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'changed commit' '
+	git range-diff --no-color topic...changed >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  a4b3333 s/5/A/
+	2:  fccce22 = 2:  f51d370 s/4/A/
+	3:  147e64e ! 3:  0559556 s/11/B/
+	    @@ -10,7 +10,7 @@
+	      9
+	      10
+	     -11
+	    -+B
+	    ++BB
+	      12
+	      13
+	      14
+	4:  a63e992 ! 4:  d966c5c s/12/B/
+	    @@ -8,7 +8,7 @@
+	     @@
+	      9
+	      10
+	    - B
+	    + BB
+	     -12
+	     +B
+	      13
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'changed message' '
+	git range-diff --no-color topic...changed-message >actual &&
+	sed s/Z/\ /g >expected <<-EOF &&
+	1:  4de457d = 1:  f686024 s/5/A/
+	2:  fccce22 ! 2:  4ab067d s/4/A/
+	    @@ -2,6 +2,8 @@
+	    Z
+	    Z    s/4/A/
+	    Z
+	    +    Also a silly comment here!
+	    +
+	    Zdiff --git a/file b/file
+	    Z--- a/file
+	    Z+++ b/file
+	3:  147e64e = 3:  b9cb956 s/11/B/
+	4:  a63e992 = 4:  8add5f1 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_done
diff --git a/t/t3206/history.export b/t/t3206/history.export
new file mode 100644
index 000000000..b8ffff094
--- /dev/null
+++ b/t/t3206/history.export
@@ -0,0 +1,604 @@
+blob
+mark :1
+data 51
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+reset refs/heads/removed
+commit refs/heads/removed
+mark :2
+author Thomas Rast <trast@inf.ethz.ch> 1374424921 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374484724 +0200
+data 8
+initial
+M 100644 :1 file
+
+blob
+mark :3
+data 51
+1
+2
+3
+4
+A
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :4
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+blob
+mark :5
+data 51
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :6
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+data 7
+s/4/A/
+from :4
+M 100644 :5 file
+
+blob
+mark :7
+data 50
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+B
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :8
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+data 8
+s/11/B/
+from :6
+M 100644 :7 file
+
+blob
+mark :9
+data 49
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+B
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :10
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+data 8
+s/12/B/
+from :8
+M 100644 :9 file
+
+blob
+mark :11
+data 10
+unrelated
+
+commit refs/heads/master
+mark :12
+author Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200
+data 10
+unrelated
+from :2
+M 100644 :11 otherfile
+
+commit refs/heads/rebased
+mark :13
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485137 +0200
+data 7
+s/5/A/
+from :12
+M 100644 :3 file
+
+commit refs/heads/rebased
+mark :14
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
+data 7
+s/4/A/
+from :13
+M 100644 :5 file
+
+commit refs/heads/rebased
+mark :15
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
+data 8
+s/11/B/
+from :14
+M 100644 :7 file
+
+commit refs/heads/rebased
+mark :16
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
+data 8
+s/12/B/
+from :15
+M 100644 :9 file
+
+commit refs/heads/added
+mark :17
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/added
+mark :18
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 7
+s/4/A/
+from :17
+M 100644 :5 file
+
+blob
+mark :19
+data 51
+1
+2
+3
+A
+A
+A
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/added
+mark :20
+author Thomas Rast <trast@inf.ethz.ch> 1374485186 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 7
+s/6/A/
+from :18
+M 100644 :19 file
+
+blob
+mark :21
+data 50
+1
+2
+3
+A
+A
+A
+7
+8
+9
+10
+B
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/added
+mark :22
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 8
+s/11/B/
+from :20
+M 100644 :21 file
+
+blob
+mark :23
+data 49
+1
+2
+3
+A
+A
+A
+7
+8
+9
+10
+B
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/added
+mark :24
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 8
+s/12/B/
+from :22
+M 100644 :23 file
+
+commit refs/heads/reordered
+mark :25
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+blob
+mark :26
+data 50
+1
+2
+3
+4
+A
+6
+7
+8
+9
+10
+B
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/reordered
+mark :27
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 8
+s/11/B/
+from :25
+M 100644 :26 file
+
+blob
+mark :28
+data 49
+1
+2
+3
+4
+A
+6
+7
+8
+9
+10
+B
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/reordered
+mark :29
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 8
+s/12/B/
+from :27
+M 100644 :28 file
+
+commit refs/heads/reordered
+mark :30
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 7
+s/4/A/
+from :29
+M 100644 :9 file
+
+commit refs/heads/changed
+mark :31
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/changed
+mark :32
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 7
+s/4/A/
+from :31
+M 100644 :5 file
+
+blob
+mark :33
+data 51
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+BB
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/changed
+mark :34
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 8
+s/11/B/
+from :32
+M 100644 :33 file
+
+blob
+mark :35
+data 50
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+BB
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/changed
+mark :36
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 8
+s/12/B/
+from :34
+M 100644 :35 file
+
+commit refs/heads/changed-message
+mark :37
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/changed-message
+mark :38
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200
+data 35
+s/4/A/
+
+Also a silly comment here!
+from :37
+M 100644 :5 file
+
+commit refs/heads/changed-message
+mark :39
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200
+data 8
+s/11/B/
+from :38
+M 100644 :7 file
+
+commit refs/heads/changed-message
+mark :40
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200
+data 8
+s/12/B/
+from :39
+M 100644 :9 file
+
+commit refs/heads/unmodified
+mark :41
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/unmodified
+mark :42
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200
+data 7
+s/4/A/
+from :41
+M 100644 :5 file
+
+commit refs/heads/unmodified
+mark :43
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200
+data 8
+s/11/B/
+from :42
+M 100644 :7 file
+
+commit refs/heads/unmodified
+mark :44
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200
+data 8
+s/12/B/
+from :43
+M 100644 :9 file
+
+commit refs/heads/removed
+mark :45
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/removed
+mark :46
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
+data 8
+s/11/B/
+from :45
+M 100644 :26 file
+
+commit refs/heads/removed
+mark :47
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
+data 8
+s/12/B/
+from :46
+M 100644 :28 file
+
+reset refs/heads/removed
+from :47
+
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 12/21] range-diff: use color for the commit pairs
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (10 preceding siblings ...)
  2018-07-21 22:05       ` [PATCH v4 11/21] range-diff: add tests Thomas Rast via GitGitGadget
@ 2018-07-21 22:05       ` Johannes Schindelin via GitGitGadget
  2018-07-21 22:05       ` [PATCH v4 13/21] color: add the meta color GIT_COLOR_REVERSE Johannes Schindelin via GitGitGadget
                         ` (11 subsequent siblings)
  23 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:05 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

Arguably the most important part of `git range-diff`'s output is the
list of commits in the two branches, together with their relationships.

For that reason, tbdiff introduced color-coding that is pretty
intuitive, especially for unchanged patches (all dim yellow, like the
first line in `git show`'s output) vs modified patches (old commit is
red, new commit is green). Let's imitate that color scheme.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 51 ++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 38 insertions(+), 13 deletions(-)

diff --git a/range-diff.c b/range-diff.c
index 3fc3a4018..ab1e71e10 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -258,34 +258,53 @@ static void get_correspondences(struct string_list *a, struct string_list *b,
 	free(b2a);
 }
 
-static void output_pair_header(struct strbuf *buf,
+static void output_pair_header(struct diff_options *diffopt,
+			       struct strbuf *buf,
 			       struct strbuf *dashes,
 			       struct patch_util *a_util,
 			       struct patch_util *b_util)
 {
 	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
 	struct commit *commit;
+	char status;
+	const char *color_reset = diff_get_color_opt(diffopt, DIFF_RESET);
+	const char *color_old = diff_get_color_opt(diffopt, DIFF_FILE_OLD);
+	const char *color_new = diff_get_color_opt(diffopt, DIFF_FILE_NEW);
+	const char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT);
+	const char *color;
 
 	if (!dashes->len)
 		strbuf_addchars(dashes, '-',
 				strlen(find_unique_abbrev(oid,
 							  DEFAULT_ABBREV)));
 
+	if (!b_util) {
+		color = color_old;
+		status = '<';
+	} else if (!a_util) {
+		color = color_new;
+		status = '>';
+	} else if (strcmp(a_util->patch, b_util->patch)) {
+		color = color_commit;
+		status = '!';
+	} else {
+		color = color_commit;
+		status = '=';
+	}
+
 	strbuf_reset(buf);
+	strbuf_addstr(buf, status == '!' ? color_old : color);
 	if (!a_util)
 		strbuf_addf(buf, "-:  %s ", dashes->buf);
 	else
 		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
 			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
 
-	if (!a_util)
-		strbuf_addch(buf, '>');
-	else if (!b_util)
-		strbuf_addch(buf, '<');
-	else if (strcmp(a_util->patch, b_util->patch))
-		strbuf_addch(buf, '!');
-	else
-		strbuf_addch(buf, '=');
+	if (status == '!')
+		strbuf_addf(buf, "%s%s", color_reset, color);
+	strbuf_addch(buf, status);
+	if (status == '!')
+		strbuf_addf(buf, "%s%s", color_reset, color_new);
 
 	if (!b_util)
 		strbuf_addf(buf, " -:  %s", dashes->buf);
@@ -298,12 +317,15 @@ static void output_pair_header(struct strbuf *buf,
 		const char *commit_buffer = get_commit_buffer(commit, NULL);
 		const char *subject;
 
+		if (status == '!')
+			strbuf_addf(buf, "%s%s", color_reset, color);
+
 		find_commit_subject(commit_buffer, &subject);
 		strbuf_addch(buf, ' ');
 		format_subject(buf, subject, " ");
 		unuse_commit_buffer(commit, commit_buffer);
 	}
-	strbuf_addch(buf, '\n');
+	strbuf_addf(buf, "%s\n", color_reset);
 
 	fwrite(buf->buf, buf->len, 1, stdout);
 }
@@ -361,21 +383,24 @@ static void output(struct string_list *a, struct string_list *b,
 
 		/* Show unmatched LHS commit whose predecessors were shown. */
 		if (i < a->nr && a_util->matching < 0) {
-			output_pair_header(&buf, &dashes, a_util, NULL);
+			output_pair_header(diffopt,
+					   &buf, &dashes, a_util, NULL);
 			i++;
 			continue;
 		}
 
 		/* Show unmatched RHS commits. */
 		while (j < b->nr && b_util->matching < 0) {
-			output_pair_header(&buf, &dashes, NULL, b_util);
+			output_pair_header(diffopt,
+					   &buf, &dashes, NULL, b_util);
 			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
 
 		/* Show matching LHS/RHS pair. */
 		if (j < b->nr) {
 			a_util = a->items[b_util->matching].util;
-			output_pair_header(&buf, &dashes, a_util, b_util);
+			output_pair_header(diffopt,
+					   &buf, &dashes, a_util, b_util);
 			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
 				patch_diff(a->items[b_util->matching].string,
 					   b->items[j].string, diffopt);
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 13/21] color: add the meta color GIT_COLOR_REVERSE
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (11 preceding siblings ...)
  2018-07-21 22:05       ` [PATCH v4 12/21] range-diff: use color for the commit pairs Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:05       ` Johannes Schindelin via GitGitGadget
  2018-07-21 22:05       ` [PATCH v4 14/21] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin via GitGitGadget
                         ` (10 subsequent siblings)
  23 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:05 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

This "color" simply reverts background and foreground. It will be used
in the upcoming "dual color" mode of `git range-diff`, where we will
reverse colors for the -/+ markers and the fragment headers of the
"outer" diff.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 color.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/color.h b/color.h
index 5b744e1bc..33e786342 100644
--- a/color.h
+++ b/color.h
@@ -44,6 +44,7 @@ struct strbuf;
 #define GIT_COLOR_BG_CYAN	"\033[46m"
 #define GIT_COLOR_FAINT		"\033[2m"
 #define GIT_COLOR_FAINT_ITALIC	"\033[2;3m"
+#define GIT_COLOR_REVERSE	"\033[7m"
 
 /* A special value meaning "no color selected" */
 #define GIT_COLOR_NIL "NIL"
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 14/21] diff: add an internal option to dual-color diffs of diffs
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (12 preceding siblings ...)
  2018-07-21 22:05       ` [PATCH v4 13/21] color: add the meta color GIT_COLOR_REVERSE Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:05       ` Johannes Schindelin via GitGitGadget
  2018-07-23 22:27         ` Junio C Hamano
  2018-07-21 22:05       ` [PATCH v4 15/21] range-diff: offer to dual-color the diffs Johannes Schindelin via GitGitGadget
                         ` (9 subsequent siblings)
  23 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:05 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When diffing diffs, it can be quite daunting to figure out what the heck
is going on, as there are nested +/- signs.

Let's make this easier by adding a flag in diff_options that allows
color-coding the outer diff sign with inverted colors, so that the
preimage and postimage is colored like the diff it is.

Of course, this really only makes sense when the preimage and postimage
*are* diffs. So let's not expose this flag via a command-line option for
now.

This is a feature that was invented by git-tbdiff, and it will be used
by `git range-diff` in the next commit, by offering it via a new option:
`--dual-color`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 diff.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++-----------
 diff.h |  1 +
 2 files changed, 69 insertions(+), 15 deletions(-)

diff --git a/diff.c b/diff.c
index a94a8214f..e163bc8a3 100644
--- a/diff.c
+++ b/diff.c
@@ -563,14 +563,18 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,
 	ecbdata->blank_at_eof_in_postimage = (at - l2) + 1;
 }
 
-static void emit_line_0(struct diff_options *o, const char *set, const char *reset,
+static void emit_line_0(struct diff_options *o,
+			const char *set, unsigned reverse, const char *reset,
 			int first, const char *line, int len)
 {
 	int has_trailing_newline, has_trailing_carriage_return;
 	int nofirst;
 	FILE *file = o->file;
 
-	fputs(diff_line_prefix(o), file);
+	if (first)
+		fputs(diff_line_prefix(o), file);
+	else if (!len)
+		return;
 
 	if (len == 0) {
 		has_trailing_newline = (first == '\n');
@@ -588,8 +592,10 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res
 	}
 
 	if (len || !nofirst) {
+		if (reverse && want_color(o->use_color))
+			fputs(GIT_COLOR_REVERSE, file);
 		fputs(set, file);
-		if (!nofirst)
+		if (first && !nofirst)
 			fputc(first, file);
 		fwrite(line, len, 1, file);
 		fputs(reset, file);
@@ -603,7 +609,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res
 static void emit_line(struct diff_options *o, const char *set, const char *reset,
 		      const char *line, int len)
 {
-	emit_line_0(o, set, reset, line[0], line+1, len-1);
+	emit_line_0(o, set, 0, reset, line[0], line+1, len-1);
 }
 
 enum diff_symbol {
@@ -963,7 +969,8 @@ static void dim_moved_lines(struct diff_options *o)
 
 static void emit_line_ws_markup(struct diff_options *o,
 				const char *set, const char *reset,
-				const char *line, int len, char sign,
+				const char *line, int len,
+				const char *set_sign, char sign,
 				unsigned ws_rule, int blank_at_eof)
 {
 	const char *ws = NULL;
@@ -974,14 +981,20 @@ static void emit_line_ws_markup(struct diff_options *o,
 			ws = NULL;
 	}
 
-	if (!ws)
-		emit_line_0(o, set, reset, sign, line, len);
-	else if (blank_at_eof)
+	if (!ws && !set_sign)
+		emit_line_0(o, set, 0, reset, sign, line, len);
+	else if (!ws) {
+		/* Emit just the prefix, then the rest. */
+		emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
+			    sign, "", 0);
+		emit_line_0(o, set, 0, reset, 0, line, len);
+	} else if (blank_at_eof)
 		/* Blank line at EOF - paint '+' as well */
-		emit_line_0(o, ws, reset, sign, line, len);
+		emit_line_0(o, ws, 0, reset, sign, line, len);
 	else {
 		/* Emit just the prefix, then the rest. */
-		emit_line_0(o, set, reset, sign, "", 0);
+		emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
+			    sign, "", 0);
 		ws_check_emit(line, len, ws_rule,
 			      o->file, set, reset, ws);
 	}
@@ -991,7 +1004,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 					 struct emitted_diff_symbol *eds)
 {
 	static const char *nneof = " No newline at end of file\n";
-	const char *context, *reset, *set, *meta, *fraginfo;
+	const char *context, *reset, *set, *set_sign, *meta, *fraginfo;
 	struct strbuf sb = STRBUF_INIT;
 
 	enum diff_symbol s = eds->s;
@@ -1004,7 +1017,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 		context = diff_get_color_opt(o, DIFF_CONTEXT);
 		reset = diff_get_color_opt(o, DIFF_RESET);
 		putc('\n', o->file);
-		emit_line_0(o, context, reset, '\\',
+		emit_line_0(o, context, 0, reset, '\\',
 			    nneof, strlen(nneof));
 		break;
 	case DIFF_SYMBOL_SUBMODULE_HEADER:
@@ -1031,7 +1044,18 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 	case DIFF_SYMBOL_CONTEXT:
 		set = diff_get_color_opt(o, DIFF_CONTEXT);
 		reset = diff_get_color_opt(o, DIFF_RESET);
-		emit_line_ws_markup(o, set, reset, line, len, ' ',
+		set_sign = NULL;
+		if (o->flags.dual_color_diffed_diffs) {
+			char c = !len ? 0 : line[0];
+
+			if (c == '+')
+				set = diff_get_color_opt(o, DIFF_FILE_NEW);
+			else if (c == '@')
+				set = diff_get_color_opt(o, DIFF_FRAGINFO);
+			else if (c == '-')
+				set = diff_get_color_opt(o, DIFF_FILE_OLD);
+		}
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',
 				    flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);
 		break;
 	case DIFF_SYMBOL_PLUS:
@@ -1058,7 +1082,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 			set = diff_get_color_opt(o, DIFF_FILE_NEW);
 		}
 		reset = diff_get_color_opt(o, DIFF_RESET);
-		emit_line_ws_markup(o, set, reset, line, len, '+',
+		if (!o->flags.dual_color_diffed_diffs)
+			set_sign = NULL;
+		else {
+			char c = !len ? 0 : line[0];
+
+			set_sign = set;
+			if (c == '-')
+				set = diff_get_color_opt(o, DIFF_FILE_OLD);
+			else if (c == '@')
+				set = diff_get_color_opt(o, DIFF_FRAGINFO);
+			else if (c != '+')
+				set = diff_get_color_opt(o, DIFF_CONTEXT);
+		}
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK,
 				    flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);
 		break;
@@ -1086,7 +1123,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 			set = diff_get_color_opt(o, DIFF_FILE_OLD);
 		}
 		reset = diff_get_color_opt(o, DIFF_RESET);
-		emit_line_ws_markup(o, set, reset, line, len, '-',
+		if (!o->flags.dual_color_diffed_diffs)
+			set_sign = NULL;
+		else {
+			char c = !len ? 0 : line[0];
+
+			set_sign = set;
+			if (c == '+')
+				set = diff_get_color_opt(o, DIFF_FILE_NEW);
+			else if (c == '@')
+				set = diff_get_color_opt(o, DIFF_FRAGINFO);
+			else if (c != '-')
+				set = diff_get_color_opt(o, DIFF_CONTEXT);
+		}
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, '-',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);
 		break;
 	case DIFF_SYMBOL_WORDS_PORCELAIN:
@@ -1277,6 +1327,7 @@ static void emit_hunk_header(struct emit_callback *ecbdata,
 	const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
 	const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);
 	const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
+	const char *reverse = ecbdata->color_diff ? GIT_COLOR_REVERSE : "";
 	static const char atat[2] = { '@', '@' };
 	const char *cp, *ep;
 	struct strbuf msgbuf = STRBUF_INIT;
@@ -1297,6 +1348,8 @@ static void emit_hunk_header(struct emit_callback *ecbdata,
 	ep += 2; /* skip over @@ */
 
 	/* The hunk header in fraginfo color */
+	if (ecbdata->opt->flags.dual_color_diffed_diffs)
+		strbuf_addstr(&msgbuf, reverse);
 	strbuf_addstr(&msgbuf, frag);
 	strbuf_add(&msgbuf, line, ep - line);
 	strbuf_addstr(&msgbuf, reset);
diff --git a/diff.h b/diff.h
index 928f48995..79beb6eea 100644
--- a/diff.h
+++ b/diff.h
@@ -95,6 +95,7 @@ struct diff_flags {
 	unsigned default_follow_renames:1;
 	unsigned stat_with_summary:1;
 	unsigned suppress_diff_headers:1;
+	unsigned dual_color_diffed_diffs:1;
 };
 
 static inline void diff_flags_or(struct diff_flags *a,
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 15/21] range-diff: offer to dual-color the diffs
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (13 preceding siblings ...)
  2018-07-21 22:05       ` [PATCH v4 14/21] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:05       ` Johannes Schindelin via GitGitGadget
  2018-07-21 22:05       ` [PATCH v4 16/21] range-diff --dual-color: fix bogus white-space warning Johannes Schindelin via GitGitGadget
                         ` (8 subsequent siblings)
  23 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:05 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When showing what changed between old and new commits, we show a diff of
the patches. This diff is a diff between diffs, therefore there are
nested +/- signs, and it can be relatively hard to understand what is
going on.

With the --dual-color option, the preimage and the postimage are colored
like the diffs they are, and the *outer* +/- sign is inverted for
clarity.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/range-diff.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 10065315d..c25d88317 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -20,9 +20,12 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 {
 	int creation_factor = 60;
 	struct diff_options diffopt = { NULL };
+	int dual_color = 0;
 	struct option options[] = {
 		OPT_INTEGER(0, "creation-factor", &creation_factor,
 			    N_("Percentage by which creation is weighted")),
+		OPT_BOOL(0, "dual-color", &dual_color,
+			    N_("color both diff and diff-between-diffs")),
 		OPT_END()
 	};
 	int i, j, res = 0;
@@ -52,6 +55,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 	argc = j;
 	diff_setup_done(&diffopt);
 
+	if (dual_color) {
+		diffopt.use_color = 1;
+		diffopt.flags.dual_color_diffed_diffs = 1;
+	}
+
 	if (argc == 2) {
 		if (!strstr(argv[0], ".."))
 			die(_("no .. in range: '%s'"), argv[0]);
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 16/21] range-diff --dual-color: fix bogus white-space warning
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (14 preceding siblings ...)
  2018-07-21 22:05       ` [PATCH v4 15/21] range-diff: offer to dual-color the diffs Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:05       ` Johannes Schindelin via GitGitGadget
  2018-07-23 22:20         ` Stefan Beller
  2018-07-23 22:39         ` Junio C Hamano
  2018-07-21 22:05       ` [PATCH v4 17/21] range-diff: populate the man page Johannes Schindelin via GitGitGadget
                         ` (7 subsequent siblings)
  23 siblings, 2 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:05 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When displaying a diff of diffs, it is possible that there is an outer
`+` before a context line. That happens when the context changed between
old and new commit. When that context line starts with a tab (after the
space that marks it as context line), our diff machinery spits out a
white-space error (space before tab), but in this case, that is
incorrect.

Fix this by adding a specific whitespace flag that simply ignores the
first space in the output.

Of course, this flag is *really* specific to the "diff of diffs" use
case. The original idea was to simply skip the space from the output,
but that workaround was rejected by the Git maintainer as causing
headaches.

Note: as the original code did not leave any space in the bit mask
before the WSEH_* bits, the diff of this commit looks unnecessarily
involved: the diff is dominated by making room for one more bit to be
used by the whitespace rules.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 cache.h |  3 ++-
 diff.c  | 15 ++++++++-------
 diff.h  |  6 +++---
 ws.c    | 11 ++++++++++-
 4 files changed, 23 insertions(+), 12 deletions(-)

diff --git a/cache.h b/cache.h
index 8b447652a..8abfbeb73 100644
--- a/cache.h
+++ b/cache.h
@@ -1681,11 +1681,12 @@ void shift_tree_by(const struct object_id *, const struct object_id *, struct ob
 #define WS_CR_AT_EOL           01000
 #define WS_BLANK_AT_EOF        02000
 #define WS_TAB_IN_INDENT       04000
+#define WS_IGNORE_FIRST_SPACE 010000
 #define WS_TRAILING_SPACE      (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF)
 #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|8)
 #define WS_TAB_WIDTH_MASK        077
 /* All WS_* -- when extended, adapt diff.c emit_symbol */
-#define WS_RULE_MASK           07777
+#define WS_RULE_MASK           017777
 extern unsigned whitespace_rule_cfg;
 extern unsigned whitespace_rule(const char *);
 extern unsigned parse_whitespace_rule(const char *);
diff --git a/diff.c b/diff.c
index e163bc8a3..03ed235c7 100644
--- a/diff.c
+++ b/diff.c
@@ -650,14 +650,14 @@ enum diff_symbol {
 };
 /*
  * Flags for content lines:
- * 0..12 are whitespace rules
- * 13-15 are WSEH_NEW | WSEH_OLD | WSEH_CONTEXT
- * 16 is marking if the line is blank at EOF
+ * 0..14 are whitespace rules
+ * 14-16 are WSEH_NEW | WSEH_OLD | WSEH_CONTEXT
+ * 17 is marking if the line is blank at EOF
  */
-#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF	(1<<16)
-#define DIFF_SYMBOL_MOVED_LINE			(1<<17)
-#define DIFF_SYMBOL_MOVED_LINE_ALT		(1<<18)
-#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING	(1<<19)
+#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF	(1<<17)
+#define DIFF_SYMBOL_MOVED_LINE			(1<<18)
+#define DIFF_SYMBOL_MOVED_LINE_ALT		(1<<19)
+#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING	(1<<20)
 #define DIFF_SYMBOL_CONTENT_WS_MASK (WSEH_NEW | WSEH_OLD | WSEH_CONTEXT | WS_RULE_MASK)
 
 /*
@@ -1094,6 +1094,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 				set = diff_get_color_opt(o, DIFF_FRAGINFO);
 			else if (c != '+')
 				set = diff_get_color_opt(o, DIFF_CONTEXT);
+			flags |= WS_IGNORE_FIRST_SPACE;
 		}
 		emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK,
diff --git a/diff.h b/diff.h
index 79beb6eea..892416a14 100644
--- a/diff.h
+++ b/diff.h
@@ -160,9 +160,9 @@ struct diff_options {
 	int abbrev;
 	int ita_invisible_in_index;
 /* white-space error highlighting */
-#define WSEH_NEW (1<<12)
-#define WSEH_CONTEXT (1<<13)
-#define WSEH_OLD (1<<14)
+#define WSEH_NEW (1<<13)
+#define WSEH_CONTEXT (1<<14)
+#define WSEH_OLD (1<<15)
 	unsigned ws_error_highlight;
 	const char *prefix;
 	int prefix_length;
diff --git a/ws.c b/ws.c
index a07caedd5..e02365a6a 100644
--- a/ws.c
+++ b/ws.c
@@ -20,6 +20,7 @@ static struct whitespace_rule {
 	{ "blank-at-eol", WS_BLANK_AT_EOL, 0 },
 	{ "blank-at-eof", WS_BLANK_AT_EOF, 0 },
 	{ "tab-in-indent", WS_TAB_IN_INDENT, 0, 1 },
+	{ "ignore-first-space", WS_IGNORE_FIRST_SPACE, 0, 1 },
 };
 
 unsigned parse_whitespace_rule(const char *string)
@@ -177,8 +178,16 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,
 	if (trailing_whitespace == -1)
 		trailing_whitespace = len;
 
+	if ((ws_rule & WS_IGNORE_FIRST_SPACE) && len && line[0] == ' ') {
+		if (stream)
+			fwrite(line, 1, 1, stream);
+		written++;
+		if (!trailing_whitespace)
+			trailing_whitespace++;
+	}
+
 	/* Check indentation */
-	for (i = 0; i < trailing_whitespace; i++) {
+	for (i = written; i < trailing_whitespace; i++) {
 		if (line[i] == ' ')
 			continue;
 		if (line[i] != '\t')
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 17/21] range-diff: populate the man page
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (15 preceding siblings ...)
  2018-07-21 22:05       ` [PATCH v4 16/21] range-diff --dual-color: fix bogus white-space warning Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:05       ` Johannes Schindelin via GitGitGadget
  2018-07-29 21:23         ` Thomas Gummerer
  2018-07-21 22:05       ` [PATCH v4 18/21] completion: support `git range-diff` Johannes Schindelin via GitGitGadget
                         ` (6 subsequent siblings)
  23 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:05 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

The bulk of this patch consists of a heavily butchered version of
tbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from
https://github.com/trast/tbdiff.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/git-range-diff.txt | 229 +++++++++++++++++++++++++++++++
 1 file changed, 229 insertions(+)

diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
index de0ca5df4..f1a6737f8 100644
--- a/Documentation/git-range-diff.txt
+++ b/Documentation/git-range-diff.txt
@@ -5,6 +5,235 @@ NAME
 ----
 git-range-diff - Compare two commit ranges (e.g. two versions of a branch)
 
+SYNOPSIS
+--------
+[verse]
+'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]
+	[--dual-color] [--creation-factor=<factor>]
+	( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
+
+DESCRIPTION
+-----------
+
+This command shows the differences between two versions of a patch
+series, or more generally, two commit ranges (ignoring merge commits).
+
+To that end, it first finds pairs of commits from both commit ranges
+that correspond with each other. Two commits are said to correspond when
+the diff between their patches (i.e. the author information, the commit
+message and the commit diff) is reasonably small compared to the
+patches' size. See ``Algorithm`` below for details.
+
+Finally, the list of matching commits is shown in the order of the
+second commit range, with unmatched commits being inserted just after
+all of their ancestors have been shown.
+
+
+OPTIONS
+-------
+--dual-color::
+	When the commit diffs differ, recreate the original diffs'
+	coloring, and add outer -/+ diff markers with the *background*
+	being red/green to make it easier to see e.g. when there was a
+	change in what exact lines were added.
+
+--creation-factor=<percent>::
+	Set the creation/deletion cost fudge factor to `<percent>`.
+	Defaults to 60. Try a larger value if `git range-diff` erroneously
+	considers a large change a total rewrite (deletion of one commit
+	and addition of another), and a smaller one in the reverse case.
+	See the ``Algorithm`` section below for an explanation why this is
+	needed.
+
+<range1> <range2>::
+	Compare the commits specified by the two ranges, where
+	`<range1>` is considered an older version of `<range2>`.
+
+<rev1>...<rev2>::
+	Equivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.
+
+<base> <rev1> <rev2>::
+	Equivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.
+	Note that `<base>` does not need to be the exact branch point
+	of the branches. Example: after rebasing a branch `my-topic`,
+	`git range-diff my-topic@{u} my-topic@{1} my-topic` would
+	show the differences introduced by the rebase.
+
+`git range-diff` also accepts the regular diff options (see
+linkgit:git-diff[1]), most notably the `--color=[<when>]` and
+`--no-color` options. These options are used when generating the "diff
+between patches", i.e. to compare the author, commit message and diff of
+corresponding old/new commits. There is currently no means to tweak the
+diff options passed to `git log` when generating those patches.
+
+
+CONFIGURATION
+-------------
+This command uses the `diff.color.*` and `pager.range-diff` settings
+(the latter is on by default).
+See linkgit:git-config[1].
+
+
+EXAMPLES
+--------
+
+When a rebase required merge conflicts to be resolved, compare the changes
+introduced by the rebase directly afterwards using:
+
+------------
+$ git range-diff @{u} @{1} @
+------------
+
+
+A typical output of `git range-diff` would look like this:
+
+------------
+-:  ------- > 1:  0ddba11 Prepare for the inevitable!
+1:  c0debee = 2:  cab005e Add a helpful message at the start
+2:  f00dbal ! 3:  decafe1 Describe a bug
+    @@ -1,3 +1,3 @@
+     Author: A U Thor <author@example.com>
+
+    -TODO: Describe a bug
+    +Describe a bug
+    @@ -324,5 +324,6
+      This is expected.
+
+    -+What is unexpected is that it will also crash.
+    ++Unexpectedly, it also crashes. This is a bug, and the jury is
+    ++still out there how to fix it best. See ticket #314 for details.
+
+      Contact
+3:  bedead < -:  ------- TO-UNDO
+------------
+
+In this example, there are 3 old and 3 new commits, where the developer
+removed the 3rd, added a new one before the first two, and modified the
+commit message of the 2nd commit as well its diff.
+
+When the output goes to a terminal, it is color-coded by default, just
+like regular `git diff`'s output. In addition, the first line (adding a
+commit) is green, the last line (deleting a commit) is red, the second
+line (with a perfect match) is yellow like the commit header of `git
+show`'s output, and the third line colors the old commit red, the new
+one green and the rest like `git show`'s commit header.
+
+The color-coded diff is actually a bit hard to read, though, as it
+colors the entire lines red or green. The line that added "What is
+unexpected" in the old commit, for example, is completely red, even if
+the intent of the old commit was to add something.
+
+To help with that, use the `--dual-color` mode. In this mode, the diff
+of diffs will retain the original diff colors, and prefix the lines with
+-/+ markers that have their *background* red or green, to make it more
+obvious that they describe how the diff itself changed.
+
+
+Algorithm
+---------
+
+The general idea is this: we generate a cost matrix between the commits
+in both commit ranges, then solve the least-cost assignment.
+
+The cost matrix is populated thusly: for each pair of commits, both
+diffs are generated and the "diff of diffs" is generated, with 3 context
+lines, then the number of lines in that diff is used as cost.
+
+To avoid false positives (e.g. when a patch has been removed, and an
+unrelated patch has been added between two iterations of the same patch
+series), the cost matrix is extended to allow for that, by adding
+fixed-cost entries for wholesale deletes/adds.
+
+Example: Let commits `1--2` be the first iteration of a patch series and
+`A--C` the second iteration. Let's assume that `A` is a cherry-pick of
+`2,` and `C` is a cherry-pick of `1` but with a small modification (say,
+a fixed typo). Visualize the commits as a bipartite graph:
+
+------------
+    1            A
+
+    2            B
+
+		 C
+------------
+
+We are looking for a "best" explanation of the new series in terms of
+the old one. We can represent an "explanation" as an edge in the graph:
+
+
+------------
+    1            A
+	       /
+    2 --------'  B
+
+		 C
+------------
+
+This explanation comes for "free" because there was no change. Similarly
+`C` could be explained using `1`, but that comes at some cost c>0
+because of the modification:
+
+------------
+    1 ----.      A
+	  |    /
+    2 ----+---'  B
+	  |
+	  `----- C
+	  c>0
+------------
+
+In mathematical terms, what we are looking for is some sort of a minimum
+cost bipartite matching; `1` is matched to `C` at some cost, etc. The
+underlying graph is in fact a complete bipartite graph; the cost we
+associate with every edge is the size of the diff between the two
+commits' patches. To explain also new commits, we introduce dummy nodes
+on both sides:
+
+------------
+    1 ----.      A
+	  |    /
+    2 ----+---'  B
+	  |
+    o     `----- C
+	  c>0
+    o            o
+
+    o            o
+------------
+
+The cost of an edge `o--C` is the size of `C`'s diff, modified by a
+fudge factor that should be smaller than 100%. The cost of an edge
+`o--o` is free. The fudge factor is necessary because even if `1` and
+`C` have nothing in common, they may still share a few empty lines and
+such, possibly making the assignment `1--C`, `o--o` slightly cheaper
+than `1--o`, `o--C` even if `1` and `C` have nothing in common. With the
+fudge factor we require a much larger common part to consider patches as
+corresponding.
+
+The overall time needed to compute this algorithm is the time needed to
+compute n+m commit diffs and then n*m diffs of patches, plus the time
+needed to compute the least-cost assigment between n and m diffs. Git
+uses an implementation of the Jonker-Volgenant algorithm to solve the
+assignment problem, which has cubic runtime complexity. The matching
+found in this case will look like this:
+
+------------
+    1 ----.      A
+	  |    /
+    2 ----+---'  B
+       .--+-----'
+    o -'  `----- C
+	  c>0
+    o ---------- o
+
+    o ---------- o
+------------
+
+
+SEE ALSO
+--------
+linkgit:git-log[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 18/21] completion: support `git range-diff`
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (16 preceding siblings ...)
  2018-07-21 22:05       ` [PATCH v4 17/21] range-diff: populate the man page Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:05       ` Johannes Schindelin via GitGitGadget
  2018-07-22  5:49         ` Eric Sunshine
  2018-07-21 22:05       ` [PATCH v4 19/21] range-diff: left-pad patch numbers Johannes Schindelin via GitGitGadget
                         ` (5 subsequent siblings)
  23 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:05 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

Tab completion of `git range-diff` is very convenient, especially
given that the revision arguments to specify the commit ranges to
compare are typically more complex than, say, what is normally passed
to `git log`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 contrib/completion/git-completion.bash | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 94c95516e..402490673 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1976,6 +1976,20 @@ _git_push ()
 	__git_complete_remote_or_refspec
 }
 
+_git_range_diff ()
+{
+  case "$cur" in
+  --*)
+          __gitcomp "
+	  	--creation-factor= --dual-color
+                  $__git_diff_common_options
+                  "
+          return
+          ;;
+  esac
+  __git_complete_revlist
+}
+
 _git_rebase ()
 {
 	__git_find_repo_path
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 19/21] range-diff: left-pad patch numbers
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (17 preceding siblings ...)
  2018-07-21 22:05       ` [PATCH v4 18/21] completion: support `git range-diff` Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:05       ` Johannes Schindelin via GitGitGadget
  2018-07-21 22:05       ` [PATCH v4 20/21] range-diff: make --dual-color the default mode Johannes Schindelin via GitGitGadget
                         ` (4 subsequent siblings)
  23 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:05 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

As pointed out by Elijah Newren, tbdiff has this neat little alignment
trick where it outputs the commit pairs with patch numbers that are
padded to the maximal patch number's width:

	  1: cafedead =   1: acefade first patch
	[...]
	314: beefeada < 314: facecab up to PI!

Let's do the same in range-diff, too.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/range-diff.c b/range-diff.c
index ab1e71e10..347b4a79f 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -259,6 +259,7 @@ static void get_correspondences(struct string_list *a, struct string_list *b,
 }
 
 static void output_pair_header(struct diff_options *diffopt,
+			       int patch_no_width,
 			       struct strbuf *buf,
 			       struct strbuf *dashes,
 			       struct patch_util *a_util,
@@ -295,9 +296,9 @@ static void output_pair_header(struct diff_options *diffopt,
 	strbuf_reset(buf);
 	strbuf_addstr(buf, status == '!' ? color_old : color);
 	if (!a_util)
-		strbuf_addf(buf, "-:  %s ", dashes->buf);
+		strbuf_addf(buf, "%*s:  %s ", patch_no_width, "-", dashes->buf);
 	else
-		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
+		strbuf_addf(buf, "%*d:  %s ", patch_no_width, a_util->i + 1,
 			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
 
 	if (status == '!')
@@ -307,9 +308,9 @@ static void output_pair_header(struct diff_options *diffopt,
 		strbuf_addf(buf, "%s%s", color_reset, color_new);
 
 	if (!b_util)
-		strbuf_addf(buf, " -:  %s", dashes->buf);
+		strbuf_addf(buf, " %*s:  %s", patch_no_width, "-", dashes->buf);
 	else
-		strbuf_addf(buf, " %d:  %s", b_util->i + 1,
+		strbuf_addf(buf, " %*d:  %s", patch_no_width, b_util->i + 1,
 			    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));
 
 	commit = lookup_commit_reference(oid);
@@ -362,6 +363,7 @@ static void output(struct string_list *a, struct string_list *b,
 		   struct diff_options *diffopt)
 {
 	struct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;
+	int patch_no_width = decimal_width(1 + (a->nr > b->nr ? a->nr : b->nr));
 	int i = 0, j = 0;
 
 	/*
@@ -383,7 +385,7 @@ static void output(struct string_list *a, struct string_list *b,
 
 		/* Show unmatched LHS commit whose predecessors were shown. */
 		if (i < a->nr && a_util->matching < 0) {
-			output_pair_header(diffopt,
+			output_pair_header(diffopt, patch_no_width,
 					   &buf, &dashes, a_util, NULL);
 			i++;
 			continue;
@@ -391,7 +393,7 @@ static void output(struct string_list *a, struct string_list *b,
 
 		/* Show unmatched RHS commits. */
 		while (j < b->nr && b_util->matching < 0) {
-			output_pair_header(diffopt,
+			output_pair_header(diffopt, patch_no_width,
 					   &buf, &dashes, NULL, b_util);
 			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
@@ -399,7 +401,7 @@ static void output(struct string_list *a, struct string_list *b,
 		/* Show matching LHS/RHS pair. */
 		if (j < b->nr) {
 			a_util = a->items[b_util->matching].util;
-			output_pair_header(diffopt,
+			output_pair_header(diffopt, patch_no_width,
 					   &buf, &dashes, a_util, b_util);
 			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
 				patch_diff(a->items[b_util->matching].string,
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 20/21] range-diff: make --dual-color the default mode
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (18 preceding siblings ...)
  2018-07-21 22:05       ` [PATCH v4 19/21] range-diff: left-pad patch numbers Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:05       ` Johannes Schindelin via GitGitGadget
  2018-07-29 21:33         ` Thomas Gummerer
  2018-07-21 22:05       ` [PATCH v4 21/21] range-diff: use dim/bold cues to improve dual color mode Johannes Schindelin via GitGitGadget
                         ` (3 subsequent siblings)
  23 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:05 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

After using this command extensively for the last two months, this
developer came to the conclusion that even if the dual color mode still
leaves a lot of room for confusion about what was actually changed, the
non-dual color mode is substantially worse in that regard.

Therefore, we really want to make the dual color mode the default.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/git-range-diff.txt       | 32 +++++++++++++++-----------
 builtin/range-diff.c                   | 10 ++++----
 contrib/completion/git-completion.bash |  2 +-
 3 files changed, 25 insertions(+), 19 deletions(-)

diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
index f1a6737f8..e3c0be559 100644
--- a/Documentation/git-range-diff.txt
+++ b/Documentation/git-range-diff.txt
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]
-	[--dual-color] [--creation-factor=<factor>]
+	[--no-dual-color] [--creation-factor=<factor>]
 	( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
 
 DESCRIPTION
@@ -31,11 +31,14 @@ all of their ancestors have been shown.
 
 OPTIONS
 -------
---dual-color::
-	When the commit diffs differ, recreate the original diffs'
-	coloring, and add outer -/+ diff markers with the *background*
-	being red/green to make it easier to see e.g. when there was a
-	change in what exact lines were added.
+--no-dual-color::
+	When the commit diffs differ, `git range-diff` recreates the
+	original diffs' coloring, and adds outer -/+ diff markers with
+	the *background* being red/green to make it easier to see e.g.
+	when there was a change in what exact lines were added. This is
+	known to `range-diff` as "dual coloring". Use `--no-dual-color`
+	to revert to color all lines according to the outer diff markers
+	(and completely ignore the inner diff when it comes to color).
 
 --creation-factor=<percent>::
 	Set the creation/deletion cost fudge factor to `<percent>`.
@@ -118,15 +121,16 @@ line (with a perfect match) is yellow like the commit header of `git
 show`'s output, and the third line colors the old commit red, the new
 one green and the rest like `git show`'s commit header.
 
-The color-coded diff is actually a bit hard to read, though, as it
-colors the entire lines red or green. The line that added "What is
-unexpected" in the old commit, for example, is completely red, even if
-the intent of the old commit was to add something.
+A naive color-coded diff of diffs is actually a bit hard to read,
+though, as it colors the entire lines red or green. The line that added
+"What is unexpected" in the old commit, for example, is completely red,
+even if the intent of the old commit was to add something.
 
-To help with that, use the `--dual-color` mode. In this mode, the diff
-of diffs will retain the original diff colors, and prefix the lines with
--/+ markers that have their *background* red or green, to make it more
-obvious that they describe how the diff itself changed.
+To help with that, `range` uses the `--dual-color` mode by default. In
+this mode, the diff of diffs will retain the original diff colors, and
+prefix the lines with -/+ markers that have their *background* red or
+green, to make it more obvious that they describe how the diff itself
+changed.
 
 
 Algorithm
diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index c25d88317..77ac3bff7 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -20,11 +20,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 {
 	int creation_factor = 60;
 	struct diff_options diffopt = { NULL };
-	int dual_color = 0;
+	int simple_color = -1;
 	struct option options[] = {
 		OPT_INTEGER(0, "creation-factor", &creation_factor,
 			    N_("Percentage by which creation is weighted")),
-		OPT_BOOL(0, "dual-color", &dual_color,
+		OPT_BOOL(0, "no-dual-color", &simple_color,
 			    N_("color both diff and diff-between-diffs")),
 		OPT_END()
 	};
@@ -55,8 +55,10 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 	argc = j;
 	diff_setup_done(&diffopt);
 
-	if (dual_color) {
-		diffopt.use_color = 1;
+	if (simple_color < 1) {
+		if (!simple_color)
+			/* force color when --dual-color was used */
+			diffopt.use_color = 1;
 		diffopt.flags.dual_color_diffed_diffs = 1;
 	}
 
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 402490673..e35fc28fc 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1981,7 +1981,7 @@ _git_range_diff ()
   case "$cur" in
   --*)
           __gitcomp "
-	  	--creation-factor= --dual-color
+	  	--creation-factor= --no-dual-color
                   $__git_diff_common_options
                   "
           return
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v4 21/21] range-diff: use dim/bold cues to improve dual color mode
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (19 preceding siblings ...)
  2018-07-21 22:05       ` [PATCH v4 20/21] range-diff: make --dual-color the default mode Johannes Schindelin via GitGitGadget
@ 2018-07-21 22:05       ` Johannes Schindelin via GitGitGadget
  2018-07-23 21:03       ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Stefan Beller
                         ` (2 subsequent siblings)
  23 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-07-21 22:05 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

It *is* a confusing thing to look at a diff of diffs. All too easy is it
to mix up whether the -/+ markers refer to the "inner" or the "outer"
diff, i.e. whether a `+` indicates that a line was added by either the
old or the new diff (or both), or whether the new diff does something
different than the old diff.

To make things easier to process for normal developers, we introduced
the dual color mode which colors the lines according to the commit diff,
i.e. lines that are added by a commit (whether old, new, or both) are
colored in green. In non-dual color mode, the lines would be colored
according to the outer diff: if the old commit added a line, it would be
colored red (because that line addition is only present in the first
commit range that was specified on the command-line, i.e. the "old"
commit, but not in the second commit range, i.e. the "new" commit).

However, this dual color mode is still not making things clear enough,
as we are looking at two levels of diffs, and we still only pick a color
according to *one* of them (the outer diff marker is colored
differently, of course, but in particular with deep indentation, it is
easy to lose track of that outer diff marker's background color).

Therefore, let's add another dimension to the mix. Still use
green/red/normal according to the commit diffs, but now also dim the
lines that were only in the old commit, and use bold face for the lines
that are only in the new commit.

That way, it is much easier not to lose track of, say, when we are
looking at a line that was added in the previous iteration of a patch
series but the new iteration adds a slightly different version: the
obsolete change will be dimmed, the current version of the patch will be
bold.

At least this developer has a much easier time reading the range-diffs
that way.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/config.txt         |  6 ++++--
 Documentation/git-range-diff.txt | 17 +++++++++++++----
 color.h                          |  6 ++++++
 diff.c                           | 28 ++++++++++++++++++++++------
 diff.h                           |  8 +++++++-
 5 files changed, 52 insertions(+), 13 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index a32172a43..6dbfc9a09 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1159,8 +1159,10 @@ color.diff.<slot>::
 	(highlighting whitespace errors), `oldMoved` (deleted lines),
 	`newMoved` (added lines), `oldMovedDimmed`, `oldMovedAlternative`,
 	`oldMovedAlternativeDimmed`, `newMovedDimmed`, `newMovedAlternative`
-	and `newMovedAlternativeDimmed` (See the '<mode>'
-	setting of '--color-moved' in linkgit:git-diff[1] for details).
+	`newMovedAlternativeDimmed` (See the '<mode>'
+	setting of '--color-moved' in linkgit:git-diff[1] for details),
+	`contextDimmed`, `oldDimmed`, `newDimmed`, `contextBold`,
+	`oldBold`, and `newBold` (see linkgit:git-range-diff[1] for details).
 
 color.decorate.<slot>::
 	Use customized color for 'git log --decorate' output.  `<slot>` is one
diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
index e3c0be559..0027f35a2 100644
--- a/Documentation/git-range-diff.txt
+++ b/Documentation/git-range-diff.txt
@@ -35,10 +35,19 @@ OPTIONS
 	When the commit diffs differ, `git range-diff` recreates the
 	original diffs' coloring, and adds outer -/+ diff markers with
 	the *background* being red/green to make it easier to see e.g.
-	when there was a change in what exact lines were added. This is
-	known to `range-diff` as "dual coloring". Use `--no-dual-color`
-	to revert to color all lines according to the outer diff markers
-	(and completely ignore the inner diff when it comes to color).
+	when there was a change in what exact lines were added.
++
+Additionally, the commit diff lines that are only present in the first commit
+range are shown "dimmed" (this can be overridden using the `color.diff.<slot>`
+config setting where `<slot>` is one of `contextDimmed`, `oldDimmed` and
+`newDimmed`), and the commit diff lines that are only present in the second
+commit range are shown in bold (which can be overridden using the config
+settings `color.diff.<slot>` with `<slot>` being one of `contextBold`,
+`oldBold` or `newBold`).
++
+This is known to `range-diff` as "dual coloring". Use `--no-dual-color`
+to revert to color all lines according to the outer diff markers
+(and completely ignore the inner diff when it comes to color).
 
 --creation-factor=<percent>::
 	Set the creation/deletion cost fudge factor to `<percent>`.
diff --git a/color.h b/color.h
index 33e786342..98894d6a1 100644
--- a/color.h
+++ b/color.h
@@ -36,6 +36,12 @@ struct strbuf;
 #define GIT_COLOR_BOLD_BLUE	"\033[1;34m"
 #define GIT_COLOR_BOLD_MAGENTA	"\033[1;35m"
 #define GIT_COLOR_BOLD_CYAN	"\033[1;36m"
+#define GIT_COLOR_FAINT_RED	"\033[2;31m"
+#define GIT_COLOR_FAINT_GREEN	"\033[2;32m"
+#define GIT_COLOR_FAINT_YELLOW	"\033[2;33m"
+#define GIT_COLOR_FAINT_BLUE	"\033[2;34m"
+#define GIT_COLOR_FAINT_MAGENTA	"\033[2;35m"
+#define GIT_COLOR_FAINT_CYAN	"\033[2;36m"
 #define GIT_COLOR_BG_RED	"\033[41m"
 #define GIT_COLOR_BG_GREEN	"\033[42m"
 #define GIT_COLOR_BG_YELLOW	"\033[43m"
diff --git a/diff.c b/diff.c
index 03ed235c7..272b0b938 100644
--- a/diff.c
+++ b/diff.c
@@ -69,6 +69,12 @@ static char diff_colors[][COLOR_MAXLEN] = {
 	GIT_COLOR_BOLD_YELLOW,	/* NEW_MOVED ALTERNATIVE */
 	GIT_COLOR_FAINT,	/* NEW_MOVED_DIM */
 	GIT_COLOR_FAINT_ITALIC,	/* NEW_MOVED_ALTERNATIVE_DIM */
+	GIT_COLOR_FAINT,	/* CONTEXT_DIM */
+	GIT_COLOR_FAINT_RED,	/* OLD_DIM */
+	GIT_COLOR_FAINT_GREEN,	/* NEW_DIM */
+	GIT_COLOR_BOLD,		/* CONTEXT_BOLD */
+	GIT_COLOR_BOLD_RED,	/* OLD_BOLD */
+	GIT_COLOR_BOLD_GREEN,	/* NEW_BOLD */
 };
 
 static const char *color_diff_slots[] = {
@@ -88,6 +94,12 @@ static const char *color_diff_slots[] = {
 	[DIFF_FILE_NEW_MOVED_ALT]     = "newMovedAlternative",
 	[DIFF_FILE_NEW_MOVED_DIM]     = "newMovedDimmed",
 	[DIFF_FILE_NEW_MOVED_ALT_DIM] = "newMovedAlternativeDimmed",
+	[DIFF_CONTEXT_DIM]	      = "contextDimmed",
+	[DIFF_FILE_OLD_DIM]	      = "oldDimmed",
+	[DIFF_FILE_NEW_DIM]	      = "newDimmed",
+	[DIFF_CONTEXT_BOLD]	      = "contextBold",
+	[DIFF_FILE_OLD_BOLD]	      = "oldBold",
+	[DIFF_FILE_NEW_BOLD]	      = "newBold",
 };
 
 static NORETURN void die_want_option(const char *option_name)
@@ -1089,11 +1101,13 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 
 			set_sign = set;
 			if (c == '-')
-				set = diff_get_color_opt(o, DIFF_FILE_OLD);
+				set = diff_get_color_opt(o, DIFF_FILE_OLD_BOLD);
 			else if (c == '@')
 				set = diff_get_color_opt(o, DIFF_FRAGINFO);
-			else if (c != '+')
-				set = diff_get_color_opt(o, DIFF_CONTEXT);
+			else if (c == '+')
+				set = diff_get_color_opt(o, DIFF_FILE_NEW_BOLD);
+			else
+				set = diff_get_color_opt(o, DIFF_CONTEXT_BOLD);
 			flags |= WS_IGNORE_FIRST_SPACE;
 		}
 		emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
@@ -1131,11 +1145,13 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 
 			set_sign = set;
 			if (c == '+')
-				set = diff_get_color_opt(o, DIFF_FILE_NEW);
+				set = diff_get_color_opt(o, DIFF_FILE_NEW_DIM);
 			else if (c == '@')
 				set = diff_get_color_opt(o, DIFF_FRAGINFO);
-			else if (c != '-')
-				set = diff_get_color_opt(o, DIFF_CONTEXT);
+			else if (c == '-')
+				set = diff_get_color_opt(o, DIFF_FILE_OLD_DIM);
+			else
+				set = diff_get_color_opt(o, DIFF_CONTEXT_DIM);
 		}
 		emit_line_ws_markup(o, set, reset, line, len, set_sign, '-',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);
diff --git a/diff.h b/diff.h
index 892416a14..a08a3b2a2 100644
--- a/diff.h
+++ b/diff.h
@@ -243,7 +243,13 @@ enum color_diff {
 	DIFF_FILE_NEW_MOVED = 13,
 	DIFF_FILE_NEW_MOVED_ALT = 14,
 	DIFF_FILE_NEW_MOVED_DIM = 15,
-	DIFF_FILE_NEW_MOVED_ALT_DIM = 16
+	DIFF_FILE_NEW_MOVED_ALT_DIM = 16,
+	DIFF_CONTEXT_DIM = 17,
+	DIFF_FILE_OLD_DIM = 18,
+	DIFF_FILE_NEW_DIM = 19,
+	DIFF_CONTEXT_BOLD = 20,
+	DIFF_FILE_OLD_BOLD = 21,
+	DIFF_FILE_NEW_BOLD = 22,
 };
 const char *diff_get_color(int diff_use_color, enum color_diff ix);
 #define diff_get_color_opt(o, ix) \
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 387+ messages in thread

* Re: [PATCH v3 09/20] range-diff: adjust the output of the commit pairs
  2018-07-20 19:16               ` Stefan Beller
@ 2018-07-21 22:07                 ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-21 22:07 UTC (permalink / raw)
  To: Stefan Beller; +Cc: Eric Sunshine, gitgitgadget, git, Junio C Hamano

[-- Attachment #1: Type: text/plain, Size: 3552 bytes --]

Hi Stefan,

On Fri, 20 Jul 2018, Stefan Beller wrote:

> >     1. To roll again.
> >
> >         A player who rolls two sixes can reroll the dice for an additional
> >         turn.
> 
> This is where I had my AHA moment!
> (Consider my software development process as chaotic as a dice roll
> So rerolling is really just rolling the dice again to "get my patch
> accepted" ;-)

Wouldn't that be nice? But you only get to reroll if you had two sixes.
Tough luck for you, Stefan.

> >     2. (programming) To convert (an unrolled instruction sequence) back into
> >        a loop. quotations ▼
> 
> We do not have unrolled loops?

When resending patch series? *rolls eyes*

> This was good back in the day where the cost of each instruction weighted
> heavy on the CPU, such that the JMPs that are needed (and the loop
> variable check that might have had a bad branch prediction) for the loop were
> slowing down the execution.
> 
> Nowadays (when I was studying 5 years ago) the branch prediction and
> individual instruction execution are really good, but the bottleneck
> that I measured (when I had a lot of time at my disposal and attending a
> class/project on micro architectures), was the CPU instruction cache
> size, i.e. loop unrolling made the code *slower* than keeping tight
> loops loaded in memory.
> https://stackoverflow.com/questions/24196076/is-gcc-loop-unrolling-flag-really-effective
> 
> > Noun
> >
> > reroll (plural rerolls)
> >
> >     (dice games) A situation in the rules of certain dice games where a
> >     player is given the option to reroll an undesirable roll of the dice.
> >
> >
> > You will notice how this does not list *any* hint at referring to
> > something that Junio calls "reroll".
> 
> We have undesirable patches that were 'rolled' onto the mailing list,
> so they have to be rerolled?
> 
> > Footnote *1*: https://en.wiktionary.org/wiki/commit#Noun does not even
> > bother to acknowledge our use of referring to a snapshot of a source code
> > base as a "commit".
> 
> When Git was a content addressable file system, a commit was precisely
> "a database transaction, [...] making it a permanent change."
> 
> Side note:
> I was just giving a talk to my colleagues about diff aglorithms
> (and eventually describing a bug in the histogram diff algorithm)
> and we got really riled up with "Longest Common Subsequence",
> as the mathematical definition is different than what the code
> or I (after studying the code) had in mind.
> 
> Naming things is hard, and sometimes the collective wisdom got
> it wrong, but changing it would be very costly in the short/medium
> term.

My point is not that naming is hard. But picking names that are
*different* from what is established nomenclature is... unwise.

In this case, it makes an already unnecessarily awkward code contribution
process even more unnecessarily uninviting.

> Another note about "rolling things": At $DAYJOB I review changes
> that are committed to the another revision control system w.r.t. its
> compliance of open source licenses (hence I am exposed to a lot
> of different projects), and some of those changes are titled
> "Roll up to version $X" which I found strange, but knew
> what was meant.

To "roll up" is, as far as this non-native speaker can tell, an
established way to express this action.

In short: nothing you wrote can adequately defend why the Git project
chooses to confuse new contributors seemingly on purpose.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 11/21] range-diff: add tests
  2018-07-21 22:05       ` [PATCH v4 11/21] range-diff: add tests Thomas Rast via GitGitGadget
@ 2018-07-22  5:04         ` Eric Sunshine
  2018-07-30 16:30           ` Johannes Schindelin
  2018-07-23 21:25         ` Stefan Beller
  1 sibling, 1 reply; 387+ messages in thread
From: Eric Sunshine @ 2018-07-22  5:04 UTC (permalink / raw)
  To: gitgitgadget; +Cc: Git List, Junio C Hamano, Thomas Rast

On Sat, Jul 21, 2018 at 6:05 PM Thomas Rast via GitGitGadget
<gitgitgadget@gmail.com> wrote:
> These are essentially lifted from https://github.com/trast/tbdiff, with
> light touch-ups to account for the command now being names `git

s/names/named/

> range-diff`.
>
> Apart from renaming `tbdiff` to `range-diff`, only one test case needed
> to be adjusted: 11 - 'changed message'.
>
> The underlying reason it had to be adjusted is that diff generation is
> sometimes ambiguous. In this case, a comment line and an empty line are
> added, but it is ambiguous whether they were added after the existing
> empty line, or whether an empty line and the comment line are added
> *before* the existing empty line. And apparently xdiff picks a different
> option here than Python's difflib.
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 18/21] completion: support `git range-diff`
  2018-07-21 22:05       ` [PATCH v4 18/21] completion: support `git range-diff` Johannes Schindelin via GitGitGadget
@ 2018-07-22  5:49         ` Eric Sunshine
  2018-08-10 20:24           ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Eric Sunshine @ 2018-07-22  5:49 UTC (permalink / raw)
  To: gitgitgadget; +Cc: Git List, Junio C Hamano, Johannes Schindelin

On Sat, Jul 21, 2018 at 6:05 PM Johannes Schindelin via GitGitGadget
<gitgitgadget@gmail.com> wrote:
> Tab completion of `git range-diff` is very convenient, especially
> given that the revision arguments to specify the commit ranges to
> compare are typically more complex than, say, what is normally passed
> to `git log`.
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
> @@ -1976,6 +1976,20 @@ _git_push ()
> +_git_range_diff ()
> +{
> +  case "$cur" in
> +  --*)
> +          __gitcomp "
> +               --creation-factor= --dual-color
> +                  $__git_diff_common_options
> +                  "

This is indented with a mix of spaces and tabs.

    Applying: completion: support `git range-diff`
    .git/rebase-apply/patch:18: space before tab in indent.
                --creation-factor= --dual-color
    warning: 1 line adds whitespace errors.
    Applying: range-diff: make --dual-color the default mode
    .git/rebase-apply/patch:105: space before tab in indent.
                --creation-factor= --no-dual-color
    warning: 1 line adds whitespace errors.

Other parts of this script seem to use tabs for indentation.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-21 21:56                               ` Johannes Schindelin
@ 2018-07-23  1:25                                 ` Jeff King
  2018-07-24  1:50                                   ` Junio C Hamano
  0 siblings, 1 reply; 387+ messages in thread
From: Jeff King @ 2018-07-23  1:25 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Junio C Hamano, Johannes Schindelin via GitGitGadget, git

On Sat, Jul 21, 2018 at 11:56:06PM +0200, Johannes Schindelin wrote:

> > The script that I feed a message from gmane or public-inbox when I need
> > to learn the set of commits that resulted from the message instead uses
> > "git grep $message-id notes/amlog".  And that is fast enough for my
> > purpose.
> 
> Awesome. You might want to make sure that Peff stops advertising the amlog
> notes, then, though.

Woah, what did I do now?

> > There is no good reason to abuse the notes mechanism to map a random
> > object-name looking string (i.e. hash result of message id), other
> > than the ease of "quick access" when somebody is making a lot of
> > inquiry, but that "database" does not have to be stored in notes.
> 
> Right. And it does not have to be stored anywhere, because nobody used it
> anyway, right?

If I understand the situation correctly, Junio is saying that he will
continue to produce the amlog mapping, and that it contains sufficient
information to produce the reverse mapping (which, as an aside, I did
not even know existed -- I mostly want to go the other way, from digging
in history to a mailing list conversation).

E.g., the script below builds and queries an incremental reverse
mapping.

-- >8 --
#!/usr/bin/perl

my $REF = 'refs/notes/amlog';
my $DBFILE = '.git/amlog.rev';

use DB_File;
my %h;
my $db = tie %h, 'DB_File', $DBFILE, O_CREAT|O_RDWR, 0644
  or die "unable to open/create $DBFILE: $!";

my $db_tip = $h{TIP};
chomp(my $rev_tip = `git rev-parse $REF`);
if (!defined $db_tip || $db_tip ne $rev_tip) {
  print STDERR "Updating reverse mapping...\n";
  # using -p here is quick and easy, since we know the
  # shape of the data. Using --raw and cat-file might be less
  # hacky, though.
  my @cmd = (qw(git log --format= --reverse -p), $rev_tip);
  push @cmd, "^$db_tip" if defined $db_tip;
  open(my $fh, "-|", @cmd);

  my $commit;
  while (<$fh>) {
    if (m{^\+\+\+ b/([0-9a-f/]+)}) {
      $commit = $1;
      $commit =~ s/[^0-9a-f]//g;
    } elsif (/^\+Message-Id: <(.*)>/i) {
      print STDERR "Imported $commit => $1\n";
      $h{$1} = $commit;
    }
  }
  $h{TIP} = $rev_tip;
}

print "$h{$_} $_\n" for @ARGV;
-- >8 --

That stores it in a local dbm. But it could also build a git-notes tree
if you really want that.

And if I understand what is being said here:

> > It certainly does not belong to cycles worth spending by me *while*
> > I work during the say with various history reshaping tools to record
> > and/or update the reverse mapping and that is why my post-applypatch
> > hook no longer has the "reverse map" hack.
> > 
> > It is not like anybody (including me) needs realtime up-to-date
> > reverse mapping from amlog while I run my "commit --amend", "rebase
> > -i", etc. and the reverse map is constructable by reversing the
> > forward map, obviously, with a postprocessing.  And I think that is
> > a reasonably way forward if anybody wants to have a reverse mapping.
> > The postprocessing can be done either by me before pushing out the
> > amlog ref, or done by any consumer after fetching the amlog ref from
> > me.  If I did the postprocessing and refuse to use rewrite hook you
> > wouldn't even know ;-)

It is not "I refuse to push out a reverse mapping". It is "I could make
the reverse mapping before push-out, and you would not need to know or
care if I did it all at once, or using a rewrite hook".

Though personally, I do not know if there is much point in pushing it
out, given that receivers can reverse the mapping themselves.

Or is there some argument that there is information in the reverse map
that _cannot_ be generated from the forward map?

-Peff

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (20 preceding siblings ...)
  2018-07-21 22:05       ` [PATCH v4 21/21] range-diff: use dim/bold cues to improve dual color mode Johannes Schindelin via GitGitGadget
@ 2018-07-23 21:03       ` Stefan Beller
  2018-07-23 21:49         ` Junio C Hamano
                           ` (2 more replies)
  2018-07-29 21:50       ` Thomas Gummerer
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
  23 siblings, 3 replies; 387+ messages in thread
From: Stefan Beller @ 2018-07-23 21:03 UTC (permalink / raw)
  To: gitgitgadget; +Cc: git, Junio C Hamano

On Sat, Jul 21, 2018 at 3:04 PM Johannes Schindelin via GitGitGadget
<gitgitgadget@gmail.com> wrote:

> 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
> 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:

Thanks for making tools that makes the life of a Git developer easier!
(Just filed https://github.com/gitgitgadget/gitgitgadget/issues/26
which asks to break lines for this cover letter)

> base-commit: b7bd9486b055c3f967a870311e704e3bb0654e4f
> Published-As: https://github.com/gitgitgadget/git/releases/tags/pr-1%2Fdscho%2Fbranch-diff-v4
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1/dscho/branch-diff-v4
> Pull-Request: https://github.com/gitgitgadget/git/pull/1

I just realized that if I had ideal memory of the previous review,
I could contain my whole review answer to this email only.

>
> Range-diff vs v3:
>
>   1:  39272eefc !  1:  f7e70689e linear-assignment: a function to solve least-cost assignment problems
>      @@ -223,9 +223,7 @@
>       +                         BUG("negative j: %d", j);
>       +                 i = pred[j];
>       +                 column2row[j] = i;
>      -+                 k = j;
>      -+                 j = row2column[i];
>      -+                 row2column[i] = k;
>      ++                 SWAP(j, row2column[i]);

The dual color option (as a default) really helps here. Thanks for that!
Does it have to be renamed though? (It's more than two colors; originally
it was inverting the beginning signs)

Maybe --color=emphasize-later
assuming there will be other modes for coloring, such as "diff-only",
which would correspond with --no-dual-color, or "none" that will correspond
would be --no-color. I imagine there could be more fancy things, hence I would
propose a mode rather than a switch.
(Feel free to push back if discussing naming here feels like bike shedding)


2:  7f15b26d4ea !  82:  88134121d2a Introduce `range-diff` to compare
iterations of a topic branch
[...]
>       diff --git a/Makefile b/Makefile
>       --- a/Makefile
>       +++ b/Makefile

The line starting with --- is red (default removed color) and the line
with +++ is green (default add color).

Ideally these two lines and the third line above starting with "diff --git"
would render in GIT_COLOR_BOLD ("METAINFO").

>   3:  076e1192d !  3:  4e3fb47a1 range-diff: first rudimentary implementation
>      @@ -4,7 +4,7 @@
>
>           At this stage, `git range-diff` can determine corresponding commits
>           of two related commit ranges. This makes use of the recently introduced
>      -    implementation of the Hungarian algorithm.
>      +    implementation of the linear assignment algorithm.
>
>           The core of this patch is a straight port of the ideas of tbdiff, the
>           apparently dormant project at https://github.com/trast/tbdiff.
>      @@ -51,19 +51,17 @@
>       + int res = 0;
>       + struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
>
>      -- argc = parse_options(argc, argv, NULL, options,
>      --                      builtin_range_diff_usage, 0);
>      -+ argc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,
>      -+                      0);
>      +  argc = parse_options(argc, argv, NULL, options,
>      +                       builtin_range_diff_usage, 0);

This is really nice in colors when viewed locally.

>  16:  dfa7b1e71 <  -:  --------- range-diff --dual-color: work around bogus white-space warning
>   -:  --------- > 16:  f4252f2b2 range-diff --dual-color: fix bogus white-space warning

Ah; here my initial assumption of only reviewing the range-diff breaks down now.
I'll dig into patch 16 separately.

Maybe it is worth having an option to expand all "new" patches.
(Given that the range-diff pr-1/dscho/branch-diff-v3...pr-1/dscho/branch-diff-v4
told me you used a different base, this is a hard problem, as I certainly
would want to skip over all new base commits, but this one is interesting
to look at. An easy way out: Maybe an option to expand any new
commits/patches after the first expansion? Asking for opinions rather
than implementing it)

>  19:  144363006 <  -:  --------- range-diff: left-pad patch numbers
>   -:  --------- > 19:  07ec215e8 range-diff: left-pad patch numbers

>   -:  --------- > 21:  d8498fb32 range-diff: use dim/bold cues to improve dual color mode

Those are interesting, I'll look at them separately, too.

Thanks,
Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 11/21] range-diff: add tests
  2018-07-21 22:05       ` [PATCH v4 11/21] range-diff: add tests Thomas Rast via GitGitGadget
  2018-07-22  5:04         ` Eric Sunshine
@ 2018-07-23 21:25         ` Stefan Beller
  1 sibling, 0 replies; 387+ messages in thread
From: Stefan Beller @ 2018-07-23 21:25 UTC (permalink / raw)
  To: gitgitgadget; +Cc: git, Junio C Hamano, Thomas Rast

> +test_expect_success 'simple B...C (unmodified)' '
> +       git range-diff --no-color

I wonder if we want to have tests for colors, too, eventually.
(Feel free to push back on it or put it into the left over hashtag.
But given how much time we (or I) spent discussing colors,
this would be a welcome extension for the tests)

Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike
  2018-07-23 21:03       ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Stefan Beller
@ 2018-07-23 21:49         ` Junio C Hamano
  2018-07-25 17:44           ` Stefan Beller
  2018-07-26  9:47         ` Johannes Schindelin
  2018-08-08 13:05         ` Johannes Schindelin
  2 siblings, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-07-23 21:49 UTC (permalink / raw)
  To: Stefan Beller; +Cc: gitgitgadget, git

Stefan Beller <sbeller@google.com> writes:

> On Sat, Jul 21, 2018 at 3:04 PM Johannes Schindelin via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
>
>> 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
>> 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:
>
> Thanks for making tools that makes the life of a Git developer easier!
> (Just filed https://github.com/gitgitgadget/gitgitgadget/issues/26
> which asks to break lines for this cover letter)

Thanks.  These cover letters are unreadable without W Q
(gnus-article-fill-long-lines)

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 16/21] range-diff --dual-color: fix bogus white-space warning
  2018-07-21 22:05       ` [PATCH v4 16/21] range-diff --dual-color: fix bogus white-space warning Johannes Schindelin via GitGitGadget
@ 2018-07-23 22:20         ` Stefan Beller
  2018-08-10 21:05           ` Johannes Schindelin
  2018-07-23 22:39         ` Junio C Hamano
  1 sibling, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-07-23 22:20 UTC (permalink / raw)
  To: gitgitgadget; +Cc: git, Junio C Hamano, Johannes Schindelin

On Sat, Jul 21, 2018 at 3:05 PM Johannes Schindelin via GitGitGadget
<gitgitgadget@gmail.com> wrote:
>
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
>
> When displaying a diff of diffs, it is possible that there is an outer
> `+` before a context line. That happens when the context changed between
> old and new commit. When that context line starts with a tab (after the
> space that marks it as context line), our diff machinery spits out a
> white-space error (space before tab), but in this case, that is
> incorrect.
>
> Fix this by adding a specific whitespace flag that simply ignores the
> first space in the output.

That sounds like a simple (not easy) solution, which sounds acceptable
to me here.

I guess you dropped all ideas that I originally proposed for the cleanup
regarding ws. that is fine, I can roll the cleanup on top of your patches
here.

> Of course, this flag is *really* specific to the "diff of diffs" use
> case. The original idea was to simply skip the space from the output,
> but that workaround was rejected by the Git maintainer as causing
> headaches.

By that you mean
https://public-inbox.org/git/xmqqr2kb9jk2.fsf@gitster-ct.c.googlers.com/
?

> Note: as the original code did not leave any space in the bit mask
> before the WSEH_* bits, the diff of this commit looks unnecessarily
> involved: the diff is dominated by making room for one more bit to be
> used by the whitespace rules.

It took me some minutes, but I am reasonably convinced this patch
is correct (and doesn't collide with other series in flight, sb/diff-color-more
adds another flag to move detection in another bit field at (1<<23))

Thanks for writing this patch instead of the other, though I'll leave
it to Junio to weigh in if this approach is the best design.

Stefan

>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  cache.h |  3 ++-
>  diff.c  | 15 ++++++++-------
>  diff.h  |  6 +++---
>  ws.c    | 11 ++++++++++-
>  4 files changed, 23 insertions(+), 12 deletions(-)
>
> diff --git a/cache.h b/cache.h
> index 8b447652a..8abfbeb73 100644
> --- a/cache.h
> +++ b/cache.h
> @@ -1681,11 +1681,12 @@ void shift_tree_by(const struct object_id *, const struct object_id *, struct ob
>  #define WS_CR_AT_EOL           01000
>  #define WS_BLANK_AT_EOF        02000
>  #define WS_TAB_IN_INDENT       04000
> +#define WS_IGNORE_FIRST_SPACE 010000
>  #define WS_TRAILING_SPACE      (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF)
>  #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|8)
>  #define WS_TAB_WIDTH_MASK        077
>  /* All WS_* -- when extended, adapt diff.c emit_symbol */
> -#define WS_RULE_MASK           07777
> +#define WS_RULE_MASK           017777
>  extern unsigned whitespace_rule_cfg;
>  extern unsigned whitespace_rule(const char *);
>  extern unsigned parse_whitespace_rule(const char *);
> diff --git a/diff.c b/diff.c
> index e163bc8a3..03ed235c7 100644
> --- a/diff.c
> +++ b/diff.c
> @@ -650,14 +650,14 @@ enum diff_symbol {
>  };
>  /*
>   * Flags for content lines:
> - * 0..12 are whitespace rules
> - * 13-15 are WSEH_NEW | WSEH_OLD | WSEH_CONTEXT
> - * 16 is marking if the line is blank at EOF
> + * 0..14 are whitespace rules
> + * 14-16 are WSEH_NEW | WSEH_OLD | WSEH_CONTEXT
> + * 17 is marking if the line is blank at EOF
>   */
> -#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF     (1<<16)
> -#define DIFF_SYMBOL_MOVED_LINE                 (1<<17)
> -#define DIFF_SYMBOL_MOVED_LINE_ALT             (1<<18)
> -#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING   (1<<19)
> +#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF     (1<<17)
> +#define DIFF_SYMBOL_MOVED_LINE                 (1<<18)
> +#define DIFF_SYMBOL_MOVED_LINE_ALT             (1<<19)
> +#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING   (1<<20)
>  #define DIFF_SYMBOL_CONTENT_WS_MASK (WSEH_NEW | WSEH_OLD | WSEH_CONTEXT | WS_RULE_MASK)
>
>  /*
> @@ -1094,6 +1094,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
>                                 set = diff_get_color_opt(o, DIFF_FRAGINFO);
>                         else if (c != '+')
>                                 set = diff_get_color_opt(o, DIFF_CONTEXT);
> +                       flags |= WS_IGNORE_FIRST_SPACE;
>                 }
>                 emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
>                                     flags & DIFF_SYMBOL_CONTENT_WS_MASK,
> diff --git a/diff.h b/diff.h
> index 79beb6eea..892416a14 100644
> --- a/diff.h
> +++ b/diff.h
> @@ -160,9 +160,9 @@ struct diff_options {
>         int abbrev;
>         int ita_invisible_in_index;
>  /* white-space error highlighting */
> -#define WSEH_NEW (1<<12)
> -#define WSEH_CONTEXT (1<<13)
> -#define WSEH_OLD (1<<14)
> +#define WSEH_NEW (1<<13)
> +#define WSEH_CONTEXT (1<<14)
> +#define WSEH_OLD (1<<15)
>         unsigned ws_error_highlight;
>         const char *prefix;
>         int prefix_length;
> diff --git a/ws.c b/ws.c
> index a07caedd5..e02365a6a 100644
> --- a/ws.c
> +++ b/ws.c
> @@ -20,6 +20,7 @@ static struct whitespace_rule {
>         { "blank-at-eol", WS_BLANK_AT_EOL, 0 },
>         { "blank-at-eof", WS_BLANK_AT_EOF, 0 },
>         { "tab-in-indent", WS_TAB_IN_INDENT, 0, 1 },
> +       { "ignore-first-space", WS_IGNORE_FIRST_SPACE, 0, 1 },
>  };
>
>  unsigned parse_whitespace_rule(const char *string)
> @@ -177,8 +178,16 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,
>         if (trailing_whitespace == -1)
>                 trailing_whitespace = len;
>
> +       if ((ws_rule & WS_IGNORE_FIRST_SPACE) && len && line[0] == ' ') {
> +               if (stream)
> +                       fwrite(line, 1, 1, stream);
> +               written++;
> +               if (!trailing_whitespace)
> +                       trailing_whitespace++;
> +       }
> +
>         /* Check indentation */
> -       for (i = 0; i < trailing_whitespace; i++) {
> +       for (i = written; i < trailing_whitespace; i++) {
>                 if (line[i] == ' ')
>                         continue;
>                 if (line[i] != '\t')
> --
> gitgitgadget
>

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 14/21] diff: add an internal option to dual-color diffs of diffs
  2018-07-21 22:05       ` [PATCH v4 14/21] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin via GitGitGadget
@ 2018-07-23 22:27         ` Junio C Hamano
  2018-07-23 22:48           ` Stefan Beller
  0 siblings, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-07-23 22:27 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget; +Cc: git, Johannes Schindelin

"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:

> From: Johannes Schindelin <johannes.schindelin@gmx.de>
>
> When diffing diffs, it can be quite daunting to figure out what the heck
> is going on, as there are nested +/- signs.
>
> Let's make this easier by adding a flag in diff_options that allows
> color-coding the outer diff sign with inverted colors, so that the
> preimage and postimage is colored like the diff it is.
>
> Of course, this really only makes sense when the preimage and postimage
> *are* diffs. So let's not expose this flag via a command-line option for
> now.
>
> This is a feature that was invented by git-tbdiff, and it will be used
> by `git range-diff` in the next commit, by offering it via a new option:
> `--dual-color`.
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  diff.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++-----------
>  diff.h |  1 +
>  2 files changed, 69 insertions(+), 15 deletions(-)
>
> diff --git a/diff.c b/diff.c
> index a94a8214f..e163bc8a3 100644
> --- a/diff.c
> +++ b/diff.c
> @@ -563,14 +563,18 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,
>  	ecbdata->blank_at_eof_in_postimage = (at - l2) + 1;
>  }
>  
> -static void emit_line_0(struct diff_options *o, const char *set, const char *reset,
> +static void emit_line_0(struct diff_options *o,
> +			const char *set, unsigned reverse, const char *reset,
>  			int first, const char *line, int len)
>  {
>  	int has_trailing_newline, has_trailing_carriage_return;
>  	int nofirst;
>  	FILE *file = o->file;
>  
> -	fputs(diff_line_prefix(o), file);
> +	if (first)
> +		fputs(diff_line_prefix(o), file);
> +	else if (!len)
> +		return;

Can you explain this hunk in the log message?  I am not sure how the
description in the log message relates to this change.  Is the idea
of this change essentially "all the existing callers that aren't
doing the diff-of-diffs send a non-NUL first character, and for them
this change is a no-op.  New callers share most of the remainder of
emit_line_0() logic but do not want to show the prefix, so the
support for it is piggy-backing by a special case where first could
be NUL"?

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 16/21] range-diff --dual-color: fix bogus white-space warning
  2018-07-21 22:05       ` [PATCH v4 16/21] range-diff --dual-color: fix bogus white-space warning Johannes Schindelin via GitGitGadget
  2018-07-23 22:20         ` Stefan Beller
@ 2018-07-23 22:39         ` Junio C Hamano
  2018-07-24  1:27           ` Junio C Hamano
  1 sibling, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-07-23 22:39 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget; +Cc: git, Johannes Schindelin

"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:

> @@ -177,8 +178,16 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,
>  	if (trailing_whitespace == -1)
>  		trailing_whitespace = len;
>  
> +	if ((ws_rule & WS_IGNORE_FIRST_SPACE) && len && line[0] == ' ') {
> +		if (stream)
> +			fwrite(line, 1, 1, stream);
> +		written++;
> +		if (!trailing_whitespace)
> +			trailing_whitespace++;
> +	}
> +
>  	/* Check indentation */
> -	for (i = 0; i < trailing_whitespace; i++) {
> +	for (i = written; i < trailing_whitespace; i++) {

It is pleasing to see that with a surprisingly clean and small
change like this we can exempt the initial space byte from
SP-before-HT check and from Indent-with-non-tab at the same time.

Very nice.

One reason why a surprisingly small special case is required is
perhaps because we are blessed with the original code being clean
[*1*], and the fact that a line[0] that is not ' ' will not trigger
any indentation related whitespace errors without this special case,
I guess.

>  		if (line[i] == ' ')
>  			continue;
>  		if (line[i] != '\t')


[Footnote]

*1* ws.c used to be almost all my code long time ago, but most of
    the shape of the current whitespace_error checking code comes from
    c1795bb08aa which is not mine, and I can say good things about it
    without feeling embarrassingly boasty ;-)

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 14/21] diff: add an internal option to dual-color diffs of diffs
  2018-07-23 22:27         ` Junio C Hamano
@ 2018-07-23 22:48           ` Stefan Beller
  0 siblings, 0 replies; 387+ messages in thread
From: Stefan Beller @ 2018-07-23 22:48 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: gitgitgadget, git, Johannes Schindelin

> > -     fputs(diff_line_prefix(o), file);
> > +     if (first)
> > +             fputs(diff_line_prefix(o), file);
> > +     else if (!len)
> > +             return;
>
> Can you explain this hunk in the log message?  I am not sure how the
> description in the log message relates to this change.  Is the idea
> of this change essentially "all the existing callers that aren't
> doing the diff-of-diffs send a non-NUL first character, and for them
> this change is a no-op.  New callers share most of the remainder of
> emit_line_0() logic but do not want to show the prefix, so the
> support for it is piggy-backing by a special case where first could
> be NUL"?

All but two caller have 'reverse' set to 0, using the arguments as before.

The other two callers are using the function twice to get the prefix
and set sign going, and then the second call to get the rest of the
line going (which needs to omit the prefix as that was done in the
first call) :

+               /* Emit just the prefix, then the rest. */
+               emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
+                           sign, "", 0);
+               emit_line_0(o, set, 0, reset, 0, line, len);

I attempted to clean it up on top, but likely got it wrong as we have
no tests for colored range diffs, yet.
https://public-inbox.org/git/20180710174552.30123-3-sbeller@google.com/
My suggestion would be to first clarify emit_line_0 and have its arguments
and its execution map better to each other, (and as a result only needing to
have one call of emit_line_0 instead of two)

That is my understanding of the situation.

Thanks,
Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 16/21] range-diff --dual-color: fix bogus white-space warning
  2018-07-23 22:39         ` Junio C Hamano
@ 2018-07-24  1:27           ` Junio C Hamano
  0 siblings, 0 replies; 387+ messages in thread
From: Junio C Hamano @ 2018-07-24  1:27 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget; +Cc: git, Johannes Schindelin

Junio C Hamano <gitster@pobox.com> writes:

> It is pleasing to see that with a surprisingly clean and small
> change like this we can exempt the initial space byte from
> SP-before-HT check and from Indent-with-non-tab at the same time.
>
> Very nice.
>
> One reason why a surprisingly small special case is required is
> perhaps because we are blessed with the original code being clean
> [*1*], and the fact that a line[0] that is not ' ' will not trigger
> any indentation related whitespace errors without this special case,
> I guess.

Having said good things about the patch, I unfortunately realized
that we weren't that lucky.  As we do want to see whitespace errors
on lines with line[0] != ' ' to be flagged.  So "... will not
trigger" in the above is not a blessing, but something that further
needs to be fixed.  The special case should also be made for line[0]
that is '+' and possibly '-' (and I also suspect that the changes in
this patch may mostly be reusable with little tweaks if any).

Imagine we start from this commit that "git show" shows us like so:

	 int main(int ac, char **av)
	 {
	 ________  printf("Hello");
	+________  putchar(',');
	+          putchar(' ');
	           printf("World\n");
	           return 0;
	 }

I've drawn a horizontal-tab as long underscore to clarify in the
above picture.  If you have "core.whitespace=indent-with-non-tab"
"git show" would paint the line that adds " " as violating (as it
types 10 SPs, when it could have been a tab and 2 SPs), but it does
not highlight the line with "World" or "return", which is sensible
as they are pre-existing violations.

Then imagine we did "git commit --amend" and "git show" would give
this instead:

	 int main(int ac, char **av)
	 {
	 ________  printf("Hello");
	+          putchar(',');
	+________  putchar(' ');
	           printf("World\n");
	           return 0;
	 }

That is, relative to the previous attempt, we stopped introducing
new indent-with-non-tab violation to the line that adds " ", but
added a new violation to the line that adds ",".

After such "git commit --amend", what do we want to see in the
output from "git range-diff @{1}..."?

My quick test of the current code does not show any whitespace
breakage for either versions.  I *think* what we want to actually
see is

 - just like WS_IGNORE_FIRST_SPACE logic shifted the column by
   incrementing written (and final condition) for the loop in your
   patch for line[0]==' ', detect the overlong run of SP correctly
   by ignoring the first column that is '+', and complaining that
   the new commit is typing 10 SPs before putchar(',').

 - Earlier I thought that lines with '-' in the outer diff should
   become exempt from whitespace-error highlighting, but I think
   that is a mistake.  If a line in diff-of-diff that begins with
   "-+" has whitespace violation (e.g "-+" followed by 10 SPs), that
   is "the old version of the patch used to introduce whitespace
   violation", which is a useful piece of information when we look
   at the corresponding line in the same diff-of-diff that begins
   with "++".  We could either say "and you cleaned that mistake up
   in your newer patch", or "and you still have that mistake in your
   newer patch" (another possibility is there is no whitespace error
   on a "-+" line, and the corresponding "++" line has one---"you
   got it right in the previous round, but you somehow made it
   worse").

Here is the reproduction of the sample data I based on the above
thought experiment on.

diff --git a/script.sh b/script.sh
new file mode 100755
index 0000000..0090661
--- /dev/null
+++ b/script.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+git init
+git config core.whitespace indent-with-non-tab
+
+tr _ '\011' <<\EOF >hello.c
+int main(int ac, char **av)
+{
+_  printf("Hello");
+          printf("World\n");
+          return 0;
+}
+EOF
+
+git add hello.c && git commit -m initial
+
+tr _ '\011' <<\EOF >hello.c
+int main(int ac, char **av)
+{
+_  printf("Hello");
+          putchar(',');
+_  putchar(' ');
+          printf("World\n");
+          return 0;
+}
+EOF
+
+git commit -a -m second
+
+tr _ '\011' <<\EOF >hello.c
+int main(int ac, char **av)
+{
+_  printf("Hello");
+_  putchar(',');
+          putchar(' ');
+          printf("World\n");
+          return 0;
+}
+EOF
+
+git commit -a --amend -m third
+
+
+git show @{1}
+git show HEAD
+
+git range-diff ..@{1} @{1}..
+
-- 
2.18.0-232-gb7bd9486b0




^ permalink raw reply related	[flat|nested] 387+ messages in thread

* Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-23  1:25                                 ` Jeff King
@ 2018-07-24  1:50                                   ` Junio C Hamano
  2018-07-24  9:45                                     ` Jeff King
  0 siblings, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-07-24  1:50 UTC (permalink / raw)
  To: Jeff King; +Cc: Johannes Schindelin, Johannes Schindelin via GitGitGadget, git

Jeff King <peff@peff.net> writes:

> If I understand the situation correctly, Junio is saying that he will
> continue to produce the amlog mapping, and that it contains sufficient
> information to produce the reverse mapping (which, as an aside, I did
> not even know existed -- I mostly want to go the other way, from digging
> in history to a mailing list conversation).

Yes, the reverse mapping in amlog was an experiment that did not
work well in the end.

When I use "git am" to make a commit out of a message, a
post-applypatch hook picks up the "Message-Id:" from the original
message and adds a git note to the resulting commit.  This is in
line with how the notes are meant to be used.  We have a commit
object, and a piece of information that we want to associate with
the commit object, which is not recorded as a part of the commit
object.  So we say "git notes add -m 'that piece of info' $commit"
(the message-id happens to be that piece of info in this example).

And with notes.rewriteRef, "git commit --amend" etc. would copy the
piece of info about the original commit to the rewritten commit.

	Side Note: there are a few workflow elements I do want to
	keep using but they currently *lose* the mapping info.  An
	obvious one is

	  $ git checkout -b to/pic master &&
	  ... review in MUA and then ...
	  $ git am -s mbox &&
	  ... review in tree, attempt to build, tweak, etc.
          $ git format-patch --stdout master..to/pic >P &&
          $ edit P &&
          $ git reset --hard master &&
          $ git am P

	which is far more versatile and efficient when doing certain
	transformations on the series than running "rebase -i" and
	reopening and editing the target files of the patches one by
	one in each step.  But because format-patch does not
	generate Message-Id header of the original one out of the
	commit, the post-applypatch hook run by "am" at the end of
	the steps would not have a chance to record that for the
	newly created commit.

	For this one, I think I can use "format-patch --notes=amlog"
	to produce the patch file and then teach post-applypatch
	script to pay attention to the Notes annotation without
	changing anything else to record the message id of the
	original.  Other workflow elements that lose the notes need
	to be identified and either a fix implemented or a
	workaround found for each of them.  For example, I suspect
	there is no workaround for "cherry-pick" and it would take a
	real fix.

A reverse mapping entry used to get created by post-applypatch to
map the blob that represents the notes text added to the $commit to
another text blob that contains the 40-hex of the commit object.
This is the experiment that did not work well.  As none of the later
integrator's work e.g. "commit --amend", "rebase", "cherry-pick",
etc. is about rewriting that blob, notes.rewriteRef mechanism would
not kick in, and that is understandasble.

And these (incomplete) reverse mapping entries get in the way to
maintain and correct the forward mapping.  When a commit that got
unreachable gets expired, I want "git notes prune" to remove notes
on them, and I do not want to even think about what should happen to
the entries in the notes tree that abuse the mechanism to map blobs
that are otherwise *not* even reachable from the main history.

A much more important task is to make sure that the forward mapping
that annotates invidual commits reachable from 'pu' and/or 'master' 
is maintained correctly by various tools.  From a correctly maintained
forward mapping, it should be straight forward to get a reverse mapping
if needed.

> Though personally, I do not know if there is much point in pushing it
> out, given that receivers can reverse the mapping themselves.

Before this thread, I was planning to construct and publish the
reverse mapping at the end of the day, but do so on a separate notes
ref (see above---the hacky abuse gets in the way of maintaining and
debugging the forward mapping, but a separate notes-ref that only
contains hacks is less worrysome).  But I have changed my mind and
decided not to generate or publish one.  It is sort of similar to
the way the pack .idx is constructed only by the receiver [*1*].

> Or is there some argument that there is information in the reverse map
> that _cannot_ be generated from the forward map?

I know there is no information loss (after all I was the only one
who ran that experimental hack), but there is one objection that is
still possible, even though I admit that is a weak argument.

If a plumbing "diff-{files,tree,index}" family had a sibling
"diff-notes" to compare two notes-shaped trees while pretending that
the object-name fan-out did not exist (i.e. instead, the trees being
compared is without a subtree and full of 40-hex filenames), then it
would be less cumbersome to incrementally update the reverse mapping
by reading forward mapping with something like:

	git diff-notes --raw amlog@{1} amlog

to learn the commits whose notes have changed.  But without such a
plumbing, it is cumbersome to do so correctly.  "git diff-tree -r"
could serve as a rough substitute, until the note tree grows and get
rebalanced by reorganizing the fan-out, and on the day it happens
the reverse mapper needs to read and discard ghost changes that are
only due to tree reorganizing [*2*].


[Footnotes]

*1* Even if the sender could give one when it creates a .pack, the
    receiver would not trust that it is matches the corresponding
    .pack before using it, and the cost to validate is similar to
    the cost to generate.

*2* That makes it less efficient on that day (which hopefully would
    happen once in a blue moon) but would not affect correctness.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: refs/notes/amlog problems, was Re: [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems
  2018-07-24  1:50                                   ` Junio C Hamano
@ 2018-07-24  9:45                                     ` Jeff King
  0 siblings, 0 replies; 387+ messages in thread
From: Jeff King @ 2018-07-24  9:45 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Johannes Schindelin, Johannes Schindelin via GitGitGadget, git

On Mon, Jul 23, 2018 at 06:50:46PM -0700, Junio C Hamano wrote:

> 	Side Note: there are a few workflow elements I do want to
> 	keep using but they currently *lose* the mapping info.  An
> 	obvious one is
> 
> 	  $ git checkout -b to/pic master &&
> 	  ... review in MUA and then ...
> 	  $ git am -s mbox &&
> 	  ... review in tree, attempt to build, tweak, etc.
>           $ git format-patch --stdout master..to/pic >P &&
>           $ edit P &&
>           $ git reset --hard master &&
>           $ git am P
> 
> 	which is far more versatile and efficient when doing certain
> 	transformations on the series than running "rebase -i" and
> 	reopening and editing the target files of the patches one by
> 	one in each step.  But because format-patch does not
> 	generate Message-Id header of the original one out of the
> 	commit, the post-applypatch hook run by "am" at the end of
> 	the steps would not have a chance to record that for the
> 	newly created commit.
> 
> 	For this one, I think I can use "format-patch --notes=amlog"
> 	to produce the patch file and then teach post-applypatch
> 	script to pay attention to the Notes annotation without
> 	changing anything else to record the message id of the
> 	original.

Yes. I wonder if it would make sense to teach format-patch/am a
micro-format to automatically handle this case. I.e., some
machine-readable way of passing the notes in the email message.
Of course it's easy to design a format that covers the relatively
restricted form of these amlog notes, and much harder to cover the
general case.

>       Other workflow elements that lose the notes need
> 	to be identified and either a fix implemented or a
> 	workaround found for each of them.  For example, I suspect
> 	there is no workaround for "cherry-pick" and it would take a
> 	real fix.

I think the existing notes.rewriteRef is probably a good match here. I
can definitely think of notes you wouldn't want to cherry-pick, but I'm
having trouble coming up with an example that should survive a rebase
but not a cherry-pick.

> And these (incomplete) reverse mapping entries get in the way to
> maintain and correct the forward mapping.  When a commit that got
> unreachable gets expired, I want "git notes prune" to remove notes
> on them, and I do not want to even think about what should happen to
> the entries in the notes tree that abuse the mechanism to map blobs
> that are otherwise *not* even reachable from the main history.

Right, I think the notes tree is a poor distribution method for that
reason.

> > Though personally, I do not know if there is much point in pushing it
> > out, given that receivers can reverse the mapping themselves.
> 
> Before this thread, I was planning to construct and publish the
> reverse mapping at the end of the day, but do so on a separate notes
> ref (see above---the hacky abuse gets in the way of maintaining and
> debugging the forward mapping, but a separate notes-ref that only
> contains hacks is less worrysome).  But I have changed my mind and
> decided not to generate or publish one.  It is sort of similar to
> the way the pack .idx is constructed only by the receiver [*1*].

Yes, the pack .idx was the same mental model I had when writing my
earlier message.

> > Or is there some argument that there is information in the reverse map
> > that _cannot_ be generated from the forward map?
> 
> I know there is no information loss (after all I was the only one
> who ran that experimental hack), but there is one objection that is
> still possible, even though I admit that is a weak argument.

I wondered if you might have a case like this (building as we go):

 - message-id M becomes commit X
   - we write the forward map X->M
   - we write the reverse map M->X
 - during a rewrite (e.g., --amend), commit X becomes commit Y
   - we write the forward map Y->M
   - we write the reverse map M->Y

The difference between that result and an inverted map created at the
end is that we know that M->Y is the final result. Whereas by looking at
the inverted map, we do not know which of M->X and M->Y is correct. In
fact they are _both_ correct. But only one of X and Y would eventually
get merged (both may make it into the repo's of people fetching from you
if we imagine that X is on "pu" and you push between the two steps).

So I think the inverted mapping is not actually one-to-one, and in
either case you'd want to retain all possible matches (pruning only when
a commit is eventually dropped from the forward mapping, which rewritten
things from "pu" would eventually do). And in that case it does not
matter if you generate it incrementally or all at once.

> If a plumbing "diff-{files,tree,index}" family had a sibling
> "diff-notes" to compare two notes-shaped trees while pretending that
> the object-name fan-out did not exist (i.e. instead, the trees being
> compared is without a subtree and full of 40-hex filenames), then it
> would be less cumbersome to incrementally update the reverse mapping
> by reading forward mapping with something like:
> 
> 	git diff-notes --raw amlog@{1} amlog
> 
> to learn the commits whose notes have changed.  But without such a
> plumbing, it is cumbersome to do so correctly.  "git diff-tree -r"
> could serve as a rough substitute, until the note tree grows and get
> rebalanced by reorganizing the fan-out, and on the day it happens
> the reverse mapper needs to read and discard ghost changes that are
> only due to tree reorganizing [*2*].

Yeah. My "log" hackery was trying to do that incremental comparison, but
it did not handle the multiple-commit case (nor did it handle
deletions). I agree an end-point diff is sufficient (and more
efficient).

-Peff

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike
  2018-07-23 21:49         ` Junio C Hamano
@ 2018-07-25 17:44           ` Stefan Beller
  0 siblings, 0 replies; 387+ messages in thread
From: Stefan Beller @ 2018-07-25 17:44 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: gitgitgadget, git

On Mon, Jul 23, 2018 at 2:49 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Stefan Beller <sbeller@google.com> writes:
>
> > On Sat, Jul 21, 2018 at 3:04 PM Johannes Schindelin via GitGitGadget
> > <gitgitgadget@gmail.com> wrote:
> >
> >> 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
> >> 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:
> >
> > Thanks for making tools that makes the life of a Git developer easier!
> > (Just filed https://github.com/gitgitgadget/gitgitgadget/issues/26
> > which asks to break lines for this cover letter)
>
> Thanks.  These cover letters are unreadable without W Q
> (gnus-article-fill-long-lines)

While I had some comments on how I dislike some aspects of the
implementation, I think it proves its usefulness by my usage, so I
would suggest to merge it down to next as-is (and as soon as possible);
deferring the issues in the implementation to later.

I found running the range-diff on origin/pu to be a pleasant
experience, although that still highlights the issues of
in-exact coloring (the colors are chosen by the first two
characters of the diff, which leads to mis-coloring of
diff headers of the inner diff in the outer diff.

But despite the imperfection, I strongly urge to consider
the series as-is as good enough for inclusion.

Thanks,
Stefan

Thanks,
Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike
  2018-07-23 21:03       ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Stefan Beller
  2018-07-23 21:49         ` Junio C Hamano
@ 2018-07-26  9:47         ` Johannes Schindelin
  2018-08-08 13:05         ` Johannes Schindelin
  2 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-26  9:47 UTC (permalink / raw)
  To: Stefan Beller; +Cc: gitgitgadget, git, Junio C Hamano

Hi Stefan,

On Mon, 23 Jul 2018, Stefan Beller wrote:

> On Sat, Jul 21, 2018 at 3:04 PM Johannes Schindelin via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
> 
> > Range-diff vs v3:
> >
> >   1:  39272eefc !  1:  f7e70689e linear-assignment: a function to solve least-cost assignment problems
> >      @@ -223,9 +223,7 @@
> >       +                         BUG("negative j: %d", j);
> >       +                 i = pred[j];
> >       +                 column2row[j] = i;
> >      -+                 k = j;
> >      -+                 j = row2column[i];
> >      -+                 row2column[i] = k;
> >      ++                 SWAP(j, row2column[i]);
> 
> The dual color option (as a default) really helps here. Thanks for that!
> Does it have to be renamed though? (It's more than two colors; originally
> it was inverting the beginning signs)

I understand (and understood) the "dual" to mean that there are two
separate coloring methods, the coloring of the inner, and the coloring of
the outer diff. (And in my mind, the dimming is not so much an "inner"
diff things as an "outer" diff thing.)

> Maybe --color=emphasize-later assuming there will be other modes for
> coloring, such as "diff-only", which would correspond with
> --no-dual-color, or "none" that will correspond would be --no-color. I
> imagine there could be more fancy things, hence I would propose a mode
> rather than a switch.  (Feel free to push back if discussing naming here
> feels like bike shedding)

Your suggestion does not really feel like bike-shedding to me, here, I can
see the merit of it.

It's just that 1) overloading `--color` here would be cumbersome, as
`--color` is *already* a diff option that we actually use, and 2) I am not
all that certain that new fancy things will crop up anytime soon. It was
hard enough to think of the dimming feature, and then implementing it.

So while I think your idea has merit, I still think that we can do that
later. The --no-dual-color option can easily be deprecated in favor of,
say, --color-mode=<mode>, when (and if) new modes crop up.

> 2:  7f15b26d4ea !  82:  88134121d2a Introduce `range-diff` to compare
> iterations of a topic branch
> [...]
> >       diff --git a/Makefile b/Makefile
> >       --- a/Makefile
> >       +++ b/Makefile
> 
> The line starting with --- is red (default removed color) and the line
> with +++ is green (default add color).
> 
> Ideally these two lines and the third line above starting with "diff --git"
> would render in GIT_COLOR_BOLD ("METAINFO").

I see where you are coming from, but given how complicated it seems to me
to implement this (dual color mode gets away with working line-based for
the moment, and what you ask for would require state, and would not even
be fool-proof, as the `diff --git` line might not even be part of the
context.

Seeing how long this patch series has already simmered, I would want to
invoke that old adage "the perfect is the enemy of the good", and rather
see a version of range-diff enter Git's source code, if need be marked as
"EXPERIMENTAL" so that the maintainer can claim that it is okay to be
buggy, and then invite contributions from other sides than from me.

> >  16:  dfa7b1e71 <  -:  --------- range-diff --dual-color: work around bogus white-space warning
> >   -:  --------- > 16:  f4252f2b2 range-diff --dual-color: fix bogus white-space warning
> 
> Ah; here my initial assumption of only reviewing the range-diff breaks down now.
> I'll dig into patch 16 separately.

Right. In this case, it is a total rewrite, anyway (and I'll have to ask
you to overlook my frustration with how complex and hard it is to work on
ws.c without breaking anything). For the sake of review, you should ignore
the old patch. Unless you find that the new version is so complex and
prone to introduce bugs (with which I would agree) that we should go back
to the original workaround, which is so easy to understand that there are
no obvious bugs lurking inside it.

> Maybe it is worth having an option to expand all "new" patches.

Sure. And I would love to have this in a separate patch series, as it is
well beyond the original purpose of this patch series to simply make
tbdiff a builtin.

I should have known better, of course, but I was really not keen on
improving range-diff *before* it enters the code base, to the point of
introducing new features that might very well introduce new regressions in
unrelated commands, too.

Essentially, I am declaring a feature-freeze on this patch series until it
enters `next`.

> (Given that the range-diff
> pr-1/dscho/branch-diff-v3...pr-1/dscho/branch-diff-v4 told me you used a
> different base, this is a hard problem, as I certainly would want to
> skip over all new base commits, but this one is interesting to look at.
> An easy way out: Maybe an option to expand any new commits/patches after
> the first expansion? Asking for opinions rather than implementing it)

Any fulfilled wish is immediately welcomed with offspring, it seems.

Again, this is a very nice feature, I think, and even nicer: it can be
implemented by somebody else than me, on top of my patch series, after it
stabilized and entered `next`.

> >  19:  144363006 <  -:  --------- range-diff: left-pad patch numbers
> >   -:  --------- > 19:  07ec215e8 range-diff: left-pad patch numbers

Yes, this is something where I would have used a different
`--creation-factor` locally, but I did not want to hack up GitGitGadget
*just* for this patch series.

> >   -:  --------- > 21:  d8498fb32 range-diff: use dim/bold cues to improve dual color mode
> 
> Those are interesting, I'll look at them separately, too.

That last one is indeed very interesting, from a feature point of view,
and a little intimidating from a review point of view: it entered the
patch series only in v4. Combined, this is a clear sign that I should not
have included that feature in this here patch series. But I did fall prey
to "featuritis". I'll try to be better about this. The most important
thing now is to stabilize the `range-diff` command and to get it included.
It already simmers for way too long.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 01/21] linear-assignment: a function to solve least-cost assignment problems
  2018-07-21 22:04       ` [PATCH v4 01/21] linear-assignment: a function to solve least-cost assignment problems Johannes Schindelin via GitGitGadget
@ 2018-07-28  8:46         ` Thomas Gummerer
  2018-07-30 15:59           ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Thomas Gummerer @ 2018-07-28  8:46 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: git, Junio C Hamano, Johannes Schindelin

On 07/21, Johannes Schindelin via GitGitGadget wrote:
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
> 
> The problem solved by the code introduced in this commit goes like this:
> given two sets of items, and a cost matrix which says how much it
> "costs" to assign any given item of the first set to any given item of
> the second, assign all items (except when the sets have different size)
> in the cheapest way.
> 
> We use the Jonker-Volgenant algorithm to solve the assignment problem to
> answer questions such as: given two different versions of a topic branch
> (or iterations of a patch series), what is the best pairing of
> commits/patches between the different versions?
> 
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  Makefile            |   1 +
>  linear-assignment.c | 201 ++++++++++++++++++++++++++++++++++++++++++++
>  linear-assignment.h |  22 +++++
>  3 files changed, 224 insertions(+)
>  create mode 100644 linear-assignment.c
>  create mode 100644 linear-assignment.h
>
> [...]
> 
> diff --git a/linear-assignment.h b/linear-assignment.h
> new file mode 100644
> index 000000000..fc4c502c8
> --- /dev/null
> +++ b/linear-assignment.h
> @@ -0,0 +1,22 @@
> +#ifndef HUNGARIAN_H
> +#define HUNGARIAN_H

nit: maybe s/HUNGARIAN/LINEAR_ASSIGNMENT/ in the two lines above.

> +
> +/*
> + * Compute an assignment of columns -> rows (and vice versa) such that every
> + * column is assigned to at most one row (and vice versa) minimizing the
> + * overall cost.
> + *
> + * The parameter `cost` is the cost matrix: the cost to assign column j to row
> + * i is `cost[j + column_count * i].
> + *
> + * The arrays column2row and row2column will be populated with the respective
> + * assignments (-1 for unassigned, which can happen only if column_count !=
> + * row_count).
> + */
> +void compute_assignment(int column_count, int row_count, int *cost,
> +			int *column2row, int *row2column);
> +
> +/* The maximal cost in the cost matrix (to prevent integer overflows). */
> +#define COST_MAX (1<<16)
> +
> +#endif
> -- 
> gitgitgadget
> 

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 03/21] range-diff: first rudimentary implementation
  2018-07-21 22:04       ` [PATCH v4 03/21] range-diff: first rudimentary implementation Johannes Schindelin via GitGitGadget
@ 2018-07-29 18:36         ` Thomas Gummerer
  2018-07-30 16:21           ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Thomas Gummerer @ 2018-07-29 18:36 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: git, Junio C Hamano, Johannes Schindelin

On 07/21, Johannes Schindelin via GitGitGadget wrote:
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
> 
> At this stage, `git range-diff` can determine corresponding commits
> of two related commit ranges. This makes use of the recently introduced
> implementation of the linear assignment algorithm.
> 
> The core of this patch is a straight port of the ideas of tbdiff, the
> apparently dormant project at https://github.com/trast/tbdiff.
> 
> The output does not at all match `tbdiff`'s output yet, as this patch
> really concentrates on getting the patch matching part right.
> 
> Note: due to differences in the diff algorithm (`tbdiff` uses the Python
> module `difflib`, Git uses its xdiff fork), the cost matrix calculated
> by `range-diff` is different (but very similar) to the one calculated
> by `tbdiff`. Therefore, it is possible that they find different matching
> commits in corner cases (e.g. when a patch was split into two patches of
> roughly equal length).
> 
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  Makefile             |   1 +
>  builtin/range-diff.c |  43 +++++-
>  range-diff.c         | 311 +++++++++++++++++++++++++++++++++++++++++++
>  range-diff.h         |   7 +
>  4 files changed, 361 insertions(+), 1 deletion(-)
>  create mode 100644 range-diff.c
>  create mode 100644 range-diff.h
>
> [...]
> 
> diff --git a/range-diff.c b/range-diff.c
> new file mode 100644
> index 000000000..15d418afa
> --- /dev/null
> +++ b/range-diff.c
> @@ -0,0 +1,311 @@
> +#include "cache.h"
> +#include "range-diff.h"
> +#include "string-list.h"
> +#include "run-command.h"
> +#include "argv-array.h"
> +#include "hashmap.h"
> +#include "xdiff-interface.h"
> +#include "linear-assignment.h"
> +
> +struct patch_util {
> +	/* For the search for an exact match */
> +	struct hashmap_entry e;
> +	const char *diff, *patch;
> +
> +	int i;
> +	int diffsize;
> +	size_t diff_offset;
> +	/* the index of the matching item in the other branch, or -1 */
> +	int matching;
> +	struct object_id oid;
> +};
> +
> +/*
> + * Reads the patches into a string list, with the `util` field being populated
> + * as struct object_id (will need to be free()d).
> + */
> +static int read_patches(const char *range, struct string_list *list)
> +{
> +	struct child_process cp = CHILD_PROCESS_INIT;
> +	FILE *in;
> +	struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;
> +	struct patch_util *util = NULL;
> +	int in_header = 1;
> +
> +	argv_array_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
> +			"--reverse", "--date-order", "--decorate=no",
> +			"--no-abbrev-commit", range,
> +			NULL);

Compared to tbdiff, add "--decorate=no", and "--no-abbrev-commit".  I
see we're abbreviating the commit hashes later.  We don't want ref
names here, so "--decorate=no" makes sense as well.

> +	cp.out = -1;
> +	cp.no_stdin = 1;
> +	cp.git_cmd = 1;
> +
> +	if (start_command(&cp))
> +		return error_errno(_("could not start `log`"));
> +	in = fdopen(cp.out, "r");
> +	if (!in) {
> +		error_errno(_("could not read `log` output"));
> +		finish_command(&cp);
> +		return -1;
> +	}
> +
> +	while (strbuf_getline(&line, in) != EOF) {
> +		const char *p;
> +
> +		if (skip_prefix(line.buf, "commit ", &p)) {
> +			if (util) {
> +				string_list_append(list, buf.buf)->util = util;
> +				strbuf_reset(&buf);
> +			}
> +			util = xcalloc(sizeof(*util), 1);
> +			if (get_oid(p, &util->oid)) {
> +				error(_("could not parse commit '%s'"), p);
> +				free(util);
> +				string_list_clear(list, 1);
> +				strbuf_release(&buf);
> +				strbuf_release(&line);
> +				fclose(in);
> +				finish_command(&cp);
> +				return -1;
> +			}
> +			util->matching = -1;
> +			in_header = 1;
> +			continue;
> +		}
> +
> +		if (starts_with(line.buf, "diff --git")) {
> +			in_header = 0;
> +			strbuf_addch(&buf, '\n');
> +			if (!util->diff_offset)
> +				util->diff_offset = buf.len;
> +			strbuf_addbuf(&buf, &line);
> +		} else if (in_header) {
> +			if (starts_with(line.buf, "Author: ")) {
> +				strbuf_addbuf(&buf, &line);
> +				strbuf_addstr(&buf, "\n\n");
> +			} else if (starts_with(line.buf, "    ")) {
> +				strbuf_addbuf(&buf, &line);
> +				strbuf_addch(&buf, '\n');
> +			}
> +			continue;
> +		} else if (starts_with(line.buf, "@@ "))
> +			strbuf_addstr(&buf, "@@");
> +		else if (!line.buf[0] || starts_with(line.buf, "index "))
> +			/*
> +			 * A completely blank (not ' \n', which is context)
> +			 * line is not valid in a diff.  We skip it
> +			 * silently, because this neatly handles the blank
> +			 * separator line between commits in git-log
> +			 * output.
> +			 *
> +			 * We also want to ignore the diff's `index` lines
> +			 * because they contain exact blob hashes in which
> +			 * we are not interested.
> +			 */
> +			continue;
> +		else
> +			strbuf_addbuf(&buf, &line);
> +
> +		strbuf_addch(&buf, '\n');
> +		util->diffsize++;
> +	}

This seems to differ from tbdiff in the number of newlines we're
adding in various places, however I think as long as it's consistent
in itself, and with the way we're printing the output the differences
shouldn't matter.

> +	fclose(in);
> +	strbuf_release(&line);
> +
> +	if (util)
> +		string_list_append(list, buf.buf)->util = util;
> +	strbuf_release(&buf);
> +
> +	if (finish_command(&cp))
> +		return -1;
> +
> +	return 0;
> +}
> +
> +static int patch_util_cmp(const void *dummy, const struct patch_util *a,
> +		     const struct patch_util *b, const char *keydata)
> +{
> +	return strcmp(a->diff, keydata ? keydata : b->diff);
> +}
> +
> +static void find_exact_matches(struct string_list *a, struct string_list *b)
> +{
> +	struct hashmap map;
> +	int i;
> +
> +	hashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);
> +
> +	/* First, add the patches of a to a hash map */
> +	for (i = 0; i < a->nr; i++) {
> +		struct patch_util *util = a->items[i].util;
> +
> +		util->i = i;
> +		util->patch = a->items[i].string;
> +		util->diff = util->patch + util->diff_offset;
> +		hashmap_entry_init(util, strhash(util->diff));
> +		hashmap_add(&map, util);
> +	}
> +
> +	/* Now try to find exact matches in b */
> +	for (i = 0; i < b->nr; i++) {
> +		struct patch_util *util = b->items[i].util, *other;
> +
> +		util->i = i;
> +		util->patch = b->items[i].string;
> +		util->diff = util->patch + util->diff_offset;
> +		hashmap_entry_init(util, strhash(util->diff));
> +		other = hashmap_remove(&map, util, NULL);
> +		if (other) {
> +			if (other->matching >= 0)
> +				BUG("already assigned!");
> +
> +			other->matching = i;
> +			util->matching = other->i;
> +		}
> +	}

One possibly interesting corner case here is what happens when there
are two patches that have the exact same diff, for example in the
pathological case of commit A doing something, commit B reverting
commit A, and then commit C reverting commit B, so it ends up with the
same diff.

Having those same commits unchanged in both ranges (e.g. if a commit
earlier in the range has been changed, and range B has been rebased on
top of that), we'd get the following mapping from range A to range B
for the commits in question:

A -> C
B -> B
C -> A

Which is not quite what I would expect as the user (even though it is
a valid mapping, and it probably doesn't matter too much for the end
result of the range diff, as nothing has changed between the commits
anyway).  So I'm not sure it's worth fixing this, as it is a
pathological case, and nothing really breaks.

> +
> +	hashmap_free(&map, 0);
> +}
> +
> +static void diffsize_consume(void *data, char *line, unsigned long len)
> +{
> +	(*(int *)data)++;
> +}
> +
> +static int diffsize(const char *a, const char *b)
> +{
> +	xpparam_t pp = { 0 };
> +	xdemitconf_t cfg = { 0 };
> +	mmfile_t mf1, mf2;
> +	int count = 0;
> +
> +	mf1.ptr = (char *)a;
> +	mf1.size = strlen(a);
> +	mf2.ptr = (char *)b;
> +	mf2.size = strlen(b);
> +
> +	cfg.ctxlen = 3;
> +	if (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))
> +		return count;
> +
> +	error(_("failed to generate diff"));
> +	return COST_MAX;
> +}
> +
> +static void get_correspondences(struct string_list *a, struct string_list *b,
> +				int creation_factor)
> +{
> +	int n = a->nr + b->nr;
> +	int *cost, c, *a2b, *b2a;
> +	int i, j;
> +
> +	ALLOC_ARRAY(cost, st_mult(n, n));
> +	ALLOC_ARRAY(a2b, n);
> +	ALLOC_ARRAY(b2a, n);
> +
> +	for (i = 0; i < a->nr; i++) {
> +		struct patch_util *a_util = a->items[i].util;
> +
> +		for (j = 0; j < b->nr; j++) {
> +			struct patch_util *b_util = b->items[j].util;
> +
> +			if (a_util->matching == j)
> +				c = 0;
> +			else if (a_util->matching < 0 && b_util->matching < 0)
> +				c = diffsize(a_util->diff, b_util->diff);
> +			else
> +				c = COST_MAX;
> +			cost[i + n * j] = c;
> +		}
> +
> +		c = a_util->matching < 0 ?
> +			a_util->diffsize * creation_factor / 100 : COST_MAX;
> +		for (j = b->nr; j < n; j++)
> +			cost[i + n * j] = c;
> +	}
> +
> +	for (j = 0; j < b->nr; j++) {
> +		struct patch_util *util = b->items[j].util;
> +
> +		c = util->matching < 0 ?
> +			util->diffsize * creation_factor / 100 : COST_MAX;
> +		for (i = a->nr; i < n; i++)
> +			cost[i + n * j] = c;
> +	}
> +
> +	for (i = a->nr; i < n; i++)
> +		for (j = b->nr; j < n; j++)
> +			cost[i + n * j] = 0;
> +
> +	compute_assignment(n, n, cost, a2b, b2a);
> +
> +	for (i = 0; i < a->nr; i++)
> +		if (a2b[i] >= 0 && a2b[i] < b->nr) {
> +			struct patch_util *a_util = a->items[i].util;
> +			struct patch_util *b_util = b->items[a2b[i]].util;
> +
> +			a_util->matching = a2b[i];
> +			b_util->matching = i;

So here we re-assign 'matching' in the struct regardless of whether it
was assigned before while searching for exact matches or not.

Shouldn't diffsize for matching patches also be 0?  So are we doing
the 'find_exact_matches()' bit only as an optimization, or am I
missing some other reason why that is beneficial?

> +		}
> +
> +	free(cost);
> +	free(a2b);
> +	free(b2a);
> +}
> +
> +static const char *short_oid(struct patch_util *util)
> +{
> +	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
> +}
> +
> +static void output(struct string_list *a, struct string_list *b)
> +{
> +	int i;
> +
> +	for (i = 0; i < b->nr; i++) {
> +		struct patch_util *util = b->items[i].util, *prev;
> +
> +		if (util->matching < 0)
> +			printf("-: -------- > %d: %s\n",
> +					i + 1, short_oid(util));
> +		else {
> +			prev = a->items[util->matching].util;
> +			printf("%d: %s ! %d: %s\n",
> +			       util->matching + 1, short_oid(prev),
> +			       i + 1, short_oid(util));
> +		}
> +	}
> +
> +	for (i = 0; i < a->nr; i++) {
> +		struct patch_util *util = a->items[i].util;
> +
> +		if (util->matching < 0)
> +			printf("%d: %s < -: --------\n",
> +			       i + 1, short_oid(util));
> +	}
> +}
> +
> +int show_range_diff(const char *range1, const char *range2,
> +		    int creation_factor)
> +{
> +	int res = 0;
> +
> +	struct string_list branch1 = STRING_LIST_INIT_DUP;
> +	struct string_list branch2 = STRING_LIST_INIT_DUP;
> +
> +	if (read_patches(range1, &branch1))
> +		res = error(_("could not parse log for '%s'"), range1);
> +	if (!res && read_patches(range2, &branch2))
> +		res = error(_("could not parse log for '%s'"), range2);
> +
> +	if (!res) {
> +		find_exact_matches(&branch1, &branch2);

Note to self: here we assign the matching member of struct patch_util
for each patch in both ranges to a patch number in the other range if
it is an exact match.

We also assign the patch and diff members, and number the patches
using the 'i' member of struct patch_util.  Let's see if that
numbering is still useful later.

> +		get_correspondences(&branch1, &branch2, creation_factor);

And here we use the linear assignment algorithm to match the rest of
the commits.

> +		output(&branch1, &branch2);

And finally we print the output.  We don't seem to use the util->i
that's assigned for range b (or range 2) anywhere at the moment, which
I was wondering about earlier, so I assume it's there mainly for
symmetry, but it doesn't really hurt other than me wondering what it
was for.

> +	}
> +
> +	string_list_clear(&branch1, 1);
> +	string_list_clear(&branch2, 1);
> +
> +	return res;
> +}
> diff --git a/range-diff.h b/range-diff.h
> new file mode 100644
> index 000000000..dd30449c4
> --- /dev/null
> +++ b/range-diff.h
> @@ -0,0 +1,7 @@
> +#ifndef BRANCH_DIFF_H
> +#define BRANCH_DIFF_H

s/BRANCH/RANGE/ above?

> +int show_range_diff(const char *range1, const char *range2,
> +		    int creation_factor);
> +
> +#endif
> -- 
> gitgitgadget
> 

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 05/21] range-diff: also show the diff between patches
  2018-07-21 22:04       ` [PATCH v4 05/21] range-diff: also show the diff between patches Johannes Schindelin via GitGitGadget
@ 2018-07-29 19:03         ` Thomas Gummerer
  2018-07-29 19:22           ` Eric Sunshine
  0 siblings, 1 reply; 387+ messages in thread
From: Thomas Gummerer @ 2018-07-29 19:03 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: git, Junio C Hamano, Johannes Schindelin

On 07/21, Johannes Schindelin via GitGitGadget wrote:
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
> 
> Just like tbdiff, we now show the diff between matching patches. This is
> a "diff of two diffs", so it can be a bit daunting to read for the
> beginner.
> 
> An alternative would be to display an interdiff, i.e. the hypothetical
> diff which is the result of first reverting the old diff and then
> applying the new diff.
> 
> Especially when rebasing often, an interdiff is often not feasible,
> though: if the old diff cannot be applied in reverse (due to a moving
> upstream), an interdiff can simply not be inferred.
> 
> This commit brings `range-diff` closer to feature parity with regard
> to tbdiff.
> 
> To make `git range-diff` respect e.g. color.diff.* settings, we have
> to adjust git_branch_config() accordingly.
> 
> Note: while we now parse diff options such as --color, the effect is not
> yet the same as in tbdiff, where also the commit pairs would be colored.
> This is left for a later commit.
> 
> Note also: while tbdiff accepts the `--no-patches` option to suppress
> these diffs between patches, we prefer the `-s` option that is
> automatically supported via our use of diff_opt_parse().

One slightly unfortunate thing here is that we don't show these
options in 'git range-diff -h', which would be nice to have.  I don't
know if that's possible in git right now, if it's not easily possible,
I definitely wouldn't want to delay this series for that, and we could
just add it to the list of possible future enhancements that other
people mentioned.

> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  builtin/range-diff.c | 25 ++++++++++++++++++++++---
>  range-diff.c         | 34 +++++++++++++++++++++++++++++++---
>  range-diff.h         |  4 +++-
>  3 files changed, 56 insertions(+), 7 deletions(-)
>
> [...]
> 
> diff --git a/range-diff.c b/range-diff.c
> index 2d94200d3..71883a4b7 100644
> --- a/range-diff.c
> +++ b/range-diff.c
> @@ -6,6 +6,7 @@
>  #include "hashmap.h"
>  #include "xdiff-interface.h"
>  #include "linear-assignment.h"
> +#include "diffcore.h"
>  
>  struct patch_util {
>  	/* For the search for an exact match */
> @@ -258,7 +259,31 @@ static const char *short_oid(struct patch_util *util)
>  	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
>  }
>  
> -static void output(struct string_list *a, struct string_list *b)
> +static struct diff_filespec *get_filespec(const char *name, const char *p)
> +{
> +	struct diff_filespec *spec = alloc_filespec(name);
> +
> +	fill_filespec(spec, &null_oid, 0, 0644);
> +	spec->data = (char *)p;
> +	spec->size = strlen(p);
> +	spec->should_munmap = 0;
> +	spec->is_stdin = 1;
> +
> +	return spec;
> +}
> +
> +static void patch_diff(const char *a, const char *b,
> +			      struct diff_options *diffopt)
> +{
> +	diff_queue(&diff_queued_diff,
> +		   get_filespec("a", a), get_filespec("b", b));
> +
> +	diffcore_std(diffopt);
> +	diff_flush(diffopt);
> +}
> +
> +static void output(struct string_list *a, struct string_list *b,
> +		   struct diff_options *diffopt)
>  {
>  	int i = 0, j = 0;
>  
> @@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)
>  			printf("%d: %s ! %d: %s\n",
>  			       b_util->matching + 1, short_oid(a_util),
>  			       j + 1, short_oid(b_util));
> +			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))

Looking at this line, it looks like it would be easy to support
'--no-patches' as well, which may be slightly easier to understand that
'-s' to someone new to the command.  But again that can be added later
if someone actually cares about it.

> +				patch_diff(a->items[b_util->matching].string,
> +					   b->items[j].string, diffopt);
>  			a_util->shown = 1;
>  			j++;
>  		}
> @@ -307,7 +335,7 @@ static void output(struct string_list *a, struct string_list *b)
>  }
>  
>  int show_range_diff(const char *range1, const char *range2,
> -		    int creation_factor)
> +		    int creation_factor, struct diff_options *diffopt)
>  {
>  	int res = 0;
>  
> @@ -322,7 +350,7 @@ int show_range_diff(const char *range1, const char *range2,
>  	if (!res) {
>  		find_exact_matches(&branch1, &branch2);
>  		get_correspondences(&branch1, &branch2, creation_factor);
> -		output(&branch1, &branch2);
> +		output(&branch1, &branch2, diffopt);
>  	}
>  
>  	string_list_clear(&branch1, 1);
> diff --git a/range-diff.h b/range-diff.h
> index dd30449c4..aea9d43f3 100644
> --- a/range-diff.h
> +++ b/range-diff.h
> @@ -1,7 +1,9 @@
>  #ifndef BRANCH_DIFF_H
>  #define BRANCH_DIFF_H
>  
> +#include "diff.h"
> +
>  int show_range_diff(const char *range1, const char *range2,
> -		    int creation_factor);
> +		    int creation_factor, struct diff_options *diffopt);
>  
>  #endif
> -- 
> gitgitgadget
> 

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 05/21] range-diff: also show the diff between patches
  2018-07-29 19:03         ` Thomas Gummerer
@ 2018-07-29 19:22           ` Eric Sunshine
  2018-07-29 21:45             ` Thomas Gummerer
  0 siblings, 1 reply; 387+ messages in thread
From: Eric Sunshine @ 2018-07-29 19:22 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: gitgitgadget, Git List, Junio C Hamano, Johannes Schindelin

On Sun, Jul 29, 2018 at 3:04 PM Thomas Gummerer <t.gummerer@gmail.com> wrote:
> On 07/21, Johannes Schindelin via GitGitGadget wrote:
> > Just like tbdiff, we now show the diff between matching patches. This is
> > a "diff of two diffs", so it can be a bit daunting to read for the
> > beginner.
> > [...]
> > Note also: while tbdiff accepts the `--no-patches` option to suppress
> > these diffs between patches, we prefer the `-s` option that is
> > automatically supported via our use of diff_opt_parse().
>
> One slightly unfortunate thing here is that we don't show these
> options in 'git range-diff -h', which would be nice to have.  I don't
> know if that's possible in git right now, if it's not easily possible,
> I definitely wouldn't want to delay this series for that, and we could
> just add it to the list of possible future enhancements that other
> people mentioned.

This issue is not specific to git-range-diff; it's shared by other
commands which inherit diff options via diff_opt_parse(). For
instance, "git log -h" doesn't show diff-related options either, yet
it accepts them.

> > diff --git a/range-diff.c b/range-diff.c
> > @@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)
> >                       printf("%d: %s ! %d: %s\n",
> >                              b_util->matching + 1, short_oid(a_util),
> >                              j + 1, short_oid(b_util));
> > +                     if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
>
> Looking at this line, it looks like it would be easy to support
> '--no-patches' as well, which may be slightly easier to understand that
> '-s' to someone new to the command.  But again that can be added later
> if someone actually cares about it.

What wasn't mentioned (but was implied) by the commit message is that
"-s" is short for "--no-patch", which also comes for free via
diff_opt_parse(). True, "--no-patch" isn't spelled exactly the same as
"--no-patches", but git-range-diff isn't exactly a perfect tbdiff
clone, so hopefully not a git problem. Moreover, "--no-patch" is
internally consistent within the Git builtin commands.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 09/21] range-diff: adjust the output of the commit pairs
  2018-07-21 22:04       ` [PATCH v4 09/21] range-diff: adjust the output of the commit pairs Johannes Schindelin via GitGitGadget
@ 2018-07-29 19:38         ` Thomas Gummerer
  2018-08-10 21:01           ` Johannes Schindelin
  2018-07-29 21:28         ` Thomas Gummerer
  1 sibling, 1 reply; 387+ messages in thread
From: Thomas Gummerer @ 2018-07-29 19:38 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: git, Junio C Hamano, Johannes Schindelin

On 07/21, Johannes Schindelin via GitGitGadget wrote:
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
> 
> This change brings `git range-diff` yet another step closer to
> feature parity with tbdiff: it now shows the oneline, too, and indicates
> with `=` when the commits have identical diffs.
> 
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  range-diff.c | 64 ++++++++++++++++++++++++++++++++++++++++++++--------
>  1 file changed, 55 insertions(+), 9 deletions(-)
> 
> diff --git a/range-diff.c b/range-diff.c
> index 1ecee2c09..8329f52e7 100644
> --- a/range-diff.c
> +++ b/range-diff.c
> @@ -7,6 +7,8 @@
>  #include "xdiff-interface.h"
>  #include "linear-assignment.h"
>  #include "diffcore.h"
> +#include "commit.h"
> +#include "pretty.h"
>  
>  struct patch_util {
>  	/* For the search for an exact match */
> @@ -255,9 +257,54 @@ static void get_correspondences(struct string_list *a, struct string_list *b,
>  	free(b2a);
>  }
>  
> -static const char *short_oid(struct patch_util *util)
> +static void output_pair_header(struct strbuf *buf,
> +			       struct strbuf *dashes,
> +			       struct patch_util *a_util,
> +			       struct patch_util *b_util)
>  {
> -	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
> +	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
> +	struct commit *commit;
> +
> +	if (!dashes->len)
> +		strbuf_addchars(dashes, '-',
> +				strlen(find_unique_abbrev(oid,
> +							  DEFAULT_ABBREV)));

We're doing this only once, which makes sense.  What's a bit
unfortunate here I guess is that if the first commit we're dealing
with in the range-diff has a longer unique abbreviation, the dashes
will be longer for all commits, even if all the others have a shorter
abbreviation.

Tbh I don't really know what the right thing to do here is, so this is
probably as good a heuristic as any.  It would probably be worse to
have different length dashes lines, than guessing based on the first
commit.

> +
> +	strbuf_reset(buf);
> +	if (!a_util)
> +		strbuf_addf(buf, "-:  %s ", dashes->buf);
> +	else
> +		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
> +			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
> +
> +	if (!a_util)
> +		strbuf_addch(buf, '>');
> +	else if (!b_util)
> +		strbuf_addch(buf, '<');
> +	else if (strcmp(a_util->patch, b_util->patch))
> +		strbuf_addch(buf, '!');
> +	else
> +		strbuf_addch(buf, '=');
> +
> +	if (!b_util)
> +		strbuf_addf(buf, " -:  %s", dashes->buf);
> +	else
> +		strbuf_addf(buf, " %d:  %s", b_util->i + 1,
> +			    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));
> +
> +	commit = lookup_commit_reference(oid);

This bit surprised me slightly.  May be worth mentioning that we now
also show the first line of the commit message here.

> +	if (commit) {
> +		const char *commit_buffer = get_commit_buffer(commit, NULL);
> +		const char *subject;
> +
> +		find_commit_subject(commit_buffer, &subject);
> +		strbuf_addch(buf, ' ');
> +		format_subject(buf, subject, " ");
> +		unuse_commit_buffer(commit, commit_buffer);

I think the above could be written slightly shorter as

    strbuf_addch(buf, ' ');
    pp_commit_easy(CMIT_FMT_ONELINE, commit, &buf);

Not sure if it's worth changing this at this stage of the series
though, or if there is something in the above that I'm missing, that
would make the shorter version not workable.

> +	}
> +	strbuf_addch(buf, '\n');
> +
> +	fwrite(buf->buf, buf->len, 1, stdout);
>  }
>  
>  static struct diff_filespec *get_filespec(const char *name, const char *p)
> @@ -286,6 +333,7 @@ static void patch_diff(const char *a, const char *b,
>  static void output(struct string_list *a, struct string_list *b,
>  		   struct diff_options *diffopt)
>  {
> +	struct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;
>  	int i = 0, j = 0;
>  
>  	/*
> @@ -307,25 +355,21 @@ static void output(struct string_list *a, struct string_list *b,
>  
>  		/* Show unmatched LHS commit whose predecessors were shown. */
>  		if (i < a->nr && a_util->matching < 0) {
> -			printf("%d: %s < -: --------\n",
> -			       i + 1, short_oid(a_util));
> +			output_pair_header(&buf, &dashes, a_util, NULL);
>  			i++;
>  			continue;
>  		}
>  
>  		/* Show unmatched RHS commits. */
>  		while (j < b->nr && b_util->matching < 0) {
> -			printf("-: -------- > %d: %s\n",
> -			       j + 1, short_oid(b_util));
> +			output_pair_header(&buf, &dashes, NULL, b_util);
>  			b_util = ++j < b->nr ? b->items[j].util : NULL;
>  		}
>  
>  		/* Show matching LHS/RHS pair. */
>  		if (j < b->nr) {
>  			a_util = a->items[b_util->matching].util;
> -			printf("%d: %s ! %d: %s\n",
> -			       b_util->matching + 1, short_oid(a_util),
> -			       j + 1, short_oid(b_util));
> +			output_pair_header(&buf, &dashes, a_util, b_util);
>  			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
>  				patch_diff(a->items[b_util->matching].string,
>  					   b->items[j].string, diffopt);
> @@ -333,6 +377,8 @@ static void output(struct string_list *a, struct string_list *b,
>  			j++;
>  		}
>  	}
> +	strbuf_release(&buf);
> +	strbuf_release(&dashes);
>  }
>  
>  int show_range_diff(const char *range1, const char *range2,
> -- 
> gitgitgadget
> 

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 10/21] range-diff: do not show "function names" in hunk headers
  2018-07-21 22:04       ` [PATCH v4 10/21] range-diff: do not show "function names" in hunk headers Johannes Schindelin via GitGitGadget
@ 2018-07-29 20:52         ` Thomas Gummerer
  2018-08-10 21:03           ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Thomas Gummerer @ 2018-07-29 20:52 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: git, Junio C Hamano, Johannes Schindelin

On 07/21, Johannes Schindelin via GitGitGadget wrote:
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
> 
> We are comparing complete, formatted commit messages with patches. There
> are no function names here, so stop looking for them.

While there are no function names here, trying out range-diff without
this patch applied, the headers were getting here do seem kind of
useful:

    1: 92588fc6b6 ! 3: 43c9ef552c
        @@ -8,8 +8,16 @@ diff --git a/read-cache.c b/read-cache.c
    	[...]

The filename can be quite useful in this output.  I guess this is a
bit brittle though, so I'm also happy to defer changing this to show
something useful to the list of possible future enhancements
(obviously doesn't necessarily have to be implemented by you at that
point).

> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  range-diff.c | 6 ++++++
>  1 file changed, 6 insertions(+)
> 
> diff --git a/range-diff.c b/range-diff.c
> index 8329f52e7..3fc3a4018 100644
> --- a/range-diff.c
> +++ b/range-diff.c
> @@ -9,6 +9,7 @@
>  #include "diffcore.h"
>  #include "commit.h"
>  #include "pretty.h"
> +#include "userdiff.h"
>  
>  struct patch_util {
>  	/* For the search for an exact match */
> @@ -307,6 +308,10 @@ static void output_pair_header(struct strbuf *buf,
>  	fwrite(buf->buf, buf->len, 1, stdout);
>  }
>  
> +static struct userdiff_driver no_func_name = {
> +	.funcname = { "$^", 0 }
> +};
> +
>  static struct diff_filespec *get_filespec(const char *name, const char *p)
>  {
>  	struct diff_filespec *spec = alloc_filespec(name);
> @@ -316,6 +321,7 @@ static struct diff_filespec *get_filespec(const char *name, const char *p)
>  	spec->size = strlen(p);
>  	spec->should_munmap = 0;
>  	spec->is_stdin = 1;
> +	spec->driver = &no_func_name;
>  
>  	return spec;
>  }
> -- 
> gitgitgadget
> 

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 17/21] range-diff: populate the man page
  2018-07-21 22:05       ` [PATCH v4 17/21] range-diff: populate the man page Johannes Schindelin via GitGitGadget
@ 2018-07-29 21:23         ` Thomas Gummerer
  2018-08-10 21:06           ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Thomas Gummerer @ 2018-07-29 21:23 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: git, Junio C Hamano, Johannes Schindelin

On 07/21, Johannes Schindelin via GitGitGadget wrote:
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
> 
> The bulk of this patch consists of a heavily butchered version of
> tbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from

Thanks for the mention here, but this was really mostly Thomas Rast's
writing.  My contributions here are very minor compared to his :)

> https://github.com/trast/tbdiff.
> 
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  Documentation/git-range-diff.txt | 229 +++++++++++++++++++++++++++++++
>  1 file changed, 229 insertions(+)
> 
> [...]
>
> +CONFIGURATION
> +-------------
> +This command uses the `diff.color.*` and `pager.range-diff` settings
> +(the latter is on by default).
> +See linkgit:git-config[1].

Would it be worth implementing a `rangeDiff.dualColor` configuration
at some point?  Dual color mode seems like something I would like to
have on by default, even if we are not making it the default for the
command itself.

(Again this is something that can be a future enhancement).

> +EXAMPLES
> +--------
> [...]
> -- 
> gitgitgadget
> 

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 09/21] range-diff: adjust the output of the commit pairs
  2018-07-21 22:04       ` [PATCH v4 09/21] range-diff: adjust the output of the commit pairs Johannes Schindelin via GitGitGadget
  2018-07-29 19:38         ` Thomas Gummerer
@ 2018-07-29 21:28         ` Thomas Gummerer
  1 sibling, 0 replies; 387+ messages in thread
From: Thomas Gummerer @ 2018-07-29 21:28 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: git, Junio C Hamano, Johannes Schindelin

On 07/21, Johannes Schindelin via GitGitGadget wrote:
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
> 
> This change brings `git range-diff` yet another step closer to
> feature parity with tbdiff: it now shows the oneline, too, and indicates
> with `=` when the commits have identical diffs.
> 
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  range-diff.c | 64 ++++++++++++++++++++++++++++++++++++++++++++--------
>  1 file changed, 55 insertions(+), 9 deletions(-)
> 
> diff --git a/range-diff.c b/range-diff.c
> index 1ecee2c09..8329f52e7 100644
> --- a/range-diff.c
> +++ b/range-diff.c
> @@ -7,6 +7,8 @@
>  #include "xdiff-interface.h"
>  #include "linear-assignment.h"
>  #include "diffcore.h"
> +#include "commit.h"
> +#include "pretty.h"
>  
>  struct patch_util {
>  	/* For the search for an exact match */
> @@ -255,9 +257,54 @@ static void get_correspondences(struct string_list *a, struct string_list *b,
>  	free(b2a);
>  }
>  
> -static const char *short_oid(struct patch_util *util)
> +static void output_pair_header(struct strbuf *buf,
> +			       struct strbuf *dashes,
> +			       struct patch_util *a_util,
> +			       struct patch_util *b_util)
>  {
> -	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
> +	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
> +	struct commit *commit;
> +
> +	if (!dashes->len)
> +		strbuf_addchars(dashes, '-',
> +				strlen(find_unique_abbrev(oid,
> +							  DEFAULT_ABBREV)));
> +
> +	strbuf_reset(buf);
> +	if (!a_util)
> +		strbuf_addf(buf, "-:  %s ", dashes->buf);
> +	else
> +		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
> +			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));

I failed to notice this earlier, but here we are starting to use
util->i, which I was wondering about earlier.  Good :)

> +	if (!a_util)
> +		strbuf_addch(buf, '>');
> +	else if (!b_util)
> +		strbuf_addch(buf, '<');
> +	else if (strcmp(a_util->patch, b_util->patch))
> +		strbuf_addch(buf, '!');
> +	else
> +		strbuf_addch(buf, '=');
> +
> +	if (!b_util)
> +		strbuf_addf(buf, " -:  %s", dashes->buf);
> +	else
> +		strbuf_addf(buf, " %d:  %s", b_util->i + 1,
> +			    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));

And here for range b.

> +
> [...]

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 20/21] range-diff: make --dual-color the default mode
  2018-07-21 22:05       ` [PATCH v4 20/21] range-diff: make --dual-color the default mode Johannes Schindelin via GitGitGadget
@ 2018-07-29 21:33         ` Thomas Gummerer
  2018-08-10 21:07           ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Thomas Gummerer @ 2018-07-29 21:33 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: git, Junio C Hamano, Johannes Schindelin

On 07/21, Johannes Schindelin via GitGitGadget wrote:
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
> 
> After using this command extensively for the last two months, this
> developer came to the conclusion that even if the dual color mode still
> leaves a lot of room for confusion about what was actually changed, the
> non-dual color mode is substantially worse in that regard.
> 
> Therefore, we really want to make the dual color mode the default.

Ah and here we're making it default, so I wouldn't need a
`rangeDiff.dualColor` config variable anymore.  Even better!

> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  Documentation/git-range-diff.txt       | 32 +++++++++++++++-----------
>  builtin/range-diff.c                   | 10 ++++----
>  contrib/completion/git-completion.bash |  2 +-
>  3 files changed, 25 insertions(+), 19 deletions(-)
> 
> [...]

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 05/21] range-diff: also show the diff between patches
  2018-07-29 19:22           ` Eric Sunshine
@ 2018-07-29 21:45             ` Thomas Gummerer
  2018-07-30 16:28               ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Thomas Gummerer @ 2018-07-29 21:45 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: gitgitgadget, Git List, Junio C Hamano, Johannes Schindelin

On 07/29, Eric Sunshine wrote:
> On Sun, Jul 29, 2018 at 3:04 PM Thomas Gummerer <t.gummerer@gmail.com> wrote:
> > On 07/21, Johannes Schindelin via GitGitGadget wrote:
> > > Just like tbdiff, we now show the diff between matching patches. This is
> > > a "diff of two diffs", so it can be a bit daunting to read for the
> > > beginner.
> > > [...]
> > > Note also: while tbdiff accepts the `--no-patches` option to suppress
> > > these diffs between patches, we prefer the `-s` option that is
> > > automatically supported via our use of diff_opt_parse().
> >
> > One slightly unfortunate thing here is that we don't show these
> > options in 'git range-diff -h', which would be nice to have.  I don't
> > know if that's possible in git right now, if it's not easily possible,
> > I definitely wouldn't want to delay this series for that, and we could
> > just add it to the list of possible future enhancements that other
> > people mentioned.
> 
> This issue is not specific to git-range-diff; it's shared by other
> commands which inherit diff options via diff_opt_parse(). For
> instance, "git log -h" doesn't show diff-related options either, yet
> it accepts them.

Fair enough, that makes sense.  Thanks for the pointer!

There's one more thing that I noticed here:

    git range-diff --no-patches
    fatal: single arg format requires a symmetric range

Which is a slightly confusing error message.  In contrast git log does
the following on an unrecognized argument:

    git log --no-patches
    fatal: unrecognized argument: --no-patches

which is a little better I think.  I do however also thing the "fatal:
single arg format requires a symmetric range" is useful when someone
genuinely tries to use the single argument version of the command.  So
I don't know what a good solution for this would be.

> > > diff --git a/range-diff.c b/range-diff.c
> > > @@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)
> > >                       printf("%d: %s ! %d: %s\n",
> > >                              b_util->matching + 1, short_oid(a_util),
> > >                              j + 1, short_oid(b_util));
> > > +                     if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
> >
> > Looking at this line, it looks like it would be easy to support
> > '--no-patches' as well, which may be slightly easier to understand that
> > '-s' to someone new to the command.  But again that can be added later
> > if someone actually cares about it.
> 
> What wasn't mentioned (but was implied) by the commit message is that
> "-s" is short for "--no-patch", which also comes for free via
> diff_opt_parse(). True, "--no-patch" isn't spelled exactly the same as
> "--no-patches", but git-range-diff isn't exactly a perfect tbdiff
> clone, so hopefully not a git problem. Moreover, "--no-patch" is
> internally consistent within the Git builtin commands.

Makes sense, thanks!  "--no-patch" does make sense to me.  There's
still a lot of command line flags in git to learn for me, even after
all this time using it ;)  Might be nice to spell it out in the commit
message for someone like me, especially as "--no-patches" is already
mentioned.  Though I guess most regulars here would know about
"--no-patch", so maybe it's not worth it.  Anyway that is definitely
not worth another round here.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (21 preceding siblings ...)
  2018-07-23 21:03       ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Stefan Beller
@ 2018-07-29 21:50       ` Thomas Gummerer
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
  23 siblings, 0 replies; 387+ messages in thread
From: Thomas Gummerer @ 2018-07-29 21:50 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget; +Cc: git, Junio C Hamano

On 07/21, Johannes Schindelin via GitGitGadget wrote:

> 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.

Thanks for your work making this a built-in!  I finally found some
time to go through the series, and overall it makes sense to me.  I
left a few comments here and there, and fwiw I didn't think there's
anything major that needs to be addressed.

Hope it helps!

> 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.
> 
> While at it, I forward-ported AEvar's patch to force `--decorate=no` because `git -p tbdiff` would fail otherwise.
> 
> 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
> 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:
> 
> ```sh
>         base=^{/Start.the.merging-rebase}
>         tag=v2.17.0.windows.1
>         pre=$tag$base^2
>         git range-diff $pre$base..$pre $tag$base..$tag
> ```
> 
> 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.
> 
> Changes since v3:
> 
> - 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).
> - The documentation was adjusted a bit more in the patch that makes `--dual-color` the default.
> - Clarified the calculation of the cost matrix, as per Stefan Beller's request.
> - The man page now spells out that merge *commits* are ignored in the commit ranges (not merges per se).
> - The code in `linear-assignment.c` was adjusted to use the `SWAP()` macro.
> - 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.
> - A bogus indentation change was backed out from the patch introducing the first rudimentary implementation.
> - Instead of merely warning about missing `..` in the 2-parameter invocation, we now exit with the error message.
> - 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).
> - A lengthy `if...else if...else if...else` was simplified (from a logical point of view) by reordering it.
> - The unnecessarily `static` variable `dashes` was turned into a local variable of the caller.
> - The commit message talking about the new man page still referred to `git branch --diff`, which has been fixed.
> - A forgotten t7910 reference was changed to t3206.
> - An unbalanced double-tick was fixed in the man page.
> - Fixed grammar both of the commit message and the description of the `--no-dual-color` option.
> - 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).
> - 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.
> - 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".
> 
> Changes since v2:
> 
> - Right-aligned the patch numbers in the commit pairs.
> - Used ALLOC_ARRAY() in hungarian.c instead of xmalloc(sizeof()*size).
> - Changed compute_assignment()s return type from int to void, as it always succeeds.
> - Changed the Hungarian Algorithm to use an integer cost matrix.
> - Changed the --creation-weight <double> option to --creation-factor <percent> where <percent> is an integer.
> - Retitled 1/19 and 2/19 to better conform with the current conventions, as pointed out (and suggested) by Junio.
> - Shut up Coverity, and at the same time avoided passing the unnecessary `i` and `j` parameters to output_pair_header().
> - Removed support for the `--no-patches` option: we inherit diff_options' support for `-s` already (and much more).
> - 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.
> - Instead of overriding the fragment header color, the dual color mode will now reverse the "outer" fragment headers, too.
> - 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.
> - Pretty much re-wrote the completion, to support the new --diff mode of git-branch. See below: it was reverted for range-diff.
> - Renamed t7910 to t3206, to be closer to the git-branch tests.
> - Ensured that git_diff_ui_config() gets called, and therefore color.diff.* respected.
> - Avoided leaking `four_spaces`.
> - Fixed a declaration in a for (;;) statement (which Junio had as a fixup! that I almost missed).
> - Renamed `branch --diff`, which had been renamed from `branch-diff` (which was picked to avoid re-using `tbdiff`) to `range-diff`.
> - Renamed `hungarian.c` and its header to `linear-assignment.c`
> - Made `--dual-color` the default, and changed it to still auto-detect whether color should be used rather than forcing it
> 
> Johannes Schindelin (20):
>   linear-assignment: a function to solve least-cost assignment problems
>   Introduce `range-diff` to compare iterations of a topic branch
>   range-diff: first rudimentary implementation
>   range-diff: improve the order of the shown commits
>   range-diff: also show the diff between patches
>   range-diff: right-trim commit messages
>   range-diff: indent the diffs just like tbdiff
>   range-diff: suppress the diff headers
>   range-diff: adjust the output of the commit pairs
>   range-diff: do not show "function names" in hunk headers
>   range-diff: use color for the commit pairs
>   color: add the meta color GIT_COLOR_REVERSE
>   diff: add an internal option to dual-color diffs of diffs
>   range-diff: offer to dual-color the diffs
>   range-diff --dual-color: fix bogus white-space warning
>   range-diff: populate the man page
>   completion: support `git range-diff`
>   range-diff: left-pad patch numbers
>   range-diff: make --dual-color the default mode
>   range-diff: use dim/bold cues to improve dual color mode
> 
> Thomas Rast (1):
>   range-diff: add tests
> 
>  .gitignore                             |   1 +
>  Documentation/config.txt               |   6 +-
>  Documentation/git-range-diff.txt       | 252 +++++++++++
>  Makefile                               |   3 +
>  builtin.h                              |   1 +
>  builtin/range-diff.c                   | 106 +++++
>  cache.h                                |   3 +-
>  color.h                                |   7 +
>  command-list.txt                       |   1 +
>  contrib/completion/git-completion.bash |  14 +
>  diff.c                                 | 119 ++++-
>  diff.h                                 |  16 +-
>  git.c                                  |   1 +
>  linear-assignment.c                    | 201 ++++++++
>  linear-assignment.h                    |  22 +
>  range-diff.c                           | 440 ++++++++++++++++++
>  range-diff.h                           |   9 +
>  t/.gitattributes                       |   1 +
>  t/t3206-range-diff.sh                  | 145 ++++++
>  t/t3206/history.export                 | 604 +++++++++++++++++++++++++
>  ws.c                                   |  11 +-
>  21 files changed, 1932 insertions(+), 31 deletions(-)
>  create mode 100644 Documentation/git-range-diff.txt
>  create mode 100644 builtin/range-diff.c
>  create mode 100644 linear-assignment.c
>  create mode 100644 linear-assignment.h
>  create mode 100644 range-diff.c
>  create mode 100644 range-diff.h
>  create mode 100755 t/t3206-range-diff.sh
>  create mode 100644 t/t3206/history.export
> 
> 
> base-commit: b7bd9486b055c3f967a870311e704e3bb0654e4f
> Published-As: https://github.com/gitgitgadget/git/releases/tags/pr-1%2Fdscho%2Fbranch-diff-v4
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1/dscho/branch-diff-v4
> Pull-Request: https://github.com/gitgitgadget/git/pull/1
> 
> Range-diff vs v3:
> 
>   1:  39272eefc !  1:  f7e70689e linear-assignment: a function to solve least-cost assignment problems
>      @@ -223,9 +223,7 @@
>       +				BUG("negative j: %d", j);
>       +			i = pred[j];
>       +			column2row[j] = i;
>      -+			k = j;
>      -+			j = row2column[i];
>      -+			row2column[i] = k;
>      ++			SWAP(j, row2column[i]);
>       +		} while (i1 != i);
>       +	}
>       +
>   2:  7f15b26d4 !  2:  88134121d Introduce `range-diff` to compare iterations of a topic branch
>      @@ -10,6 +10,10 @@
>           At this point, we ignore tbdiff's color options, as they will all be
>           implemented later using diff_options.
>       
>      +    Since f318d739159 (generate-cmds.sh: export all commands to
>      +    command-list.h, 2018-05-10), every new command *requires* a man page to
>      +    build right away, so let's also add a blank man page, too.
>      +
>           Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
>       
>       diff --git a/.gitignore b/.gitignore
>      @@ -24,6 +28,22 @@
>        /git-rebase
>        /git-rebase--am
>       
>      +diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
>      +new file mode 100644
>      +--- /dev/null
>      ++++ b/Documentation/git-range-diff.txt
>      +@@
>      ++git-range-diff(1)
>      ++==================
>      ++
>      ++NAME
>      ++----
>      ++git-range-diff - Compare two commit ranges (e.g. two versions of a branch)
>      ++
>      ++GIT
>      ++---
>      ++Part of the linkgit:git[1] suite
>      +
>       diff --git a/Makefile b/Makefile
>       --- a/Makefile
>       +++ b/Makefile
>   3:  076e1192d !  3:  4e3fb47a1 range-diff: first rudimentary implementation
>      @@ -4,7 +4,7 @@
>       
>           At this stage, `git range-diff` can determine corresponding commits
>           of two related commit ranges. This makes use of the recently introduced
>      -    implementation of the Hungarian algorithm.
>      +    implementation of the linear assignment algorithm.
>       
>           The core of this patch is a straight port of the ideas of tbdiff, the
>           apparently dormant project at https://github.com/trast/tbdiff.
>      @@ -51,19 +51,17 @@
>       +	int res = 0;
>       +	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
>        
>      --	argc = parse_options(argc, argv, NULL, options,
>      --			     builtin_range_diff_usage, 0);
>      -+	argc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,
>      -+			     0);
>      + 	argc = parse_options(argc, argv, NULL, options,
>      + 			     builtin_range_diff_usage, 0);
>        
>       -	return 0;
>       +	if (argc == 2) {
>       +		if (!strstr(argv[0], ".."))
>      -+			warning(_("no .. in range: '%s'"), argv[0]);
>      ++			die(_("no .. in range: '%s'"), argv[0]);
>       +		strbuf_addstr(&range1, argv[0]);
>       +
>       +		if (!strstr(argv[1], ".."))
>      -+			warning(_("no .. in range: '%s'"), argv[1]);
>      ++			die(_("no .. in range: '%s'"), argv[1]);
>       +		strbuf_addstr(&range2, argv[1]);
>       +	} else if (argc == 3) {
>       +		strbuf_addf(&range1, "%s..%s", argv[0], argv[1]);
>      @@ -195,17 +193,21 @@
>       +			continue;
>       +		} else if (starts_with(line.buf, "@@ "))
>       +			strbuf_addstr(&buf, "@@");
>      -+		else if (line.buf[0] && !starts_with(line.buf, "index "))
>      ++		else if (!line.buf[0] || starts_with(line.buf, "index "))
>       +			/*
>       +			 * A completely blank (not ' \n', which is context)
>       +			 * line is not valid in a diff.  We skip it
>       +			 * silently, because this neatly handles the blank
>       +			 * separator line between commits in git-log
>       +			 * output.
>      ++			 *
>      ++			 * We also want to ignore the diff's `index` lines
>      ++			 * because they contain exact blob hashes in which
>      ++			 * we are not interested.
>       +			 */
>      -+			strbuf_addbuf(&buf, &line);
>      -+		else
>       +			continue;
>      ++		else
>      ++			strbuf_addbuf(&buf, &line);
>       +
>       +		strbuf_addch(&buf, '\n');
>       +		util->diffsize++;
>   4:  e98489c8c =  4:  47bee09b0 range-diff: improve the order of the shown commits
>   5:  935cad180 !  5:  94afaeaf2 range-diff: also show the diff between patches
>      @@ -55,21 +55,22 @@
>       +	int i, j, res = 0;
>        	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
>        
>      --	argc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,
>      --			     0);
>       +	git_config(git_diff_ui_config, NULL);
>       +
>       +	diff_setup(&diffopt);
>       +	diffopt.output_format = DIFF_FORMAT_PATCH;
>       +
>      -+	argc = parse_options(argc, argv, NULL, options,
>      -+			builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);
>      + 	argc = parse_options(argc, argv, NULL, options,
>      +-			     builtin_range_diff_usage, 0);
>      ++			     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);
>       +
>      -+	for (i = j = 0; i < argc; i++) {
>      ++	for (i = j = 0; i < argc; ) {
>       +		int c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);
>       +
>       +		if (!c)
>      -+			argv[j++] = argv[i];
>      ++			argv[j++] = argv[i++];
>      ++		else
>      ++			i += c;
>       +	}
>       +	argc = j;
>       +	diff_setup_done(&diffopt);
>   6:  93ac1931d =  6:  41ab875a3 range-diff: right-trim commit messages
>   7:  ca5282815 !  7:  a3dd99509 range-diff: indent the diffs just like tbdiff
>      @@ -39,7 +39,7 @@
>       +	diffopt.output_prefix_data = &four_spaces;
>        
>        	argc = parse_options(argc, argv, NULL, options,
>      - 			builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);
>      + 			     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);
>       @@
>        
>        	strbuf_release(&range1);
>   8:  80622685f =  8:  61b2ff2f7 range-diff: suppress the diff headers
>   9:  6b31cbf72 !  9:  9641ab5c0 range-diff: adjust the output of the commit pairs
>      @@ -26,25 +26,22 @@
>        
>       -static const char *short_oid(struct patch_util *util)
>       +static void output_pair_header(struct strbuf *buf,
>      ++			       struct strbuf *dashes,
>       +			       struct patch_util *a_util,
>       +			       struct patch_util *b_util)
>        {
>       -	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
>      -+	static char *dashes;
>       +	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
>       +	struct commit *commit;
>       +
>      -+	if (!dashes) {
>      -+		char *p;
>      -+
>      -+		dashes = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV));
>      -+		for (p = dashes; *p; p++)
>      -+			*p = '-';
>      -+	}
>      ++	if (!dashes->len)
>      ++		strbuf_addchars(dashes, '-',
>      ++				strlen(find_unique_abbrev(oid,
>      ++							  DEFAULT_ABBREV)));
>       +
>       +	strbuf_reset(buf);
>       +	if (!a_util)
>      -+		strbuf_addf(buf, "-:  %s ", dashes);
>      ++		strbuf_addf(buf, "-:  %s ", dashes->buf);
>       +	else
>       +		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
>       +			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
>      @@ -59,7 +56,7 @@
>       +		strbuf_addch(buf, '=');
>       +
>       +	if (!b_util)
>      -+		strbuf_addf(buf, " -:  %s", dashes);
>      ++		strbuf_addf(buf, " -:  %s", dashes->buf);
>       +	else
>       +		strbuf_addf(buf, " %d:  %s", b_util->i + 1,
>       +			    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));
>      @@ -84,7 +81,7 @@
>        static void output(struct string_list *a, struct string_list *b,
>        		   struct diff_options *diffopt)
>        {
>      -+	struct strbuf buf = STRBUF_INIT;
>      ++	struct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;
>        	int i = 0, j = 0;
>        
>        	/*
>      @@ -94,7 +91,7 @@
>        		if (i < a->nr && a_util->matching < 0) {
>       -			printf("%d: %s < -: --------\n",
>       -			       i + 1, short_oid(a_util));
>      -+			output_pair_header(&buf, a_util, NULL);
>      ++			output_pair_header(&buf, &dashes, a_util, NULL);
>        			i++;
>        			continue;
>        		}
>      @@ -103,7 +100,7 @@
>        		while (j < b->nr && b_util->matching < 0) {
>       -			printf("-: -------- > %d: %s\n",
>       -			       j + 1, short_oid(b_util));
>      -+			output_pair_header(&buf, NULL, b_util);
>      ++			output_pair_header(&buf, &dashes, NULL, b_util);
>        			b_util = ++j < b->nr ? b->items[j].util : NULL;
>        		}
>        
>      @@ -113,7 +110,7 @@
>       -			printf("%d: %s ! %d: %s\n",
>       -			       b_util->matching + 1, short_oid(a_util),
>       -			       j + 1, short_oid(b_util));
>      -+			output_pair_header(&buf, a_util, b_util);
>      ++			output_pair_header(&buf, &dashes, a_util, b_util);
>        			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
>        				patch_diff(a->items[b_util->matching].string,
>        					   b->items[j].string, diffopt);
>      @@ -122,6 +119,7 @@
>        		}
>        	}
>       +	strbuf_release(&buf);
>      ++	strbuf_release(&dashes);
>        }
>        
>        int show_range_diff(const char *range1, const char *range2,
>  10:  ef997bb8b = 10:  0a52f8878 range-diff: do not show "function names" in hunk headers
>  11:  3d9e5b0ba ! 11:  2b8d09020 range-diff: add tests
>      @@ -3,8 +3,8 @@
>           range-diff: add tests
>       
>           These are essentially lifted from https://github.com/trast/tbdiff, with
>      -    light touch-ups to account for the command now being an option of `git
>      -    branch`.
>      +    light touch-ups to account for the command now being names `git
>      +    range-diff`.
>       
>           Apart from renaming `tbdiff` to `range-diff`, only one test case needed
>           to be adjusted: 11 - 'changed message'.
>      @@ -22,12 +22,13 @@
>       --- a/t/.gitattributes
>       +++ b/t/.gitattributes
>       @@
>      - /t5515/* eol=lf
>      - /t556x_common eol=lf
>      - /t7500/* eol=lf
>      -+/t7910/* eol=lf
>      - /t8005/*.txt eol=lf
>      - /t9*/*.dump eol=lf
>      + t[0-9][0-9][0-9][0-9]/* -whitespace
>      + /diff-lib/* eol=lf
>      + /t0110/url-* binary
>      ++/t3206/* eol=lf
>      + /t3900/*.txt eol=lf
>      + /t3901/*.txt eol=lf
>      + /t4034/*/* eol=lf
>       
>       diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
>       new file mode 100755
>  12:  7273cc647 ! 12:  fb83ce71a range-diff: use color for the commit pairs
>      @@ -20,11 +20,12 @@
>        }
>        
>       -static void output_pair_header(struct strbuf *buf,
>      -+static void output_pair_header(struct diff_options *diffopt, struct strbuf *buf,
>      ++static void output_pair_header(struct diff_options *diffopt,
>      ++			       struct strbuf *buf,
>      + 			       struct strbuf *dashes,
>        			       struct patch_util *a_util,
>        			       struct patch_util *b_util)
>        {
>      - 	static char *dashes;
>        	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
>        	struct commit *commit;
>       +	char status;
>      @@ -34,11 +35,10 @@
>       +	const char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT);
>       +	const char *color;
>        
>      - 	if (!dashes) {
>      - 		char *p;
>      -@@
>      - 			*p = '-';
>      - 	}
>      + 	if (!dashes->len)
>      + 		strbuf_addchars(dashes, '-',
>      + 				strlen(find_unique_abbrev(oid,
>      + 							  DEFAULT_ABBREV)));
>        
>       +	if (!b_util) {
>       +		color = color_old;
>      @@ -57,7 +57,7 @@
>        	strbuf_reset(buf);
>       +	strbuf_addstr(buf, status == '!' ? color_old : color);
>        	if (!a_util)
>      - 		strbuf_addf(buf, "-:  %s ", dashes);
>      + 		strbuf_addf(buf, "-:  %s ", dashes->buf);
>        	else
>        		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
>        			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
>      @@ -77,7 +77,7 @@
>       +		strbuf_addf(buf, "%s%s", color_reset, color_new);
>        
>        	if (!b_util)
>      - 		strbuf_addf(buf, " -:  %s", dashes);
>      + 		strbuf_addf(buf, " -:  %s", dashes->buf);
>       @@
>        		const char *commit_buffer = get_commit_buffer(commit, NULL);
>        		const char *subject;
>      @@ -99,24 +99,27 @@
>        
>        		/* Show unmatched LHS commit whose predecessors were shown. */
>        		if (i < a->nr && a_util->matching < 0) {
>      --			output_pair_header(&buf, a_util, NULL);
>      -+			output_pair_header(diffopt, &buf, a_util, NULL);
>      +-			output_pair_header(&buf, &dashes, a_util, NULL);
>      ++			output_pair_header(diffopt,
>      ++					   &buf, &dashes, a_util, NULL);
>        			i++;
>        			continue;
>        		}
>        
>        		/* Show unmatched RHS commits. */
>        		while (j < b->nr && b_util->matching < 0) {
>      --			output_pair_header(&buf, NULL, b_util);
>      -+			output_pair_header(diffopt, &buf, NULL, b_util);
>      +-			output_pair_header(&buf, &dashes, NULL, b_util);
>      ++			output_pair_header(diffopt,
>      ++					   &buf, &dashes, NULL, b_util);
>        			b_util = ++j < b->nr ? b->items[j].util : NULL;
>        		}
>        
>        		/* Show matching LHS/RHS pair. */
>        		if (j < b->nr) {
>        			a_util = a->items[b_util->matching].util;
>      --			output_pair_header(&buf, a_util, b_util);
>      -+			output_pair_header(diffopt, &buf, a_util, b_util);
>      +-			output_pair_header(&buf, &dashes, a_util, b_util);
>      ++			output_pair_header(diffopt,
>      ++					   &buf, &dashes, a_util, b_util);
>        			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
>        				patch_diff(a->items[b_util->matching].string,
>        					   b->items[j].string, diffopt);
>  13:  96a3073fb = 13:  9ccb9516a color: add the meta color GIT_COLOR_REVERSE
>  14:  6be4baf60 = 14:  9de5bd229 diff: add an internal option to dual-color diffs of diffs
>  15:  02e13c0c6 ! 15:  21b2f9e4b range-diff: offer to dual-color the diffs
>      @@ -40,4 +40,4 @@
>       +
>        	if (argc == 2) {
>        		if (!strstr(argv[0], ".."))
>      - 			warning(_("no .. in range: '%s'"), argv[0]);
>      + 			die(_("no .. in range: '%s'"), argv[0]);
>  16:  dfa7b1e71 <  -:  --------- range-diff --dual-color: work around bogus white-space warning
>   -:  --------- > 16:  f4252f2b2 range-diff --dual-color: fix bogus white-space warning
>  17:  799da25ef ! 17:  9e09c6be6 range-diff: add a man page
>      @@ -1,6 +1,6 @@
>       Author: Johannes Schindelin <johannes.schindelin@gmx.de>
>       
>      -    range-diff: add a man page
>      +    range-diff: populate the man page
>       
>           The bulk of this patch consists of a heavily butchered version of
>           tbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from
>      @@ -9,17 +9,12 @@
>           Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
>       
>       diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
>      -new file mode 100644
>      ---- /dev/null
>      +--- a/Documentation/git-range-diff.txt
>       +++ b/Documentation/git-range-diff.txt
>       @@
>      -+git-range-diff(1)
>      -+==================
>      -+
>      -+NAME
>      -+----
>      -+git-range-diff - Compare two commit ranges (e.g. two versions of a branch)
>      -+
>      + ----
>      + git-range-diff - Compare two commit ranges (e.g. two versions of a branch)
>      + 
>       +SYNOPSIS
>       +--------
>       +[verse]
>      @@ -31,13 +26,13 @@
>       +-----------
>       +
>       +This command shows the differences between two versions of a patch
>      -+series, or more generally, two commit ranges (ignoring merges).
>      ++series, or more generally, two commit ranges (ignoring merge commits).
>       +
>       +To that end, it first finds pairs of commits from both commit ranges
>       +that correspond with each other. Two commits are said to correspond when
>       +the diff between their patches (i.e. the author information, the commit
>       +message and the commit diff) is reasonably small compared to the
>      -+patches' size. See ``Algorithm` below for details.
>      ++patches' size. See ``Algorithm`` below for details.
>       +
>       +Finally, the list of matching commits is shown in the order of the
>       +second commit range, with unmatched commits being inserted just after
>      @@ -150,6 +145,10 @@
>       +The general idea is this: we generate a cost matrix between the commits
>       +in both commit ranges, then solve the least-cost assignment.
>       +
>      ++The cost matrix is populated thusly: for each pair of commits, both
>      ++diffs are generated and the "diff of diffs" is generated, with 3 context
>      ++lines, then the number of lines in that diff is used as cost.
>      ++
>       +To avoid false positives (e.g. when a patch has been removed, and an
>       +unrelated patch has been added between two iterations of the same patch
>       +series), the cost matrix is extended to allow for that, by adding
>      @@ -245,6 +244,6 @@
>       +--------
>       +linkgit:git-log[1]
>       +
>      -+GIT
>      -+---
>      -+Part of the linkgit:git[1] suite
>      + GIT
>      + ---
>      + Part of the linkgit:git[1] suite
>  18:  d05b54c60 ! 18:  9b3632324 completion: support `git range-diff`
>      @@ -4,17 +4,11 @@
>       
>           Tab completion of `git range-diff` is very convenient, especially
>           given that the revision arguments to specify the commit ranges to
>      -    compare are typically more complex than, say, your grandfather's `git
>      -    log` arguments.
>      +    compare are typically more complex than, say, what is normally passed
>      +    to `git log`.
>       
>           Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
>       
>      -    squash! WIP completion: support `git range-diff`
>      -
>      -    Revert "WIP completion: support `git range-diff`"
>      -
>      -    This reverts commit 2e7af652af9e53a19fd947f8ebe37a78043afa49.
>      -
>       diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
>       --- a/contrib/completion/git-completion.bash
>       +++ b/contrib/completion/git-completion.bash
>  19:  144363006 <  -:  --------- range-diff: left-pad patch numbers
>   -:  --------- > 19:  07ec215e8 range-diff: left-pad patch numbers
>  20:  4a68b95ce ! 20:  b370468e7 range-diff: make --dual-color the default mode
>      @@ -4,7 +4,7 @@
>       
>           After using this command extensively for the last two months, this
>           developer came to the conclusion that even if the dual color mode still
>      -    leaves a lot of room for confusion what was actually changed, the
>      +    leaves a lot of room for confusion about what was actually changed, the
>           non-dual color mode is substantially worse in that regard.
>       
>           Therefore, we really want to make the dual color mode the default.
>      @@ -14,6 +14,15 @@
>       diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
>       --- a/Documentation/git-range-diff.txt
>       +++ b/Documentation/git-range-diff.txt
>      +@@
>      + --------
>      + [verse]
>      + 'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]
>      +-	[--dual-color] [--creation-factor=<factor>]
>      ++	[--no-dual-color] [--creation-factor=<factor>]
>      + 	( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
>      + 
>      + DESCRIPTION
>       @@
>        
>        OPTIONS
>      @@ -25,7 +34,7 @@
>       -	change in what exact lines were added.
>       +--no-dual-color::
>       +	When the commit diffs differ, `git range-diff` recreates the
>      -+	original diffs' coloring, and add outer -/+ diff markers with
>      ++	original diffs' coloring, and adds outer -/+ diff markers with
>       +	the *background* being red/green to make it easier to see e.g.
>       +	when there was a change in what exact lines were added. This is
>       +	known to `range-diff` as "dual coloring". Use `--no-dual-color`
>      @@ -34,6 +43,31 @@
>        
>        --creation-factor=<percent>::
>        	Set the creation/deletion cost fudge factor to `<percent>`.
>      +@@
>      + show`'s output, and the third line colors the old commit red, the new
>      + one green and the rest like `git show`'s commit header.
>      + 
>      +-The color-coded diff is actually a bit hard to read, though, as it
>      +-colors the entire lines red or green. The line that added "What is
>      +-unexpected" in the old commit, for example, is completely red, even if
>      +-the intent of the old commit was to add something.
>      ++A naive color-coded diff of diffs is actually a bit hard to read,
>      ++though, as it colors the entire lines red or green. The line that added
>      ++"What is unexpected" in the old commit, for example, is completely red,
>      ++even if the intent of the old commit was to add something.
>      + 
>      +-To help with that, use the `--dual-color` mode. In this mode, the diff
>      +-of diffs will retain the original diff colors, and prefix the lines with
>      +--/+ markers that have their *background* red or green, to make it more
>      +-obvious that they describe how the diff itself changed.
>      ++To help with that, `range` uses the `--dual-color` mode by default. In
>      ++this mode, the diff of diffs will retain the original diff colors, and
>      ++prefix the lines with -/+ markers that have their *background* red or
>      ++green, to make it more obvious that they describe how the diff itself
>      ++changed.
>      + 
>      + 
>      + Algorithm
>       
>       diff --git a/builtin/range-diff.c b/builtin/range-diff.c
>       --- a/builtin/range-diff.c
>   -:  --------- > 21:  d8498fb32 range-diff: use dim/bold cues to improve dual color mode
> 
> -- 
> gitgitgadget

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 01/21] linear-assignment: a function to solve least-cost assignment problems
  2018-07-28  8:46         ` Thomas Gummerer
@ 2018-07-30 15:59           ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-30 15:59 UTC (permalink / raw)
  To: Thomas Gummerer; +Cc: Johannes Schindelin via GitGitGadget, git, Junio C Hamano

Hi Thomas,

On Sat, 28 Jul 2018, Thomas Gummerer wrote:

> On 07/21, Johannes Schindelin via GitGitGadget wrote:
> > From: Johannes Schindelin <johannes.schindelin@gmx.de>
> > 
> > The problem solved by the code introduced in this commit goes like this:
> > given two sets of items, and a cost matrix which says how much it
> > "costs" to assign any given item of the first set to any given item of
> > the second, assign all items (except when the sets have different size)
> > in the cheapest way.
> > 
> > We use the Jonker-Volgenant algorithm to solve the assignment problem to
> > answer questions such as: given two different versions of a topic branch
> > (or iterations of a patch series), what is the best pairing of
> > commits/patches between the different versions?
> > 
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> >  Makefile            |   1 +
> >  linear-assignment.c | 201 ++++++++++++++++++++++++++++++++++++++++++++
> >  linear-assignment.h |  22 +++++
> >  3 files changed, 224 insertions(+)
> >  create mode 100644 linear-assignment.c
> >  create mode 100644 linear-assignment.h
> >
> > [...]
> > 
> > diff --git a/linear-assignment.h b/linear-assignment.h
> > new file mode 100644
> > index 000000000..fc4c502c8
> > --- /dev/null
> > +++ b/linear-assignment.h
> > @@ -0,0 +1,22 @@
> > +#ifndef HUNGARIAN_H
> > +#define HUNGARIAN_H
> 
> nit: maybe s/HUNGARIAN/LINEAR_ASSIGNMENT/ in the two lines above.

Makes sense.

Ciao,
Dscho

> 
> > +
> > +/*
> > + * Compute an assignment of columns -> rows (and vice versa) such that every
> > + * column is assigned to at most one row (and vice versa) minimizing the
> > + * overall cost.
> > + *
> > + * The parameter `cost` is the cost matrix: the cost to assign column j to row
> > + * i is `cost[j + column_count * i].
> > + *
> > + * The arrays column2row and row2column will be populated with the respective
> > + * assignments (-1 for unassigned, which can happen only if column_count !=
> > + * row_count).
> > + */
> > +void compute_assignment(int column_count, int row_count, int *cost,
> > +			int *column2row, int *row2column);
> > +
> > +/* The maximal cost in the cost matrix (to prevent integer overflows). */
> > +#define COST_MAX (1<<16)
> > +
> > +#endif
> > -- 
> > gitgitgadget
> > 
> 

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 03/21] range-diff: first rudimentary implementation
  2018-07-29 18:36         ` Thomas Gummerer
@ 2018-07-30 16:21           ` Johannes Schindelin
  2018-07-30 21:16             ` Thomas Gummerer
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-30 16:21 UTC (permalink / raw)
  To: Thomas Gummerer; +Cc: Johannes Schindelin via GitGitGadget, git, Junio C Hamano

Hi Thomas,

On Sun, 29 Jul 2018, Thomas Gummerer wrote:

> On 07/21, Johannes Schindelin via GitGitGadget wrote:
> > From: Johannes Schindelin <johannes.schindelin@gmx.de>
> > 
> > At this stage, `git range-diff` can determine corresponding commits
> > of two related commit ranges. This makes use of the recently introduced
> > implementation of the linear assignment algorithm.
> > 
> > The core of this patch is a straight port of the ideas of tbdiff, the
> > apparently dormant project at https://github.com/trast/tbdiff.
> > 
> > The output does not at all match `tbdiff`'s output yet, as this patch
> > really concentrates on getting the patch matching part right.
> > 
> > Note: due to differences in the diff algorithm (`tbdiff` uses the Python
> > module `difflib`, Git uses its xdiff fork), the cost matrix calculated
> > by `range-diff` is different (but very similar) to the one calculated
> > by `tbdiff`. Therefore, it is possible that they find different matching
> > commits in corner cases (e.g. when a patch was split into two patches of
> > roughly equal length).
> > 
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> >  Makefile             |   1 +
> >  builtin/range-diff.c |  43 +++++-
> >  range-diff.c         | 311 +++++++++++++++++++++++++++++++++++++++++++
> >  range-diff.h         |   7 +
> >  4 files changed, 361 insertions(+), 1 deletion(-)
> >  create mode 100644 range-diff.c
> >  create mode 100644 range-diff.h
> >
> > [...]
> > 
> > diff --git a/range-diff.c b/range-diff.c
> > new file mode 100644
> > index 000000000..15d418afa
> > --- /dev/null
> > +++ b/range-diff.c
> > @@ -0,0 +1,311 @@
> > +#include "cache.h"
> > +#include "range-diff.h"
> > +#include "string-list.h"
> > +#include "run-command.h"
> > +#include "argv-array.h"
> > +#include "hashmap.h"
> > +#include "xdiff-interface.h"
> > +#include "linear-assignment.h"
> > +
> > +struct patch_util {
> > +	/* For the search for an exact match */
> > +	struct hashmap_entry e;
> > +	const char *diff, *patch;
> > +
> > +	int i;
> > +	int diffsize;
> > +	size_t diff_offset;
> > +	/* the index of the matching item in the other branch, or -1 */
> > +	int matching;
> > +	struct object_id oid;
> > +};
> > +
> > +/*
> > + * Reads the patches into a string list, with the `util` field being populated
> > + * as struct object_id (will need to be free()d).
> > + */
> > +static int read_patches(const char *range, struct string_list *list)
> > +{
> > +	struct child_process cp = CHILD_PROCESS_INIT;
> > +	FILE *in;
> > +	struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;
> > +	struct patch_util *util = NULL;
> > +	int in_header = 1;
> > +
> > +	argv_array_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
> > +			"--reverse", "--date-order", "--decorate=no",
> > +			"--no-abbrev-commit", range,
> > +			NULL);
> 
> Compared to tbdiff, add "--decorate=no", and "--no-abbrev-commit".  I
> see we're abbreviating the commit hashes later.  We don't want ref
> names here, so "--decorate=no" makes sense as well.

Indeed. Compare also to https://github.com/trast/tbdiff/pull/8

> > +	cp.out = -1;
> > +	cp.no_stdin = 1;
> > +	cp.git_cmd = 1;
> > +
> > +	if (start_command(&cp))
> > +		return error_errno(_("could not start `log`"));
> > +	in = fdopen(cp.out, "r");
> > +	if (!in) {
> > +		error_errno(_("could not read `log` output"));
> > +		finish_command(&cp);
> > +		return -1;
> > +	}
> > +
> > +	while (strbuf_getline(&line, in) != EOF) {
> > +		const char *p;
> > +
> > +		if (skip_prefix(line.buf, "commit ", &p)) {
> > +			if (util) {
> > +				string_list_append(list, buf.buf)->util = util;
> > +				strbuf_reset(&buf);
> > +			}
> > +			util = xcalloc(sizeof(*util), 1);
> > +			if (get_oid(p, &util->oid)) {
> > +				error(_("could not parse commit '%s'"), p);
> > +				free(util);
> > +				string_list_clear(list, 1);
> > +				strbuf_release(&buf);
> > +				strbuf_release(&line);
> > +				fclose(in);
> > +				finish_command(&cp);
> > +				return -1;
> > +			}
> > +			util->matching = -1;
> > +			in_header = 1;
> > +			continue;
> > +		}
> > +
> > +		if (starts_with(line.buf, "diff --git")) {
> > +			in_header = 0;
> > +			strbuf_addch(&buf, '\n');
> > +			if (!util->diff_offset)
> > +				util->diff_offset = buf.len;
> > +			strbuf_addbuf(&buf, &line);
> > +		} else if (in_header) {
> > +			if (starts_with(line.buf, "Author: ")) {
> > +				strbuf_addbuf(&buf, &line);
> > +				strbuf_addstr(&buf, "\n\n");
> > +			} else if (starts_with(line.buf, "    ")) {
> > +				strbuf_addbuf(&buf, &line);
> > +				strbuf_addch(&buf, '\n');
> > +			}
> > +			continue;
> > +		} else if (starts_with(line.buf, "@@ "))
> > +			strbuf_addstr(&buf, "@@");
> > +		else if (!line.buf[0] || starts_with(line.buf, "index "))
> > +			/*
> > +			 * A completely blank (not ' \n', which is context)
> > +			 * line is not valid in a diff.  We skip it
> > +			 * silently, because this neatly handles the blank
> > +			 * separator line between commits in git-log
> > +			 * output.
> > +			 *
> > +			 * We also want to ignore the diff's `index` lines
> > +			 * because they contain exact blob hashes in which
> > +			 * we are not interested.
> > +			 */
> > +			continue;
> > +		else
> > +			strbuf_addbuf(&buf, &line);
> > +
> > +		strbuf_addch(&buf, '\n');
> > +		util->diffsize++;
> > +	}
> 
> This seems to differ from tbdiff in the number of newlines we're
> adding in various places, however I think as long as it's consistent
> in itself, and with the way we're printing the output the differences
> shouldn't matter.

Right.

> > +	fclose(in);
> > +	strbuf_release(&line);
> > +
> > +	if (util)
> > +		string_list_append(list, buf.buf)->util = util;
> > +	strbuf_release(&buf);
> > +
> > +	if (finish_command(&cp))
> > +		return -1;
> > +
> > +	return 0;
> > +}
> > +
> > +static int patch_util_cmp(const void *dummy, const struct patch_util *a,
> > +		     const struct patch_util *b, const char *keydata)
> > +{
> > +	return strcmp(a->diff, keydata ? keydata : b->diff);
> > +}
> > +
> > +static void find_exact_matches(struct string_list *a, struct string_list *b)
> > +{
> > +	struct hashmap map;
> > +	int i;
> > +
> > +	hashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);
> > +
> > +	/* First, add the patches of a to a hash map */
> > +	for (i = 0; i < a->nr; i++) {
> > +		struct patch_util *util = a->items[i].util;
> > +
> > +		util->i = i;
> > +		util->patch = a->items[i].string;
> > +		util->diff = util->patch + util->diff_offset;
> > +		hashmap_entry_init(util, strhash(util->diff));
> > +		hashmap_add(&map, util);
> > +	}
> > +
> > +	/* Now try to find exact matches in b */
> > +	for (i = 0; i < b->nr; i++) {
> > +		struct patch_util *util = b->items[i].util, *other;
> > +
> > +		util->i = i;
> > +		util->patch = b->items[i].string;
> > +		util->diff = util->patch + util->diff_offset;
> > +		hashmap_entry_init(util, strhash(util->diff));
> > +		other = hashmap_remove(&map, util, NULL);
> > +		if (other) {
> > +			if (other->matching >= 0)
> > +				BUG("already assigned!");
> > +
> > +			other->matching = i;
> > +			util->matching = other->i;
> > +		}
> > +	}
> 
> One possibly interesting corner case here is what happens when there
> are two patches that have the exact same diff, for example in the
> pathological case of commit A doing something, commit B reverting
> commit A, and then commit C reverting commit B, so it ends up with the
> same diff.
> 
> Having those same commits unchanged in both ranges (e.g. if a commit
> earlier in the range has been changed, and range B has been rebased on
> top of that), we'd get the following mapping from range A to range B
> for the commits in question:
> 
> A -> C
> B -> B
> C -> A
> 
> Which is not quite what I would expect as the user (even though it is
> a valid mapping, and it probably doesn't matter too much for the end
> result of the range diff, as nothing has changed between the commits
> anyway).  So I'm not sure it's worth fixing this, as it is a
> pathological case, and nothing really breaks.

Indeed. As far as I am concerned, this falls squarely into the "let's
cross that bridge when, or if, we reach it" category.

> > +
> > +	hashmap_free(&map, 0);
> > +}
> > +
> > +static void diffsize_consume(void *data, char *line, unsigned long len)
> > +{
> > +	(*(int *)data)++;
> > +}
> > +
> > +static int diffsize(const char *a, const char *b)
> > +{
> > +	xpparam_t pp = { 0 };
> > +	xdemitconf_t cfg = { 0 };
> > +	mmfile_t mf1, mf2;
> > +	int count = 0;
> > +
> > +	mf1.ptr = (char *)a;
> > +	mf1.size = strlen(a);
> > +	mf2.ptr = (char *)b;
> > +	mf2.size = strlen(b);
> > +
> > +	cfg.ctxlen = 3;
> > +	if (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))
> > +		return count;
> > +
> > +	error(_("failed to generate diff"));
> > +	return COST_MAX;
> > +}
> > +
> > +static void get_correspondences(struct string_list *a, struct string_list *b,
> > +				int creation_factor)
> > +{
> > +	int n = a->nr + b->nr;
> > +	int *cost, c, *a2b, *b2a;
> > +	int i, j;
> > +
> > +	ALLOC_ARRAY(cost, st_mult(n, n));
> > +	ALLOC_ARRAY(a2b, n);
> > +	ALLOC_ARRAY(b2a, n);
> > +
> > +	for (i = 0; i < a->nr; i++) {
> > +		struct patch_util *a_util = a->items[i].util;
> > +
> > +		for (j = 0; j < b->nr; j++) {
> > +			struct patch_util *b_util = b->items[j].util;
> > +
> > +			if (a_util->matching == j)
> > +				c = 0;
> > +			else if (a_util->matching < 0 && b_util->matching < 0)
> > +				c = diffsize(a_util->diff, b_util->diff);
> > +			else
> > +				c = COST_MAX;
> > +			cost[i + n * j] = c;
> > +		}
> > +
> > +		c = a_util->matching < 0 ?
> > +			a_util->diffsize * creation_factor / 100 : COST_MAX;
> > +		for (j = b->nr; j < n; j++)
> > +			cost[i + n * j] = c;
> > +	}
> > +
> > +	for (j = 0; j < b->nr; j++) {
> > +		struct patch_util *util = b->items[j].util;
> > +
> > +		c = util->matching < 0 ?
> > +			util->diffsize * creation_factor / 100 : COST_MAX;
> > +		for (i = a->nr; i < n; i++)
> > +			cost[i + n * j] = c;
> > +	}
> > +
> > +	for (i = a->nr; i < n; i++)
> > +		for (j = b->nr; j < n; j++)
> > +			cost[i + n * j] = 0;
> > +
> > +	compute_assignment(n, n, cost, a2b, b2a);
> > +
> > +	for (i = 0; i < a->nr; i++)
> > +		if (a2b[i] >= 0 && a2b[i] < b->nr) {
> > +			struct patch_util *a_util = a->items[i].util;
> > +			struct patch_util *b_util = b->items[a2b[i]].util;
> > +
> > +			a_util->matching = a2b[i];
> > +			b_util->matching = i;
> 
> So here we re-assign 'matching' in the struct regardless of whether it
> was assigned before while searching for exact matches or not.

If `matching` were assigned here, it would indicate a bug in the code, I
think. Before this loop, all of the `matching` fields should be `NULL`,
and the `compute_assignment()` function is expected to populate `a2b` and
`b2a` appropriately, i.e. no "double booking".

> Shouldn't diffsize for matching patches also be 0?  So are we doing
> the 'find_exact_matches()' bit only as an optimization, or am I
> missing some other reason why that is beneficial?

An optimization.

Remember, the linear assignment algorithm runs in O(n^3). That's not a
laughing matter by any stretch of imagination. The more stuff we can get
out of the way, as quickly as possible, the less it hurts to have a cubic
runtime complexity.

> > +		}
> > +
> > +	free(cost);
> > +	free(a2b);
> > +	free(b2a);
> > +}
> > +
> > +static const char *short_oid(struct patch_util *util)
> > +{
> > +	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
> > +}
> > +
> > +static void output(struct string_list *a, struct string_list *b)
> > +{
> > +	int i;
> > +
> > +	for (i = 0; i < b->nr; i++) {
> > +		struct patch_util *util = b->items[i].util, *prev;
> > +
> > +		if (util->matching < 0)
> > +			printf("-: -------- > %d: %s\n",
> > +					i + 1, short_oid(util));
> > +		else {
> > +			prev = a->items[util->matching].util;
> > +			printf("%d: %s ! %d: %s\n",
> > +			       util->matching + 1, short_oid(prev),
> > +			       i + 1, short_oid(util));
> > +		}
> > +	}
> > +
> > +	for (i = 0; i < a->nr; i++) {
> > +		struct patch_util *util = a->items[i].util;
> > +
> > +		if (util->matching < 0)
> > +			printf("%d: %s < -: --------\n",
> > +			       i + 1, short_oid(util));
> > +	}
> > +}
> > +
> > +int show_range_diff(const char *range1, const char *range2,
> > +		    int creation_factor)
> > +{
> > +	int res = 0;
> > +
> > +	struct string_list branch1 = STRING_LIST_INIT_DUP;
> > +	struct string_list branch2 = STRING_LIST_INIT_DUP;
> > +
> > +	if (read_patches(range1, &branch1))
> > +		res = error(_("could not parse log for '%s'"), range1);
> > +	if (!res && read_patches(range2, &branch2))
> > +		res = error(_("could not parse log for '%s'"), range2);
> > +
> > +	if (!res) {
> > +		find_exact_matches(&branch1, &branch2);
> 
> Note to self: here we assign the matching member of struct patch_util
> for each patch in both ranges to a patch number in the other range if
> it is an exact match.
> 
> We also assign the patch and diff members, and number the patches
> using the 'i' member of struct patch_util.  Let's see if that
> numbering is still useful later.
> 
> > +		get_correspondences(&branch1, &branch2, creation_factor);
> 
> And here we use the linear assignment algorithm to match the rest of
> the commits.
> 
> > +		output(&branch1, &branch2);
> 
> And finally we print the output.  We don't seem to use the util->i
> that's assigned for range b (or range 2) anywhere at the moment, which
> I was wondering about earlier, so I assume it's there mainly for
> symmetry, but it doesn't really hurt other than me wondering what it
> was for.

It's there because it would require extra care to do it only for one side.

> > +	}
> > +
> > +	string_list_clear(&branch1, 1);
> > +	string_list_clear(&branch2, 1);
> > +
> > +	return res;
> > +}
> > diff --git a/range-diff.h b/range-diff.h
> > new file mode 100644
> > index 000000000..dd30449c4
> > --- /dev/null
> > +++ b/range-diff.h
> > @@ -0,0 +1,7 @@
> > +#ifndef BRANCH_DIFF_H
> > +#define BRANCH_DIFF_H
> 
> s/BRANCH/RANGE/ above?

Good eyes.

Thank you for your review. It is nice to see that you can follow the code
and come to the same conclusions as I. Can I always have you as reviewer?

Ciao,
Dscho

> > +int show_range_diff(const char *range1, const char *range2,
> > +		    int creation_factor);
> > +
> > +#endif
> > -- 
> > gitgitgadget
> > 
> 

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 05/21] range-diff: also show the diff between patches
  2018-07-29 21:45             ` Thomas Gummerer
@ 2018-07-30 16:28               ` Johannes Schindelin
  2018-07-30 21:26                 ` Thomas Gummerer
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-30 16:28 UTC (permalink / raw)
  To: Thomas Gummerer; +Cc: Eric Sunshine, gitgitgadget, Git List, Junio C Hamano

Hi Thomas & Eric,

On Sun, 29 Jul 2018, Thomas Gummerer wrote:

> On 07/29, Eric Sunshine wrote:
> > On Sun, Jul 29, 2018 at 3:04 PM Thomas Gummerer <t.gummerer@gmail.com> wrote:
> > > On 07/21, Johannes Schindelin via GitGitGadget wrote:
> > > > Just like tbdiff, we now show the diff between matching patches. This is
> > > > a "diff of two diffs", so it can be a bit daunting to read for the
> > > > beginner.
> > > > [...]
> > > > Note also: while tbdiff accepts the `--no-patches` option to suppress
> > > > these diffs between patches, we prefer the `-s` option that is
> > > > automatically supported via our use of diff_opt_parse().
> > >
> > > One slightly unfortunate thing here is that we don't show these
> > > options in 'git range-diff -h', which would be nice to have.  I don't
> > > know if that's possible in git right now, if it's not easily possible,
> > > I definitely wouldn't want to delay this series for that, and we could
> > > just add it to the list of possible future enhancements that other
> > > people mentioned.
> > 
> > This issue is not specific to git-range-diff; it's shared by other
> > commands which inherit diff options via diff_opt_parse(). For
> > instance, "git log -h" doesn't show diff-related options either, yet
> > it accepts them.
> 
> Fair enough, that makes sense.  Thanks for the pointer!
> 
> There's one more thing that I noticed here:
> 
>     git range-diff --no-patches
>     fatal: single arg format requires a symmetric range
> 
> Which is a slightly confusing error message.  In contrast git log does
> the following on an unrecognized argument:
> 
>     git log --no-patches
>     fatal: unrecognized argument: --no-patches
> 
> which is a little better I think.  I do however also thing the "fatal:
> single arg format requires a symmetric range" is useful when someone
> genuinely tries to use the single argument version of the command.  So
> I don't know what a good solution for this would be.

I immediately thought of testing for a leading `-` of the remaining
argument, but I could imagine that somebody enterprisey uses

	git range-diff -- -my-first-attempt...-my-second-attempt

and I do not really want to complexify the code... Ideas?

> > > > diff --git a/range-diff.c b/range-diff.c
> > > > @@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)
> > > >                       printf("%d: %s ! %d: %s\n",
> > > >                              b_util->matching + 1, short_oid(a_util),
> > > >                              j + 1, short_oid(b_util));
> > > > +                     if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
> > >
> > > Looking at this line, it looks like it would be easy to support
> > > '--no-patches' as well, which may be slightly easier to understand that
> > > '-s' to someone new to the command.  But again that can be added later
> > > if someone actually cares about it.
> > 
> > What wasn't mentioned (but was implied) by the commit message is that
> > "-s" is short for "--no-patch", which also comes for free via
> > diff_opt_parse(). True, "--no-patch" isn't spelled exactly the same as
> > "--no-patches", but git-range-diff isn't exactly a perfect tbdiff
> > clone, so hopefully not a git problem. Moreover, "--no-patch" is
> > internally consistent within the Git builtin commands.
> 
> Makes sense, thanks!  "--no-patch" does make sense to me.  There's
> still a lot of command line flags in git to learn for me, even after
> all this time using it ;)  Might be nice to spell it out in the commit
> message for someone like me, especially as "--no-patches" is already
> mentioned.  Though I guess most regulars here would know about
> "--no-patch", so maybe it's not worth it.  Anyway that is definitely
> not worth another round here.

Sure, but not many users learn from reading the commit history...

:-)

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 11/21] range-diff: add tests
  2018-07-22  5:04         ` Eric Sunshine
@ 2018-07-30 16:30           ` Johannes Schindelin
  2018-07-30 20:18             ` Junio C Hamano
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-07-30 16:30 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: gitgitgadget, Git List, Junio C Hamano, Thomas Rast

Hi Eric,

On Sun, 22 Jul 2018, Eric Sunshine wrote:

> On Sat, Jul 21, 2018 at 6:05 PM Thomas Rast via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
> > These are essentially lifted from https://github.com/trast/tbdiff, with
> > light touch-ups to account for the command now being names `git
> 
> s/names/named/

Thanks.

I already pushed an update to https://github.com/gitgitgadget/git/pull/1.

Ciao,
Dscho

> 
> > range-diff`.
> >
> > Apart from renaming `tbdiff` to `range-diff`, only one test case needed
> > to be adjusted: 11 - 'changed message'.
> >
> > The underlying reason it had to be adjusted is that diff generation is
> > sometimes ambiguous. In this case, a comment line and an empty line are
> > added, but it is ambiguous whether they were added after the existing
> > empty line, or whether an empty line and the comment line are added
> > *before* the existing empty line. And apparently xdiff picks a different
> > option here than Python's difflib.
> >
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> 

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 11/21] range-diff: add tests
  2018-07-30 16:30           ` Johannes Schindelin
@ 2018-07-30 20:18             ` Junio C Hamano
  2018-07-30 23:40               ` Stefan Beller
  0 siblings, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-07-30 20:18 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Eric Sunshine, gitgitgadget, Git List, Thomas Rast

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> On Sun, 22 Jul 2018, Eric Sunshine wrote:
>
>> On Sat, Jul 21, 2018 at 6:05 PM Thomas Rast via GitGitGadget
>> <gitgitgadget@gmail.com> wrote:
>> > These are essentially lifted from https://github.com/trast/tbdiff, with
>> > light touch-ups to account for the command now being names `git
>> 
>> s/names/named/
>
> Thanks.
>
> I already pushed an update to https://github.com/gitgitgadget/git/pull/1.

Should I take "pushed to ... GGG" to mean "do not merge what you
have to 'next' yet, as there will be an updated series (not
incremental) being prepared"?

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 03/21] range-diff: first rudimentary implementation
  2018-07-30 16:21           ` Johannes Schindelin
@ 2018-07-30 21:16             ` Thomas Gummerer
  2018-08-10 20:50               ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Thomas Gummerer @ 2018-07-30 21:16 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Johannes Schindelin via GitGitGadget, git, Junio C Hamano

On 07/30, Johannes Schindelin wrote:
> Hi Thomas,
> 
> On Sun, 29 Jul 2018, Thomas Gummerer wrote:
> 
> > On 07/21, Johannes Schindelin via GitGitGadget wrote:
> > > 
> > > [...]
> > > 
> > > +static void find_exact_matches(struct string_list *a, struct string_list *b)
> > > +{
> > > +	struct hashmap map;
> > > +	int i;
> > > +
> > > +	hashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);
> > > +
> > > +	/* First, add the patches of a to a hash map */
> > > +	for (i = 0; i < a->nr; i++) {
> > > +		struct patch_util *util = a->items[i].util;
> > > +
> > > +		util->i = i;
> > > +		util->patch = a->items[i].string;
> > > +		util->diff = util->patch + util->diff_offset;
> > > +		hashmap_entry_init(util, strhash(util->diff));
> > > +		hashmap_add(&map, util);
> > > +	}
> > > +
> > > +	/* Now try to find exact matches in b */
> > > +	for (i = 0; i < b->nr; i++) {
> > > +		struct patch_util *util = b->items[i].util, *other;
> > > +
> > > +		util->i = i;
> > > +		util->patch = b->items[i].string;
> > > +		util->diff = util->patch + util->diff_offset;
> > > +		hashmap_entry_init(util, strhash(util->diff));
> > > +		other = hashmap_remove(&map, util, NULL);
> > > +		if (other) {
> > > +			if (other->matching >= 0)
> > > +				BUG("already assigned!");
> > > +
> > > +			other->matching = i;
> > > +			util->matching = other->i;
> > > +		}
> > > +	}
> > 
> > One possibly interesting corner case here is what happens when there
> > are two patches that have the exact same diff, for example in the
> > pathological case of commit A doing something, commit B reverting
> > commit A, and then commit C reverting commit B, so it ends up with the
> > same diff.
> > 
> > Having those same commits unchanged in both ranges (e.g. if a commit
> > earlier in the range has been changed, and range B has been rebased on
> > top of that), we'd get the following mapping from range A to range B
> > for the commits in question:
> > 
> > A -> C
> > B -> B
> > C -> A
> > 
> > Which is not quite what I would expect as the user (even though it is
> > a valid mapping, and it probably doesn't matter too much for the end
> > result of the range diff, as nothing has changed between the commits
> > anyway).  So I'm not sure it's worth fixing this, as it is a
> > pathological case, and nothing really breaks.
> 
> Indeed. As far as I am concerned, this falls squarely into the "let's
> cross that bridge when, or if, we reach it" category.

Makes sense, this can definitely be addressed later.

> > > +
> > > +	hashmap_free(&map, 0);
> > > +}
> > > +
> > > +static void diffsize_consume(void *data, char *line, unsigned long len)
> > > +{
> > > +	(*(int *)data)++;
> > > +}
> > > +
> > > +static int diffsize(const char *a, const char *b)
> > > +{
> > > +	xpparam_t pp = { 0 };
> > > +	xdemitconf_t cfg = { 0 };
> > > +	mmfile_t mf1, mf2;
> > > +	int count = 0;
> > > +
> > > +	mf1.ptr = (char *)a;
> > > +	mf1.size = strlen(a);
> > > +	mf2.ptr = (char *)b;
> > > +	mf2.size = strlen(b);
> > > +
> > > +	cfg.ctxlen = 3;
> > > +	if (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))
> > > +		return count;
> > > +
> > > +	error(_("failed to generate diff"));
> > > +	return COST_MAX;
> > > +}
> > > +
> > > +static void get_correspondences(struct string_list *a, struct string_list *b,
> > > +				int creation_factor)
> > > +{
> > > +	int n = a->nr + b->nr;
> > > +	int *cost, c, *a2b, *b2a;
> > > +	int i, j;
> > > +
> > > +	ALLOC_ARRAY(cost, st_mult(n, n));
> > > +	ALLOC_ARRAY(a2b, n);
> > > +	ALLOC_ARRAY(b2a, n);
> > > +
> > > +	for (i = 0; i < a->nr; i++) {
> > > +		struct patch_util *a_util = a->items[i].util;
> > > +
> > > +		for (j = 0; j < b->nr; j++) {
> > > +			struct patch_util *b_util = b->items[j].util;
> > > +
> > > +			if (a_util->matching == j)
> > > +				c = 0;
> > > +			else if (a_util->matching < 0 && b_util->matching < 0)
> > > +				c = diffsize(a_util->diff, b_util->diff);
> > > +			else
> > > +				c = COST_MAX;
> > > +			cost[i + n * j] = c;
> > > +		}
> > > +
> > > +		c = a_util->matching < 0 ?
> > > +			a_util->diffsize * creation_factor / 100 : COST_MAX;
> > > +		for (j = b->nr; j < n; j++)
> > > +			cost[i + n * j] = c;
> > > +	}
> > > +
> > > +	for (j = 0; j < b->nr; j++) {
> > > +		struct patch_util *util = b->items[j].util;
> > > +
> > > +		c = util->matching < 0 ?
> > > +			util->diffsize * creation_factor / 100 : COST_MAX;
> > > +		for (i = a->nr; i < n; i++)
> > > +			cost[i + n * j] = c;
> > > +	}
> > > +
> > > +	for (i = a->nr; i < n; i++)
> > > +		for (j = b->nr; j < n; j++)
> > > +			cost[i + n * j] = 0;
> > > +
> > > +	compute_assignment(n, n, cost, a2b, b2a);
> > > +
> > > +	for (i = 0; i < a->nr; i++)
> > > +		if (a2b[i] >= 0 && a2b[i] < b->nr) {
> > > +			struct patch_util *a_util = a->items[i].util;
> > > +			struct patch_util *b_util = b->items[a2b[i]].util;
> > > +
> > > +			a_util->matching = a2b[i];
> > > +			b_util->matching = i;
> > 
> > So here we re-assign 'matching' in the struct regardless of whether it
> > was assigned before while searching for exact matches or not.
> 
> If `matching` were assigned here, it would indicate a bug in the code, I
> think. Before this loop, all of the `matching` fields should be `NULL`,
> and the `compute_assignment()` function is expected to populate `a2b` and
> `b2a` appropriately, i.e. no "double booking".

Hmm we are using the 'matching' fields in the loops above, e.g.:

	if (a_util->matching == j)
		c = 0;

They're also ints, so they can't be `NULL`, right?  So what I was
thinking is that we could do

	 if (a_util->matching < 0) {
		a_util->matching = a2b[i];
		b_util->matching = i;
	}

Anyway, I don't think it really matters, as there is no "double
booking", so we should essentially get the same results.

> > Shouldn't diffsize for matching patches also be 0?  So are we doing
> > the 'find_exact_matches()' bit only as an optimization, or am I
> > missing some other reason why that is beneficial?
> 
> An optimization.
> 
> Remember, the linear assignment algorithm runs in O(n^3). That's not a
> laughing matter by any stretch of imagination. The more stuff we can get
> out of the way, as quickly as possible, the less it hurts to have a cubic
> runtime complexity.

Makes sense, that's what I was expecting, just wanted to double check
that I didn't miss something.  Thanks for confirming!

> > > +		}
> > > +
> > > +	free(cost);
> > > +	free(a2b);
> > > +	free(b2a);
> > > +}
> > > +
> > > +static const char *short_oid(struct patch_util *util)
> > > +{
> > > +	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
> > > +}
> > > +
> > > +static void output(struct string_list *a, struct string_list *b)
> > > +{
> > > +	int i;
> > > +
> > > +	for (i = 0; i < b->nr; i++) {
> > > +		struct patch_util *util = b->items[i].util, *prev;
> > > +
> > > +		if (util->matching < 0)
> > > +			printf("-: -------- > %d: %s\n",
> > > +					i + 1, short_oid(util));
> > > +		else {
> > > +			prev = a->items[util->matching].util;
> > > +			printf("%d: %s ! %d: %s\n",
> > > +			       util->matching + 1, short_oid(prev),
> > > +			       i + 1, short_oid(util));
> > > +		}
> > > +	}
> > > +
> > > +	for (i = 0; i < a->nr; i++) {
> > > +		struct patch_util *util = a->items[i].util;
> > > +
> > > +		if (util->matching < 0)
> > > +			printf("%d: %s < -: --------\n",
> > > +			       i + 1, short_oid(util));
> > > +	}
> > > +}
> > > +
> > > +int show_range_diff(const char *range1, const char *range2,
> > > +		    int creation_factor)
> > > +{
> > > +	int res = 0;
> > > +
> > > +	struct string_list branch1 = STRING_LIST_INIT_DUP;
> > > +	struct string_list branch2 = STRING_LIST_INIT_DUP;
> > > +
> > > +	if (read_patches(range1, &branch1))
> > > +		res = error(_("could not parse log for '%s'"), range1);
> > > +	if (!res && read_patches(range2, &branch2))
> > > +		res = error(_("could not parse log for '%s'"), range2);
> > > +
> > > +	if (!res) {
> > > +		find_exact_matches(&branch1, &branch2);
> > 
> > Note to self: here we assign the matching member of struct patch_util
> > for each patch in both ranges to a patch number in the other range if
> > it is an exact match.
> > 
> > We also assign the patch and diff members, and number the patches
> > using the 'i' member of struct patch_util.  Let's see if that
> > numbering is still useful later.
> > 
> > > +		get_correspondences(&branch1, &branch2, creation_factor);
> > 
> > And here we use the linear assignment algorithm to match the rest of
> > the commits.
> > 
> > > +		output(&branch1, &branch2);
> > 
> > And finally we print the output.  We don't seem to use the util->i
> > that's assigned for range b (or range 2) anywhere at the moment, which
> > I was wondering about earlier, so I assume it's there mainly for
> > symmetry, but it doesn't really hurt other than me wondering what it
> > was for.
> 
> It's there because it would require extra care to do it only for one side.
> 
> > > +	}
> > > +
> > > +	string_list_clear(&branch1, 1);
> > > +	string_list_clear(&branch2, 1);
> > > +
> > > +	return res;
> > > +}
> > > diff --git a/range-diff.h b/range-diff.h
> > > new file mode 100644
> > > index 000000000..dd30449c4
> > > --- /dev/null
> > > +++ b/range-diff.h
> > > @@ -0,0 +1,7 @@
> > > +#ifndef BRANCH_DIFF_H
> > > +#define BRANCH_DIFF_H
> > 
> > s/BRANCH/RANGE/ above?
> 
> Good eyes.
> 
> Thank you for your review. It is nice to see that you can follow the code
> and come to the same conclusions as I. Can I always have you as reviewer?

Glad I could help.  Can I have more hours in a day? ;) I wish I had
more time for reviewing code, but sadly the time I have is limited.
Which is why I only got to review this now as well.  I did enjoy the
read!

> Ciao,
> Dscho
> 
> > > +int show_range_diff(const char *range1, const char *range2,
> > > +		    int creation_factor);
> > > +
> > > +#endif
> > > -- 
> > > gitgitgadget
> > > 
> > 

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 05/21] range-diff: also show the diff between patches
  2018-07-30 16:28               ` Johannes Schindelin
@ 2018-07-30 21:26                 ` Thomas Gummerer
  2018-07-30 21:51                   ` Eric Sunshine
  2018-08-10 20:36                   ` Johannes Schindelin
  0 siblings, 2 replies; 387+ messages in thread
From: Thomas Gummerer @ 2018-07-30 21:26 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Eric Sunshine, gitgitgadget, Git List, Junio C Hamano

On 07/30, Johannes Schindelin wrote:
> Hi Thomas & Eric,
> 
> On Sun, 29 Jul 2018, Thomas Gummerer wrote:
> 
> > On 07/29, Eric Sunshine wrote:
> > > On Sun, Jul 29, 2018 at 3:04 PM Thomas Gummerer <t.gummerer@gmail.com> wrote:
> > > > On 07/21, Johannes Schindelin via GitGitGadget wrote:
> > > > > Just like tbdiff, we now show the diff between matching patches. This is
> > > > > a "diff of two diffs", so it can be a bit daunting to read for the
> > > > > beginner.
> > > > > [...]
> > > > > Note also: while tbdiff accepts the `--no-patches` option to suppress
> > > > > these diffs between patches, we prefer the `-s` option that is
> > > > > automatically supported via our use of diff_opt_parse().
> > > >
> > > > One slightly unfortunate thing here is that we don't show these
> > > > options in 'git range-diff -h', which would be nice to have.  I don't
> > > > know if that's possible in git right now, if it's not easily possible,
> > > > I definitely wouldn't want to delay this series for that, and we could
> > > > just add it to the list of possible future enhancements that other
> > > > people mentioned.
> > > 
> > > This issue is not specific to git-range-diff; it's shared by other
> > > commands which inherit diff options via diff_opt_parse(). For
> > > instance, "git log -h" doesn't show diff-related options either, yet
> > > it accepts them.
> > 
> > Fair enough, that makes sense.  Thanks for the pointer!
> > 
> > There's one more thing that I noticed here:
> > 
> >     git range-diff --no-patches
> >     fatal: single arg format requires a symmetric range
> > 
> > Which is a slightly confusing error message.  In contrast git log does
> > the following on an unrecognized argument:
> > 
> >     git log --no-patches
> >     fatal: unrecognized argument: --no-patches
> > 
> > which is a little better I think.  I do however also thing the "fatal:
> > single arg format requires a symmetric range" is useful when someone
> > genuinely tries to use the single argument version of the command.  So
> > I don't know what a good solution for this would be.
> 
> I immediately thought of testing for a leading `-` of the remaining
> argument, but I could imagine that somebody enterprisey uses
> 
> 	git range-diff -- -my-first-attempt...-my-second-attempt
> 
> and I do not really want to complexify the code... Ideas?

Good point.  I can't really come up with a good option right now
either.  It's not too bad, as users just typed the command, so it
should be easy enough to see from the previous line what went wrong.

One potential option may be to turn "die(_("single arg format requires
a symmetric range"));" into an 'error()', and show the usage?  I think
that may be nice anyway, as "symmetric range" may not be immediately
obvious to everyone, but together with the usage it may be clearer?

> > > > > diff --git a/range-diff.c b/range-diff.c
> > > > > @@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)
> > > > >                       printf("%d: %s ! %d: %s\n",
> > > > >                              b_util->matching + 1, short_oid(a_util),
> > > > >                              j + 1, short_oid(b_util));
> > > > > +                     if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
> > > >
> > > > Looking at this line, it looks like it would be easy to support
> > > > '--no-patches' as well, which may be slightly easier to understand that
> > > > '-s' to someone new to the command.  But again that can be added later
> > > > if someone actually cares about it.
> > > 
> > > What wasn't mentioned (but was implied) by the commit message is that
> > > "-s" is short for "--no-patch", which also comes for free via
> > > diff_opt_parse(). True, "--no-patch" isn't spelled exactly the same as
> > > "--no-patches", but git-range-diff isn't exactly a perfect tbdiff
> > > clone, so hopefully not a git problem. Moreover, "--no-patch" is
> > > internally consistent within the Git builtin commands.
> > 
> > Makes sense, thanks!  "--no-patch" does make sense to me.  There's
> > still a lot of command line flags in git to learn for me, even after
> > all this time using it ;)  Might be nice to spell it out in the commit
> > message for someone like me, especially as "--no-patches" is already
> > mentioned.  Though I guess most regulars here would know about
> > "--no-patch", so maybe it's not worth it.  Anyway that is definitely
> > not worth another round here.
> 
> Sure, but not many users learn from reading the commit history...
> 
> :-)
> 
> Ciao,
> Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 05/21] range-diff: also show the diff between patches
  2018-07-30 21:26                 ` Thomas Gummerer
@ 2018-07-30 21:51                   ` Eric Sunshine
  2018-08-10 21:12                     ` Johannes Schindelin
  2018-08-10 20:36                   ` Johannes Schindelin
  1 sibling, 1 reply; 387+ messages in thread
From: Eric Sunshine @ 2018-07-30 21:51 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: Johannes Schindelin, gitgitgadget, Git List, Junio C Hamano

On Mon, Jul 30, 2018 at 5:26 PM Thomas Gummerer <t.gummerer@gmail.com> wrote:
> On 07/30, Johannes Schindelin wrote:
> > On Sun, 29 Jul 2018, Thomas Gummerer wrote:
> > > There's one more thing that I noticed here:
> > >
> > >     git range-diff --no-patches
> > >     fatal: single arg format requires a symmetric range
> > >
> > I immediately thought of testing for a leading `-` of the remaining
> > argument, but I could imagine that somebody enterprisey uses
> >
> >       git range-diff -- -my-first-attempt...-my-second-attempt
> >
> > and I do not really want to complexify the code... Ideas?
>
> Good point.  I can't really come up with a good option right now
> either.  It's not too bad, as users just typed the command, so it
> should be easy enough to see from the previous line what went wrong.

I think you can attain the desired behavior by making a final
parse_options() call with empty 'options' list after the call to
diff_setup_done(). It's pretty much a one-line fix, but can probably
be done as an incremental change rather than rerolling.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 11/21] range-diff: add tests
  2018-07-30 20:18             ` Junio C Hamano
@ 2018-07-30 23:40               ` Stefan Beller
  2018-07-31 15:19                 ` Junio C Hamano
  0 siblings, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-07-30 23:40 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Johannes Schindelin, Eric Sunshine, gitgitgadget, git, Thomas Rast

On Mon, Jul 30, 2018 at 1:18 PM Junio C Hamano <gitster@pobox.com> wrote:

> > I already pushed an update to https://github.com/gitgitgadget/git/pull/1.
>
> Should I take "pushed to ... GGG" to mean "do not merge what you
> have to 'next' yet, as there will be an updated series (not
> incremental) being prepared"?

Not speaking for Johannes, but I think that *could* work.
On the other hand full rerolls are more expensive to prep
(at least for me, I guess GGG might change that)

git range-diff gitgitgadget/pr/1...origin/js/range-diff
shows there is

* a different header guard:
   @@ -418,8 +419,8 @@
     --- /dev/null
     +++ b/range-diff.h
     @@
    -+#ifndef RANGE_DIFF_H
    -+#define RANGE_DIFF_H
    ++#ifndef BRANCH_DIFF_H
    ++#define BRANCH_DIFF_H
     +

* another different header guard:
    @@ -239,8 +240,8 @@
     --- /dev/null
     +++ b/linear-assignment.h
     @@
    -+#ifndef LINEAR_ASSIGNMENT_H
    -+#define LINEAR_ASSIGNMENT_H
    ++#ifndef HUNGARIAN_H
    ++#define HUNGARIAN_H
     +

although this one is the other way round. Did you
squash in one header guard and Johannes fixed a
header guard in a different file?

With that said, I can send my series for more color testing
and better diff.c code on top of either.
(https://public-inbox.org/git/20180728030448.192177-1-sbeller@google.com/)

Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 11/21] range-diff: add tests
  2018-07-30 23:40               ` Stefan Beller
@ 2018-07-31 15:19                 ` Junio C Hamano
  0 siblings, 0 replies; 387+ messages in thread
From: Junio C Hamano @ 2018-07-31 15:19 UTC (permalink / raw)
  To: Stefan Beller
  Cc: Johannes Schindelin, Eric Sunshine, gitgitgadget, git, Thomas Rast

Stefan Beller <sbeller@google.com> writes:

> On Mon, Jul 30, 2018 at 1:18 PM Junio C Hamano <gitster@pobox.com> wrote:
>
>> > I already pushed an update to https://github.com/gitgitgadget/git/pull/1.
>>
>> Should I take "pushed to ... GGG" to mean "do not merge what you
>> have to 'next' yet, as there will be an updated series (not
>> incremental) being prepared"?
>
> Not speaking for Johannes, but I think that *could* work.

I wasn't asking if it could or could not work.  I was asking for his
intentions.

Not knowing it means I cannot merge the topic as-is merged to 'next'
and have to wait just in case, but his not knowing my waiting would
mean an update version may never come.


^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike
  2018-07-23 21:03       ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Stefan Beller
  2018-07-23 21:49         ` Junio C Hamano
  2018-07-26  9:47         ` Johannes Schindelin
@ 2018-08-08 13:05         ` Johannes Schindelin
  2018-08-08 17:33           ` Stefan Beller
  2 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-08-08 13:05 UTC (permalink / raw)
  To: Stefan Beller; +Cc: gitgitgadget, git, Junio C Hamano

Hi Stefan,

On Mon, 23 Jul 2018, Stefan Beller wrote:

> On Sat, Jul 21, 2018 at 3:04 PM Johannes Schindelin via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
> 
> >   1:  39272eefc !  1:  f7e70689e linear-assignment: a function to solve least-cost assignment problems
> >      @@ -223,9 +223,7 @@
> >       +                         BUG("negative j: %d", j);
> >       +                 i = pred[j];
> >       +                 column2row[j] = i;
> >      -+                 k = j;
> >      -+                 j = row2column[i];
> >      -+                 row2column[i] = k;
> >      ++                 SWAP(j, row2column[i]);
> 
> The dual color option (as a default) really helps here. Thanks for that!
> Does it have to be renamed though? (It's more than two colors; originally
> it was inverting the beginning signs)
> 
> Maybe --color=emphasize-later
> assuming there will be other modes for coloring, such as "diff-only",
> which would correspond with --no-dual-color, or "none" that will correspond
> would be --no-color. I imagine there could be more fancy things, hence I would
> propose a mode rather than a switch.
> (Feel free to push back if discussing naming here feels like bike shedding)

I do feel free to push back on that.

> 2:  7f15b26d4ea !  82:  88134121d2a Introduce `range-diff` to compare
> iterations of a topic branch
> [...]
> >       diff --git a/Makefile b/Makefile
> >       --- a/Makefile
> >       +++ b/Makefile
> 
> The line starting with --- is red (default removed color) and the line
> with +++ is green (default add color).
> 
> Ideally these two lines and the third line above starting with "diff --git"
> would render in GIT_COLOR_BOLD ("METAINFO").

I agree that is not the best coloring here, but as you remarked elsewhere,
it would require content-aware dual coloring, and I am loathe to try to
implement that for two reasons: 1) it would take most likely a long time
to design and implement that, and 2) I don't have that time.

So I would like to declare that good enough is good enough in this case.

> >   3:  076e1192d !  3:  4e3fb47a1 range-diff: first rudimentary implementation
> >      @@ -4,7 +4,7 @@
> >
> >           At this stage, `git range-diff` can determine corresponding commits
> >           of two related commit ranges. This makes use of the recently introduced
> >      -    implementation of the Hungarian algorithm.
> >      +    implementation of the linear assignment algorithm.
> >
> >           The core of this patch is a straight port of the ideas of tbdiff, the
> >           apparently dormant project at https://github.com/trast/tbdiff.
> >      @@ -51,19 +51,17 @@
> >       + int res = 0;
> >       + struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
> >
> >      -- argc = parse_options(argc, argv, NULL, options,
> >      --                      builtin_range_diff_usage, 0);
> >      -+ argc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,
> >      -+                      0);
> >      +  argc = parse_options(argc, argv, NULL, options,
> >      +                       builtin_range_diff_usage, 0);
> 
> This is really nice in colors when viewed locally.
> 
> >  16:  dfa7b1e71 <  -:  --------- range-diff --dual-color: work around bogus white-space warning
> >   -:  --------- > 16:  f4252f2b2 range-diff --dual-color: fix bogus white-space warning
> 
> Ah; here my initial assumption of only reviewing the range-diff breaks down now.
> I'll dig into patch 16 separately.

Right. This was an almost complete rewrite, and then next iteration will
hopefully bring another complete rewrite: disabling whitespace warnings in
dual color mode.

> Maybe it is worth having an option to expand all "new" patches.

Sure.

And I also have a use case for --left-only/--right-only.

And I also have a strong use case (and so does Junio, it seems, or for
that matter, anybody contributing to Git due to Junio's insistence on
signing off on each patch, rather than on the merge commit) for something
like --ignore-lines=<regex>.

And you probably guess what I will say next: these features will make for
really fantastic patch series *on top* of mine. There really is no good
reason to delay the current patch series just to cram more features into
it that had not been planned in the first place.

> (Given that the range-diff
> pr-1/dscho/branch-diff-v3...pr-1/dscho/branch-diff-v4 told me you used a
> different base, this is a hard problem, as I certainly would want to
> skip over all new base commits, but this one is interesting to look at.
> An easy way out: Maybe an option to expand any new commits/patches after
> the first expansion? Asking for opinions rather than implementing it)
> 
> >  19:  144363006 <  -:  --------- range-diff: left-pad patch numbers
> >   -:  --------- > 19:  07ec215e8 range-diff: left-pad patch numbers
> 
> >   -:  --------- > 21:  d8498fb32 range-diff: use dim/bold cues to improve dual color mode
> 
> Those are interesting, I'll look at them separately, too.

Thanks,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike
  2018-08-08 13:05         ` Johannes Schindelin
@ 2018-08-08 17:33           ` Stefan Beller
  2018-08-10 21:18             ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Stefan Beller @ 2018-08-08 17:33 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: gitgitgadget, git, Junio C Hamano

On Wed, Aug 8, 2018 at 6:05 AM Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:

> > [...]
> > >       diff --git a/Makefile b/Makefile
> > >       --- a/Makefile
> > >       +++ b/Makefile
> >
> > The line starting with --- is red (default removed color) and the line
> > with +++ is green (default add color).
> >
> > Ideally these two lines and the third line above starting with "diff --git"
> > would render in GIT_COLOR_BOLD ("METAINFO").
>
> I agree that is not the best coloring here, but as you remarked elsewhere,
> it would require content-aware dual coloring, and I am loathe to try to
> implement that for two reasons: 1) it would take most likely a long time
> to design and implement that, and 2) I don't have that time.
>
> So I would like to declare that good enough is good enough in this case.

I anticipated this answer, so I wrote some patches myself, starting at
https://public-inbox.org/git/20180804015317.182683-1-sbeller@google.com/
specifically
https://public-inbox.org/git/20180804015317.182683-5-sbeller@google.com/

I plan on resending these on top of your resend (if any) at a later convenient
time for both you and Junio, as noted in
https://public-inbox.org/git/CAGZ79kZnVEsvpicNu7LXkRcHuRqGvESfvG3DL5O_2kPVYrW-Gg@mail.gmail.com/


>
> > >   3:  076e1192d !  3:  4e3fb47a1 range-diff: first rudimentary implementation
> > >      @@ -4,7 +4,7 @@
> > >
> > >           At this stage, `git range-diff` can determine corresponding commits
> > >           of two related commit ranges. This makes use of the recently introduced
> > >      -    implementation of the Hungarian algorithm.
> > >      +    implementation of the linear assignment algorithm.
> > >
> > >           The core of this patch is a straight port of the ideas of tbdiff, the
> > >           apparently dormant project at https://github.com/trast/tbdiff.
> > >      @@ -51,19 +51,17 @@
> > >       + int res = 0;
> > >       + struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
> > >
> > >      -- argc = parse_options(argc, argv, NULL, options,
> > >      --                      builtin_range_diff_usage, 0);
> > >      -+ argc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,
> > >      -+                      0);
> > >      +  argc = parse_options(argc, argv, NULL, options,
> > >      +                       builtin_range_diff_usage, 0);
> >
> > This is really nice in colors when viewed locally.
> >
> > >  16:  dfa7b1e71 <  -:  --------- range-diff --dual-color: work around bogus white-space warning
> > >   -:  --------- > 16:  f4252f2b2 range-diff --dual-color: fix bogus white-space warning
> >
> > Ah; here my initial assumption of only reviewing the range-diff breaks down now.
> > I'll dig into patch 16 separately.
>
> Right. This was an almost complete rewrite, and then next iteration will
> hopefully bring another complete rewrite: disabling whitespace warnings in
> dual color mode.
>
> > Maybe it is worth having an option to expand all "new" patches.
>
> Sure.
>
> And I also have a use case for --left-only/--right-only.
>
> And I also have a strong use case (and so does Junio, it seems, or for
> that matter, anybody contributing to Git due to Junio's insistence on
> signing off on each patch, rather than on the merge commit) for something
> like --ignore-lines=<regex>.
>
> And you probably guess what I will say next: these features will make for
> really fantastic patch series *on top* of mine. There really is no good
> reason to delay the current patch series just to cram more features into
> it that had not been planned in the first place.

Yes, I agree. I am unsure about the current state of your series, though;

Junio thinks (expects?) a resend, whereas you seem to call it good enough
but also said (some time back) that you want to resend due to Thomas
feedback.

I do have 2 series on top of the current range-diff.
* The first (queued by Junio as origin/sb/range-diff-colors)
   adds a basic test for colors and improves diff.c readability
* The second (linked above) changes colors for some lines.

I do not want to build more on top as long as I do not know if
you resend (and how much it'll change)

Thanks,
Stefan

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 18/21] completion: support `git range-diff`
  2018-07-22  5:49         ` Eric Sunshine
@ 2018-08-10 20:24           ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-08-10 20:24 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: gitgitgadget, Git List, Junio C Hamano

Hi Eric,


On Sun, 22 Jul 2018, Eric Sunshine wrote:

> On Sat, Jul 21, 2018 at 6:05 PM Johannes Schindelin via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
> > Tab completion of `git range-diff` is very convenient, especially
> > given that the revision arguments to specify the commit ranges to
> > compare are typically more complex than, say, what is normally passed
> > to `git log`.
> >
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> > diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
> > @@ -1976,6 +1976,20 @@ _git_push ()
> > +_git_range_diff ()
> > +{
> > +  case "$cur" in
> > +  --*)
> > +          __gitcomp "
> > +               --creation-factor= --dual-color
> > +                  $__git_diff_common_options
> > +                  "
> 
> This is indented with a mix of spaces and tabs.
> 
>     Applying: completion: support `git range-diff`
>     .git/rebase-apply/patch:18: space before tab in indent.
>                 --creation-factor= --dual-color
>     warning: 1 line adds whitespace errors.
>     Applying: range-diff: make --dual-color the default mode
>     .git/rebase-apply/patch:105: space before tab in indent.
>                 --creation-factor= --no-dual-color
>     warning: 1 line adds whitespace errors.
> 
> Other parts of this script seem to use tabs for indentation.

Thanks.

I guess that this is due to my playing with VS Code and failing to adjust
indentation rules of anything but C code...

Will be fixed in v5.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 05/21] range-diff: also show the diff between patches
  2018-07-30 21:26                 ` Thomas Gummerer
  2018-07-30 21:51                   ` Eric Sunshine
@ 2018-08-10 20:36                   ` Johannes Schindelin
  1 sibling, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-08-10 20:36 UTC (permalink / raw)
  To: Thomas Gummerer; +Cc: Eric Sunshine, gitgitgadget, Git List, Junio C Hamano

Hi Thomas,

On Mon, 30 Jul 2018, Thomas Gummerer wrote:

> On 07/30, Johannes Schindelin wrote:
> > 
> > On Sun, 29 Jul 2018, Thomas Gummerer wrote:
> > 
> > > On 07/29, Eric Sunshine wrote:
> > > > On Sun, Jul 29, 2018 at 3:04 PM Thomas Gummerer <t.gummerer@gmail.com> wrote:
> > > > > On 07/21, Johannes Schindelin via GitGitGadget wrote:
> > > > > > Just like tbdiff, we now show the diff between matching patches. This is
> > > > > > a "diff of two diffs", so it can be a bit daunting to read for the
> > > > > > beginner.
> > > > > > [...]
> > > > > > Note also: while tbdiff accepts the `--no-patches` option to suppress
> > > > > > these diffs between patches, we prefer the `-s` option that is
> > > > > > automatically supported via our use of diff_opt_parse().
> > > > >
> > > > > One slightly unfortunate thing here is that we don't show these
> > > > > options in 'git range-diff -h', which would be nice to have.  I don't
> > > > > know if that's possible in git right now, if it's not easily possible,
> > > > > I definitely wouldn't want to delay this series for that, and we could
> > > > > just add it to the list of possible future enhancements that other
> > > > > people mentioned.
> > > > 
> > > > This issue is not specific to git-range-diff; it's shared by other
> > > > commands which inherit diff options via diff_opt_parse(). For
> > > > instance, "git log -h" doesn't show diff-related options either, yet
> > > > it accepts them.
> > > 
> > > Fair enough, that makes sense.  Thanks for the pointer!
> > > 
> > > There's one more thing that I noticed here:
> > > 
> > >     git range-diff --no-patches
> > >     fatal: single arg format requires a symmetric range
> > > 
> > > Which is a slightly confusing error message.  In contrast git log does
> > > the following on an unrecognized argument:
> > > 
> > >     git log --no-patches
> > >     fatal: unrecognized argument: --no-patches
> > > 
> > > which is a little better I think.  I do however also thing the "fatal:
> > > single arg format requires a symmetric range" is useful when someone
> > > genuinely tries to use the single argument version of the command.  So
> > > I don't know what a good solution for this would be.
> > 
> > I immediately thought of testing for a leading `-` of the remaining
> > argument, but I could imagine that somebody enterprisey uses
> > 
> > 	git range-diff -- -my-first-attempt...-my-second-attempt
> > 
> > and I do not really want to complexify the code... Ideas?
> 
> Good point.  I can't really come up with a good option right now
> either.  It's not too bad, as users just typed the command, so it
> should be easy enough to see from the previous line what went wrong.
> 
> One potential option may be to turn "die(_("single arg format requires
> a symmetric range"));" into an 'error()', and show the usage?  I think
> that may be nice anyway, as "symmetric range" may not be immediately
> obvious to everyone, but together with the usage it may be clearer?

Agreed. Will be made so.

Ciao,
Dscho

> 
> > > > > > diff --git a/range-diff.c b/range-diff.c
> > > > > > @@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)
> > > > > >                       printf("%d: %s ! %d: %s\n",
> > > > > >                              b_util->matching + 1, short_oid(a_util),
> > > > > >                              j + 1, short_oid(b_util));
> > > > > > +                     if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
> > > > >
> > > > > Looking at this line, it looks like it would be easy to support
> > > > > '--no-patches' as well, which may be slightly easier to understand that
> > > > > '-s' to someone new to the command.  But again that can be added later
> > > > > if someone actually cares about it.
> > > > 
> > > > What wasn't mentioned (but was implied) by the commit message is that
> > > > "-s" is short for "--no-patch", which also comes for free via
> > > > diff_opt_parse(). True, "--no-patch" isn't spelled exactly the same as
> > > > "--no-patches", but git-range-diff isn't exactly a perfect tbdiff
> > > > clone, so hopefully not a git problem. Moreover, "--no-patch" is
> > > > internally consistent within the Git builtin commands.
> > > 
> > > Makes sense, thanks!  "--no-patch" does make sense to me.  There's
> > > still a lot of command line flags in git to learn for me, even after
> > > all this time using it ;)  Might be nice to spell it out in the commit
> > > message for someone like me, especially as "--no-patches" is already
> > > mentioned.  Though I guess most regulars here would know about
> > > "--no-patch", so maybe it's not worth it.  Anyway that is definitely
> > > not worth another round here.
> > 
> > Sure, but not many users learn from reading the commit history...
> > 
> > :-)
> > 
> > Ciao,
> > Dscho
> 

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 03/21] range-diff: first rudimentary implementation
  2018-07-30 21:16             ` Thomas Gummerer
@ 2018-08-10 20:50               ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-08-10 20:50 UTC (permalink / raw)
  To: Thomas Gummerer; +Cc: Johannes Schindelin via GitGitGadget, git, Junio C Hamano

Hi Thomas,

On Mon, 30 Jul 2018, Thomas Gummerer wrote:

> On 07/30, Johannes Schindelin wrote:
> > 
> > On Sun, 29 Jul 2018, Thomas Gummerer wrote:
> > 
> > > On 07/21, Johannes Schindelin via GitGitGadget wrote:
> > > > 
> > > > [...]
> > > > 
> > > > +static void find_exact_matches(struct string_list *a, struct string_list *b)
> > > > +{
> > > > +	struct hashmap map;
> > > > +	int i;
> > > > +
> > > > +	hashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);
> > > > +
> > > > +	/* First, add the patches of a to a hash map */
> > > > +	for (i = 0; i < a->nr; i++) {
> > > > +		struct patch_util *util = a->items[i].util;
> > > > +
> > > > +		util->i = i;
> > > > +		util->patch = a->items[i].string;
> > > > +		util->diff = util->patch + util->diff_offset;
> > > > +		hashmap_entry_init(util, strhash(util->diff));
> > > > +		hashmap_add(&map, util);
> > > > +	}
> > > > +
> > > > +	/* Now try to find exact matches in b */
> > > > +	for (i = 0; i < b->nr; i++) {
> > > > +		struct patch_util *util = b->items[i].util, *other;
> > > > +
> > > > +		util->i = i;
> > > > +		util->patch = b->items[i].string;
> > > > +		util->diff = util->patch + util->diff_offset;
> > > > +		hashmap_entry_init(util, strhash(util->diff));
> > > > +		other = hashmap_remove(&map, util, NULL);
> > > > +		if (other) {
> > > > +			if (other->matching >= 0)
> > > > +				BUG("already assigned!");
> > > > +
> > > > +			other->matching = i;
> > > > +			util->matching = other->i;
> > > > +		}
> > > > +	}
> > > 
> > > One possibly interesting corner case here is what happens when there
> > > are two patches that have the exact same diff, for example in the
> > > pathological case of commit A doing something, commit B reverting
> > > commit A, and then commit C reverting commit B, so it ends up with the
> > > same diff.
> > > 
> > > Having those same commits unchanged in both ranges (e.g. if a commit
> > > earlier in the range has been changed, and range B has been rebased on
> > > top of that), we'd get the following mapping from range A to range B
> > > for the commits in question:
> > > 
> > > A -> C
> > > B -> B
> > > C -> A
> > > 
> > > Which is not quite what I would expect as the user (even though it is
> > > a valid mapping, and it probably doesn't matter too much for the end
> > > result of the range diff, as nothing has changed between the commits
> > > anyway).  So I'm not sure it's worth fixing this, as it is a
> > > pathological case, and nothing really breaks.
> > 
> > Indeed. As far as I am concerned, this falls squarely into the "let's
> > cross that bridge when, or if, we reach it" category.
> 
> Makes sense, this can definitely be addressed later.
> 
> > > > +
> > > > +	hashmap_free(&map, 0);
> > > > +}
> > > > +
> > > > +static void diffsize_consume(void *data, char *line, unsigned long len)
> > > > +{
> > > > +	(*(int *)data)++;
> > > > +}
> > > > +
> > > > +static int diffsize(const char *a, const char *b)
> > > > +{
> > > > +	xpparam_t pp = { 0 };
> > > > +	xdemitconf_t cfg = { 0 };
> > > > +	mmfile_t mf1, mf2;
> > > > +	int count = 0;
> > > > +
> > > > +	mf1.ptr = (char *)a;
> > > > +	mf1.size = strlen(a);
> > > > +	mf2.ptr = (char *)b;
> > > > +	mf2.size = strlen(b);
> > > > +
> > > > +	cfg.ctxlen = 3;
> > > > +	if (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))
> > > > +		return count;
> > > > +
> > > > +	error(_("failed to generate diff"));
> > > > +	return COST_MAX;
> > > > +}
> > > > +
> > > > +static void get_correspondences(struct string_list *a, struct string_list *b,
> > > > +				int creation_factor)
> > > > +{
> > > > +	int n = a->nr + b->nr;
> > > > +	int *cost, c, *a2b, *b2a;
> > > > +	int i, j;
> > > > +
> > > > +	ALLOC_ARRAY(cost, st_mult(n, n));
> > > > +	ALLOC_ARRAY(a2b, n);
> > > > +	ALLOC_ARRAY(b2a, n);
> > > > +
> > > > +	for (i = 0; i < a->nr; i++) {
> > > > +		struct patch_util *a_util = a->items[i].util;
> > > > +
> > > > +		for (j = 0; j < b->nr; j++) {
> > > > +			struct patch_util *b_util = b->items[j].util;
> > > > +
> > > > +			if (a_util->matching == j)
> > > > +				c = 0;
> > > > +			else if (a_util->matching < 0 && b_util->matching < 0)
> > > > +				c = diffsize(a_util->diff, b_util->diff);
> > > > +			else
> > > > +				c = COST_MAX;
> > > > +			cost[i + n * j] = c;
> > > > +		}
> > > > +
> > > > +		c = a_util->matching < 0 ?
> > > > +			a_util->diffsize * creation_factor / 100 : COST_MAX;
> > > > +		for (j = b->nr; j < n; j++)
> > > > +			cost[i + n * j] = c;
> > > > +	}
> > > > +
> > > > +	for (j = 0; j < b->nr; j++) {
> > > > +		struct patch_util *util = b->items[j].util;
> > > > +
> > > > +		c = util->matching < 0 ?
> > > > +			util->diffsize * creation_factor / 100 : COST_MAX;
> > > > +		for (i = a->nr; i < n; i++)
> > > > +			cost[i + n * j] = c;
> > > > +	}
> > > > +
> > > > +	for (i = a->nr; i < n; i++)
> > > > +		for (j = b->nr; j < n; j++)
> > > > +			cost[i + n * j] = 0;
> > > > +
> > > > +	compute_assignment(n, n, cost, a2b, b2a);
> > > > +
> > > > +	for (i = 0; i < a->nr; i++)
> > > > +		if (a2b[i] >= 0 && a2b[i] < b->nr) {
> > > > +			struct patch_util *a_util = a->items[i].util;
> > > > +			struct patch_util *b_util = b->items[a2b[i]].util;
> > > > +
> > > > +			a_util->matching = a2b[i];
> > > > +			b_util->matching = i;
> > > 
> > > So here we re-assign 'matching' in the struct regardless of whether it
> > > was assigned before while searching for exact matches or not.
> > 
> > If `matching` were assigned here, it would indicate a bug in the code, I
> > think. Before this loop, all of the `matching` fields should be `NULL`,
> > and the `compute_assignment()` function is expected to populate `a2b` and
> > `b2a` appropriately, i.e. no "double booking".
> 
> Hmm we are using the 'matching' fields in the loops above, e.g.:
> 
> 	if (a_util->matching == j)
> 		c = 0;
> 
> They're also ints, so they can't be `NULL`, right?

Right, I was confused, they are not pointers. They are initialized to
`-1`, though.

> So what I was thinking is that we could do
> 
> 	 if (a_util->matching < 0) {
> 		a_util->matching = a2b[i];
> 		b_util->matching = i;
> 	}

Yes, we could have that safeguard. But then, we should actually rather say

	if (a_util->matching >= 0)
		BUG("a[%d] already assigned to %d, cannot assign to %d",
		    i, a_util->matching, a2b[i]);

The reason this is a bug is that we really just calculated a linear
assignment, and a2b was initialized accordingly: it contains a 1:1 mapping
between a->items and b->items.

Side note: technically, the linear assignment is able to handle two
differently-sized sets. However, due to implementation details, we do not
use this, as a->nr and b->nr are both set to `n`. The reason is that we
want to avoid forcing matches where there are none, i.e. we add specific
entries to the cost matrix to allow for the "creation" or "destruction" if
that is cheaper than to match two completely unrelated commits. It just
so happens that these filler entries result in identical set sizes.

So practically, that `if()` guard is not necessary, and honestly, I would
stumble over it every time ("Why is this needed? Should `a2b` and `b2a`
not have a complete assignment already?")), so I'd rather not have it.

> Anyway, I don't think it really matters, as there is no "double
> booking", so we should essentially get the same results.

Exactly.

If you were worried about it, I would introduce that guarded BUG(), but it
is not an off-by-one error, so I probably did not introduce a bug here.

> Glad I could help.  Can I have more hours in a day? ;) I wish I had
> more time for reviewing code, but sadly the time I have is limited.
> Which is why I only got to review this now as well.  I did enjoy the
> read!

Thank you so much! I appreciate your help, and it was a very pleasant and
useful review that really made the patch series better. There were three
distinct issues that would have otherwise been missed before this hit
`next`.

Thanks!
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 09/21] range-diff: adjust the output of the commit pairs
  2018-07-29 19:38         ` Thomas Gummerer
@ 2018-08-10 21:01           ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-08-10 21:01 UTC (permalink / raw)
  To: Thomas Gummerer; +Cc: Johannes Schindelin via GitGitGadget, git, Junio C Hamano

Hi Thomas,

On Sun, 29 Jul 2018, Thomas Gummerer wrote:

> On 07/21, Johannes Schindelin via GitGitGadget wrote:
> > From: Johannes Schindelin <johannes.schindelin@gmx.de>
> > 
> > This change brings `git range-diff` yet another step closer to
> > feature parity with tbdiff: it now shows the oneline, too, and indicates
> > with `=` when the commits have identical diffs.
> > 
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> >  range-diff.c | 64 ++++++++++++++++++++++++++++++++++++++++++++--------
> >  1 file changed, 55 insertions(+), 9 deletions(-)
> > 
> > diff --git a/range-diff.c b/range-diff.c
> > index 1ecee2c09..8329f52e7 100644
> > --- a/range-diff.c
> > +++ b/range-diff.c
> > @@ -7,6 +7,8 @@
> >  #include "xdiff-interface.h"
> >  #include "linear-assignment.h"
> >  #include "diffcore.h"
> > +#include "commit.h"
> > +#include "pretty.h"
> >  
> >  struct patch_util {
> >  	/* For the search for an exact match */
> > @@ -255,9 +257,54 @@ static void get_correspondences(struct string_list *a, struct string_list *b,
> >  	free(b2a);
> >  }
> >  
> > -static const char *short_oid(struct patch_util *util)
> > +static void output_pair_header(struct strbuf *buf,
> > +			       struct strbuf *dashes,
> > +			       struct patch_util *a_util,
> > +			       struct patch_util *b_util)
> >  {
> > -	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
> > +	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
> > +	struct commit *commit;
> > +
> > +	if (!dashes->len)
> > +		strbuf_addchars(dashes, '-',
> > +				strlen(find_unique_abbrev(oid,
> > +							  DEFAULT_ABBREV)));
> 
> We're doing this only once, which makes sense.  What's a bit
> unfortunate here I guess is that if the first commit we're dealing
> with in the range-diff has a longer unique abbreviation, the dashes
> will be longer for all commits, even if all the others have a shorter
> abbreviation.
> 
> Tbh I don't really know what the right thing to do here is, so this is
> probably as good a heuristic as any.  It would probably be worse to
> have different length dashes lines, than guessing based on the first
> commit.

Yes, I had the same reaction as you did. But still, I think it is the best
we can do, and I don't think it is worth spending a lot of thought about
ways to fix this, up and until the day when somebody experiences a real
problem there (and that it will be *their* responsibility to fix it).

> > +
> > +	strbuf_reset(buf);
> > +	if (!a_util)
> > +		strbuf_addf(buf, "-:  %s ", dashes->buf);
> > +	else
> > +		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
> > +			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
> > +
> > +	if (!a_util)
> > +		strbuf_addch(buf, '>');
> > +	else if (!b_util)
> > +		strbuf_addch(buf, '<');
> > +	else if (strcmp(a_util->patch, b_util->patch))
> > +		strbuf_addch(buf, '!');
> > +	else
> > +		strbuf_addch(buf, '=');
> > +
> > +	if (!b_util)
> > +		strbuf_addf(buf, " -:  %s", dashes->buf);
> > +	else
> > +		strbuf_addf(buf, " %d:  %s", b_util->i + 1,
> > +			    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));
> > +
> > +	commit = lookup_commit_reference(oid);
> 
> This bit surprised me slightly.  May be worth mentioning that we now
> also show the first line of the commit message here.

Right...

> 
> > +	if (commit) {
> > +		const char *commit_buffer = get_commit_buffer(commit, NULL);
> > +		const char *subject;
> > +
> > +		find_commit_subject(commit_buffer, &subject);
> > +		strbuf_addch(buf, ' ');
> > +		format_subject(buf, subject, " ");
> > +		unuse_commit_buffer(commit, commit_buffer);
> 
> I think the above could be written slightly shorter as
> 
>     strbuf_addch(buf, ' ');
>     pp_commit_easy(CMIT_FMT_ONELINE, commit, &buf);

I guess so. I shied away from the pretty-printing machinery because last
time I tried to use it in a libified manner I had to put in a major fight
to get the code into git.git. But I guess that was because of a user
format (which uses global state, something I still would like to fix, but
that fight just cost me too much time), which is not the case here.

> Not sure if it's worth changing this at this stage of the series
> though, or if there is something in the above that I'm missing, that
> would make the shorter version not workable.

I think your version is not only shorter, but also possibly safer.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 10/21] range-diff: do not show "function names" in hunk headers
  2018-07-29 20:52         ` Thomas Gummerer
@ 2018-08-10 21:03           ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-08-10 21:03 UTC (permalink / raw)
  To: Thomas Gummerer; +Cc: Johannes Schindelin via GitGitGadget, git, Junio C Hamano

Hi Thomas,

On Sun, 29 Jul 2018, Thomas Gummerer wrote:

> On 07/21, Johannes Schindelin via GitGitGadget wrote:
> > From: Johannes Schindelin <johannes.schindelin@gmx.de>
> > 
> > We are comparing complete, formatted commit messages with patches. There
> > are no function names here, so stop looking for them.
> 
> While there are no function names here, trying out range-diff without
> this patch applied, the headers were getting here do seem kind of
> useful:
> 
>     1: 92588fc6b6 ! 3: 43c9ef552c
>         @@ -8,8 +8,16 @@ diff --git a/read-cache.c b/read-cache.c
>     	[...]
> 
> The filename can be quite useful in this output.  I guess this is a
> bit brittle though, so I'm also happy to defer changing this to show
> something useful to the list of possible future enhancements
> (obviously doesn't necessarily have to be implemented by you at that
> point).

To be honest, I never thought about this, I just assumed that tbdiff had
good reasons to strip this.

I agree, though, that we (as in: you :-)) can look at this after
`range-diff` lands in `next`.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 16/21] range-diff --dual-color: fix bogus white-space warning
  2018-07-23 22:20         ` Stefan Beller
@ 2018-08-10 21:05           ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-08-10 21:05 UTC (permalink / raw)
  To: Stefan Beller; +Cc: gitgitgadget, git, Junio C Hamano

Hi Stefan,

On Mon, 23 Jul 2018, Stefan Beller wrote:

> On Sat, Jul 21, 2018 at 3:05 PM Johannes Schindelin via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
> >
> > From: Johannes Schindelin <johannes.schindelin@gmx.de>
> >
> > When displaying a diff of diffs, it is possible that there is an outer
> > `+` before a context line. That happens when the context changed between
> > old and new commit. When that context line starts with a tab (after the
> > space that marks it as context line), our diff machinery spits out a
> > white-space error (space before tab), but in this case, that is
> > incorrect.
> >
> > Fix this by adding a specific whitespace flag that simply ignores the
> > first space in the output.
> 
> That sounds like a simple (not easy) solution, which sounds acceptable
> to me here.
> 
> I guess you dropped all ideas that I originally proposed for the cleanup
> regarding ws. that is fine, I can roll the cleanup on top of your patches
> here.

Yes, sorry, I got the impression after our chat on IRC that you tried to
address something different from what I needed, anyway?

> > Note: as the original code did not leave any space in the bit mask
> > before the WSEH_* bits, the diff of this commit looks unnecessarily
> > involved: the diff is dominated by making room for one more bit to be
> > used by the whitespace rules.
> 
> It took me some minutes, but I am reasonably convinced this patch
> is correct (and doesn't collide with other series in flight, sb/diff-color-more
> adds another flag to move detection in another bit field at (1<<23))
> 
> Thanks for writing this patch instead of the other, though I'll leave
> it to Junio to weigh in if this approach is the best design.

I am sorry that your time was wasted in addition to mine: I will go with a
simple one-line patch in v5 instead, a single line that simply disables
white-space errors altogether in dual color mode.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 17/21] range-diff: populate the man page
  2018-07-29 21:23         ` Thomas Gummerer
@ 2018-08-10 21:06           ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-08-10 21:06 UTC (permalink / raw)
  To: Thomas Gummerer; +Cc: Johannes Schindelin via GitGitGadget, git, Junio C Hamano

Hi Thomas,

On Sun, 29 Jul 2018, Thomas Gummerer wrote:

> On 07/21, Johannes Schindelin via GitGitGadget wrote:
> > From: Johannes Schindelin <johannes.schindelin@gmx.de>
> > 
> >  Documentation/git-range-diff.txt | 229 +++++++++++++++++++++++++++++++
> >  1 file changed, 229 insertions(+)
> > 
> > [...]
> >
> > +CONFIGURATION
> > +-------------
> > +This command uses the `diff.color.*` and `pager.range-diff` settings
> > +(the latter is on by default).
> > +See linkgit:git-config[1].
> 
> Would it be worth implementing a `rangeDiff.dualColor` configuration
> at some point?  Dual color mode seems like something I would like to
> have on by default, even if we are not making it the default for the
> command itself.
> 
> (Again this is something that can be a future enhancement).

Sure, go wild!

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 20/21] range-diff: make --dual-color the default mode
  2018-07-29 21:33         ` Thomas Gummerer
@ 2018-08-10 21:07           ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-08-10 21:07 UTC (permalink / raw)
  To: Thomas Gummerer; +Cc: Johannes Schindelin via GitGitGadget, git, Junio C Hamano

Hi Thomas,

On Sun, 29 Jul 2018, Thomas Gummerer wrote:

> On 07/21, Johannes Schindelin via GitGitGadget wrote:
> > From: Johannes Schindelin <johannes.schindelin@gmx.de>
> > 
> > After using this command extensively for the last two months, this
> > developer came to the conclusion that even if the dual color mode still
> > leaves a lot of room for confusion about what was actually changed, the
> > non-dual color mode is substantially worse in that regard.
> > 
> > Therefore, we really want to make the dual color mode the default.
> 
> Ah and here we're making it default, so I wouldn't need a
> `rangeDiff.dualColor` config variable anymore.  Even better!

... except if you want to switch it off ;-)

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 05/21] range-diff: also show the diff between patches
  2018-07-30 21:51                   ` Eric Sunshine
@ 2018-08-10 21:12                     ` Johannes Schindelin
  2018-08-10 21:31                       ` Eric Sunshine
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-08-10 21:12 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: Thomas Gummerer, gitgitgadget, Git List, Junio C Hamano

Hi Eric,

On Mon, 30 Jul 2018, Eric Sunshine wrote:

> On Mon, Jul 30, 2018 at 5:26 PM Thomas Gummerer <t.gummerer@gmail.com> wrote:
> > On 07/30, Johannes Schindelin wrote:
> > > On Sun, 29 Jul 2018, Thomas Gummerer wrote:
> > > > There's one more thing that I noticed here:
> > > >
> > > >     git range-diff --no-patches
> > > >     fatal: single arg format requires a symmetric range
> > > >
> > > I immediately thought of testing for a leading `-` of the remaining
> > > argument, but I could imagine that somebody enterprisey uses
> > >
> > >       git range-diff -- -my-first-attempt...-my-second-attempt
> > >
> > > and I do not really want to complexify the code... Ideas?
> >
> > Good point.  I can't really come up with a good option right now
> > either.  It's not too bad, as users just typed the command, so it
> > should be easy enough to see from the previous line what went wrong.
> 
> I think you can attain the desired behavior by making a final
> parse_options() call with empty 'options' list after the call to
> diff_setup_done(). It's pretty much a one-line fix, but can probably
> be done as an incremental change rather than rerolling.

But then we would have to keep `--` in the first, and not in the second
parse_options() call, right? We would also have to handle that `--`
properly in the loop that calls diff_opt_parse(), I think.

A bit more involved than just a one-line fix, but I guess I'll give it a
try.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike
  2018-08-08 17:33           ` Stefan Beller
@ 2018-08-10 21:18             ` Johannes Schindelin
  2018-08-10 21:31               ` Junio C Hamano
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-08-10 21:18 UTC (permalink / raw)
  To: Stefan Beller; +Cc: gitgitgadget, git, Junio C Hamano

Hi Stefan,

On Wed, 8 Aug 2018, Stefan Beller wrote:

> On Wed, Aug 8, 2018 at 6:05 AM Johannes Schindelin
> <Johannes.Schindelin@gmx.de> wrote:
> 
> > > [...]
> > > >       diff --git a/Makefile b/Makefile
> > > >       --- a/Makefile
> > > >       +++ b/Makefile
> > >
> > > The line starting with --- is red (default removed color) and the line
> > > with +++ is green (default add color).
> > >
> > > Ideally these two lines and the third line above starting with "diff --git"
> > > would render in GIT_COLOR_BOLD ("METAINFO").
> >
> > I agree that is not the best coloring here, but as you remarked elsewhere,
> > it would require content-aware dual coloring, and I am loathe to try to
> > implement that for two reasons: 1) it would take most likely a long time
> > to design and implement that, and 2) I don't have that time.
> >
> > So I would like to declare that good enough is good enough in this case.
> 
> I anticipated this answer, so I wrote some patches myself, starting at
> https://public-inbox.org/git/20180804015317.182683-1-sbeller@google.com/
> specifically
> https://public-inbox.org/git/20180804015317.182683-5-sbeller@google.com/
> 
> I plan on resending these on top of your resend (if any) at a later convenient
> time for both you and Junio, as noted in
> https://public-inbox.org/git/CAGZ79kZnVEsvpicNu7LXkRcHuRqGvESfvG3DL5O_2kPVYrW-Gg@mail.gmail.com/

Thank you!

> > > >   3:  076e1192d !  3:  4e3fb47a1 range-diff: first rudimentary implementation
> > > >      @@ -4,7 +4,7 @@
> > > >
> > > >           At this stage, `git range-diff` can determine corresponding commits
> > > >           of two related commit ranges. This makes use of the recently introduced
> > > >      -    implementation of the Hungarian algorithm.
> > > >      +    implementation of the linear assignment algorithm.
> > > >
> > > >           The core of this patch is a straight port of the ideas of tbdiff, the
> > > >           apparently dormant project at https://github.com/trast/tbdiff.
> > > >      @@ -51,19 +51,17 @@
> > > >       + int res = 0;
> > > >       + struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
> > > >
> > > >      -- argc = parse_options(argc, argv, NULL, options,
> > > >      --                      builtin_range_diff_usage, 0);
> > > >      -+ argc = parse_options(argc, argv, NULL, options, builtin_range_diff_usage,
> > > >      -+                      0);
> > > >      +  argc = parse_options(argc, argv, NULL, options,
> > > >      +                       builtin_range_diff_usage, 0);
> > >
> > > This is really nice in colors when viewed locally.
> > >
> > > >  16:  dfa7b1e71 <  -:  --------- range-diff --dual-color: work around bogus white-space warning
> > > >   -:  --------- > 16:  f4252f2b2 range-diff --dual-color: fix bogus white-space warning
> > >
> > > Ah; here my initial assumption of only reviewing the range-diff breaks down now.
> > > I'll dig into patch 16 separately.
> >
> > Right. This was an almost complete rewrite, and then next iteration will
> > hopefully bring another complete rewrite: disabling whitespace warnings in
> > dual color mode.
> >
> > > Maybe it is worth having an option to expand all "new" patches.
> >
> > Sure.
> >
> > And I also have a use case for --left-only/--right-only.
> >
> > And I also have a strong use case (and so does Junio, it seems, or for
> > that matter, anybody contributing to Git due to Junio's insistence on
> > signing off on each patch, rather than on the merge commit) for something
> > like --ignore-lines=<regex>.
> >
> > And you probably guess what I will say next: these features will make for
> > really fantastic patch series *on top* of mine. There really is no good
> > reason to delay the current patch series just to cram more features into
> > it that had not been planned in the first place.
> 
> Yes, I agree.

I am happy to hear that.

> I am unsure about the current state of your series, though;
> 
> Junio thinks (expects?) a resend, whereas you seem to call it good enough
> but also said (some time back) that you want to resend due to Thomas
> feedback.

Sorry about being so unclear. When time gets scarce, sometimes I get too
stressed (and too short on time) to communicate properly.

Yes, I want to limit the new features put into this patch series, in the
interest of getting things into `next` (and maybe still into `master`
before v2.19, but I am not allowing myself to hope for that too much).

And yes, I want to send another iteration, as there have been too many
changes that I do not want to ask Junio to touch up, in particular because
I am a little bit of a detail-oriented person and want my fixes just so.

> I do have 2 series on top of the current range-diff.
> * The first (queued by Junio as origin/sb/range-diff-colors)
>    adds a basic test for colors and improves diff.c readability
> * The second (linked above) changes colors for some lines.
> 
> I do not want to build more on top as long as I do not know if
> you resend (and how much it'll change)

It will not change much. The biggest change is that the white-space
warning thing is done completely differently, so that I do not even have
to be in ws.c's author list.

I'll just try to get that option parsing change in that Eric suggested,
force-push, then wait for macOS and Linux builds to pass (trusting that
Windows will follow suite) and hit /submit.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 05/21] range-diff: also show the diff between patches
  2018-08-10 21:12                     ` Johannes Schindelin
@ 2018-08-10 21:31                       ` Eric Sunshine
  2018-08-10 22:02                         ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Eric Sunshine @ 2018-08-10 21:31 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Thomas Gummerer, gitgitgadget, Git List, Junio C Hamano

On Fri, Aug 10, 2018 at 5:12 PM Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:
> On Mon, 30 Jul 2018, Eric Sunshine wrote:
> > I think you can attain the desired behavior by making a final
> > parse_options() call with empty 'options' list after the call to
> > diff_setup_done(). It's pretty much a one-line fix, but can probably
> > be done as an incremental change rather than rerolling.
>
> But then we would have to keep `--` in the first, and not in the second
> parse_options() call, right? We would also have to handle that `--`
> properly in the loop that calls diff_opt_parse(), I think.
> A bit more involved than just a one-line fix, but I guess I'll give it a
> try.

It's something that could easily wait until after this series lands.
After all, it's just a slightly confusing error message, not some
fundamental problem.

As for '--', I'll have to go back and look at the code. I thought I
had thought it all through at the time I made the suggestion, but my
brain needs a refresh by now.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike
  2018-08-10 21:18             ` Johannes Schindelin
@ 2018-08-10 21:31               ` Junio C Hamano
  2018-08-10 22:00                 ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Junio C Hamano @ 2018-08-10 21:31 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Stefan Beller, gitgitgadget, git

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> I'll just try to get that option parsing change in that Eric suggested,
> force-push, then wait for macOS and Linux builds to pass (trusting that
> Windows will follow suite) and hit /submit.

OK.  Obviously receiving, applying and inspecting that result will
not be done in time for today's integration cycle, but having a
version that limits its cope and is suitable for 'next' is a good
thing to have at this point during the cycle.  Correct whitespace
error colouring, etc., can be done on top after the basics settle,
and the basics were good in a few versions ago already IIRC.


^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike
  2018-08-10 21:31               ` Junio C Hamano
@ 2018-08-10 22:00                 ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-08-10 22:00 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Stefan Beller, gitgitgadget, git

Hi Junio,

On Fri, 10 Aug 2018, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > I'll just try to get that option parsing change in that Eric suggested,
> > force-push, then wait for macOS and Linux builds to pass (trusting that
> > Windows will follow suite) and hit /submit.
> 
> OK.  Obviously receiving, applying and inspecting that result will
> not be done in time for today's integration cycle, but having a
> version that limits its cope and is suitable for 'next' is a good
> thing to have at this point during the cycle.  Correct whitespace
> error colouring, etc., can be done on top after the basics settle,
> and the basics were good in a few versions ago already IIRC.

No, a couple of issues were identified, still, that merited several dozen
rebases on my side.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v4 05/21] range-diff: also show the diff between patches
  2018-08-10 21:31                       ` Eric Sunshine
@ 2018-08-10 22:02                         ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-08-10 22:02 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: Thomas Gummerer, gitgitgadget, Git List, Junio C Hamano

Hi Eric,

On Fri, 10 Aug 2018, Eric Sunshine wrote:

> On Fri, Aug 10, 2018 at 5:12 PM Johannes Schindelin
> <Johannes.Schindelin@gmx.de> wrote:
> > On Mon, 30 Jul 2018, Eric Sunshine wrote:
> > > I think you can attain the desired behavior by making a final
> > > parse_options() call with empty 'options' list after the call to
> > > diff_setup_done(). It's pretty much a one-line fix, but can probably
> > > be done as an incremental change rather than rerolling.
> >
> > But then we would have to keep `--` in the first, and not in the second
> > parse_options() call, right? We would also have to handle that `--`
> > properly in the loop that calls diff_opt_parse(), I think.
> > A bit more involved than just a one-line fix, but I guess I'll give it a
> > try.
> 
> It's something that could easily wait until after this series lands.
> After all, it's just a slightly confusing error message, not some
> fundamental problem.
> 
> As for '--', I'll have to go back and look at the code. I thought I
> had thought it all through at the time I made the suggestion, but my
> brain needs a refresh by now.

Your suggestion might have started out by trying to fix the error message,
but after staring at the code for a couple of minutes, I think the issue
would have left all kinds of opportunities for bad user experience.

So I fixed it ;-)

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 387+ messages in thread

* [PATCH v5 00/21] Add range-diff, a tbdiff lookalike
  2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
                         ` (22 preceding siblings ...)
  2018-07-29 21:50       ` Thomas Gummerer
@ 2018-08-10 22:14       ` Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 01/21] linear-assignment: a function to solve least-cost assignment problems Johannes Schindelin via GitGitGadget
                           ` (21 more replies)
  23 siblings, 22 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

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.

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.

While at it, I forward-ported AEvar's patch to force --decorate=no because 
git -p tbdiff would fail otherwise.

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 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:

        base=^{/Start.the.merging-rebase}
        tag=v2.17.0.windows.1
        pre=$tag$base^2
        git range-diff $pre$base..$pre $tag$base..$tag

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.

Changes since v4:

 * Fixed a typo in the commit message of "range-diff: add tests" that was
   introduced in v4.
 * White-space fixes.
 * Fixed the length of the first header underline in the man page.
 * Changed the preprocessor guard in linear-assignment.h to reflect the new
   name (instead of the old name, which was hungarian.h).
 * Likewise, changed the preprocessor guards in range-diff.h to hide the
   history of the thrice-renamed command.
 * Fixed indentation in the completion.
 * Instead of trying to paper over white-space error handling that does not
   apply to "diffs of diffs", dual color mode now simply disables all
   white-space warnings.
 * When showing the "single arg must be symmetric range" error message, git
   range-diff now also shows the usage.
 * Adjusted the commit message of "range-diff: adjust the output of the
   commit pairs" to avoid the surprise of the reviewer when onelines are
   printed all of a sudden, too.
 * "range-diff: adjust the output of the commit pairs" is now using a
   simpler way to print onelines.
 * We are now sandwiching the diff_opt_parse() loop between two 
   parse_options(), to make sure that we caught all options, and that the -- 
   separator is handled.
 * Adjusted the lookup_commit_reference() call to the newest master (it now
   takes a the_repository parameter).

Changes since v3:

 * 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).
 * The documentation was adjusted a bit more in the patch that makes 
   --dual-color the default.
 * Clarified the calculation of the cost matrix, as per Stefan Beller's
   request.
 * The man page now spells out that merge commits are ignored in the commit
   ranges (not merges per se).
 * The code in linear-assignment.c was adjusted to use the SWAP() macro.
 * 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.
 * A bogus indentation change was backed out from the patch introducing the
   first rudimentary implementation.
 * Instead of merely warning about missing .. in the 2-parameter invocation,
   we now exit with the error message.
 * 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).
 * A lengthy if...else if...else if...else was simplified (from a logical
   point of view) by reordering it.
 * The unnecessarily static variable dashes was turned into a local variable
   of the caller.
 * The commit message talking about the new man page still referred to git
   branch --diff, which has been fixed.
 * A forgotten t7910 reference was changed to t3206.
 * An unbalanced double-tick was fixed in the man page.
 * Fixed grammar both of the commit message and the description of the 
   --no-dual-color option.
 * 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).
 * 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.
 * 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".

Changes since v2:

 * Right-aligned the patch numbers in the commit pairs.
 * Used ALLOC_ARRAY() in hungarian.c instead of xmalloc(sizeof()*size).
 * Changed compute_assignment()s return type from int to void, as it always
   succeeds.
 * Changed the Hungarian Algorithm to use an integer cost matrix.
 * Changed the --creation-weight option to --creation-factor where is an
   integer.
 * Retitled 1/19 and 2/19 to better conform with the current conventions, as
   pointed out (and suggested) by Junio.
 * Shut up Coverity, and at the same time avoided passing the unnecessary i 
   and j parameters to output_pair_header().
 * Removed support for the --no-patches option: we inherit diff_options'
   support for -s already (and much more).
 * 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.
 * Instead of overriding the fragment header color, the dual color mode will
   now reverse the "outer" fragment headers, too.
 * 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.
 * Pretty much re-wrote the completion, to support the new --diff mode of
   git-branch. See below: it was reverted for range-diff.
 * Renamed t7910 to t3206, to be closer to the git-branch tests.
 * Ensured that git_diff_ui_config() gets called, and therefore color.diff.*
   respected.
 * Avoided leaking four_spaces.
 * Fixed a declaration in a for (;;) statement (which Junio had as a fixup!
   that I almost missed).
 * Renamed branch --diff, which had been renamed from branch-diff (which was
   picked to avoid re-using tbdiff) to range-diff.
 * Renamed hungarian.c and its header to linear-assignment.c
 * Made --dual-color the default, and changed it to still auto-detect
   whether color should be used rather than forcing it

Johannes Schindelin (20):
  linear-assignment: a function to solve least-cost assignment problems
  Introduce `range-diff` to compare iterations of a topic branch
  range-diff: first rudimentary implementation
  range-diff: improve the order of the shown commits
  range-diff: also show the diff between patches
  range-diff: right-trim commit messages
  range-diff: indent the diffs just like tbdiff
  range-diff: suppress the diff headers
  range-diff: adjust the output of the commit pairs
  range-diff: do not show "function names" in hunk headers
  range-diff: use color for the commit pairs
  color: add the meta color GIT_COLOR_REVERSE
  diff: add an internal option to dual-color diffs of diffs
  range-diff: offer to dual-color the diffs
  range-diff --dual-color: skip white-space warnings
  range-diff: populate the man page
  completion: support `git range-diff`
  range-diff: left-pad patch numbers
  range-diff: make --dual-color the default mode
  range-diff: use dim/bold cues to improve dual color mode

Thomas Rast (1):
  range-diff: add tests

 .gitignore                             |   1 +
 Documentation/config.txt               |   6 +-
 Documentation/git-range-diff.txt       | 252 +++++++++++
 Makefile                               |   3 +
 builtin.h                              |   1 +
 builtin/range-diff.c                   | 114 +++++
 color.h                                |   7 +
 command-list.txt                       |   1 +
 contrib/completion/git-completion.bash |  14 +
 diff.c                                 | 105 ++++-
 diff.h                                 |  10 +-
 git.c                                  |   1 +
 linear-assignment.c                    | 201 ++++++++
 linear-assignment.h                    |  22 +
 range-diff.c                           | 435 ++++++++++++++++++
 range-diff.h                           |   9 +
 t/.gitattributes                       |   1 +
 t/t3206-range-diff.sh                  | 145 ++++++
 t/t3206/history.export                 | 604 +++++++++++++++++++++++++
 19 files changed, 1913 insertions(+), 19 deletions(-)
 create mode 100644 Documentation/git-range-diff.txt
 create mode 100644 builtin/range-diff.c
 create mode 100644 linear-assignment.c
 create mode 100644 linear-assignment.h
 create mode 100644 range-diff.c
 create mode 100644 range-diff.h
 create mode 100755 t/t3206-range-diff.sh
 create mode 100644 t/t3206/history.export


base-commit: 1d89318c48d233d52f1db230cf622935ac3c69fa
Published-As: https://github.com/gitgitgadget/git/releases/tags/pr-1%2Fdscho%2Fbranch-diff-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1/dscho/branch-diff-v5
Pull-Request: https://github.com/gitgitgadget/git/pull/1

Range-diff vs v4:

  1:  f7e70689e !  1:  f168da3a3 linear-assignment: a function to solve least-cost assignment problems
     @@ -239,8 +239,8 @@
      --- /dev/null
      +++ b/linear-assignment.h
      @@
     -+#ifndef HUNGARIAN_H
     -+#define HUNGARIAN_H
     ++#ifndef LINEAR_ASSIGNMENT_H
     ++#define LINEAR_ASSIGNMENT_H
      +
      +/*
      + * Compute an assignment of columns -> rows (and vice versa) such that every
  2:  88134121d !  2:  33758f361 Introduce `range-diff` to compare iterations of a topic branch
     @@ -34,7 +34,7 @@
      +++ b/Documentation/git-range-diff.txt
      @@
      +git-range-diff(1)
     -+==================
     ++=================
      +
      +NAME
      +----
  3:  4e3fb47a1 !  3:  08b8c3fc4 range-diff: first rudimentary implementation
     @@ -70,8 +70,10 @@
      +		const char *b = strstr(argv[0], "..."), *a = argv[0];
      +		int a_len;
      +
     -+		if (!b)
     -+			die(_("single arg format requires a symmetric range"));
     ++		if (!b) {
     ++			error(_("single arg format must be symmetric range"));
     ++			usage_with_options(builtin_range_diff_usage, options);
     ++		}
      +
      +		a_len = (int)(b - a);
      +		if (!a_len) {
     @@ -418,8 +420,8 @@
      --- /dev/null
      +++ b/range-diff.h
      @@
     -+#ifndef BRANCH_DIFF_H
     -+#define BRANCH_DIFF_H
     ++#ifndef RANGE_DIFF_H
     ++#define RANGE_DIFF_H
      +
      +int show_range_diff(const char *range1, const char *range2,
      +		    int creation_factor);
  4:  47bee09b0 =  4:  7b9091968 range-diff: improve the order of the shown commits
  5:  94afaeaf2 !  5:  9e1e66007 range-diff: also show the diff between patches
     @@ -10,7 +10,7 @@
          diff which is the result of first reverting the old diff and then
          applying the new diff.
      
     -    Especially when rebasing often, an interdiff is often not feasible,
     +    Especially when rebasing frequently, an interdiff is often not feasible,
          though: if the old diff cannot be applied in reverse (due to a moving
          upstream), an interdiff can simply not be inferred.
      
     @@ -25,9 +25,17 @@
          This is left for a later commit.
      
          Note also: while tbdiff accepts the `--no-patches` option to suppress
     -    these diffs between patches, we prefer the `-s` option that is
     -    automatically supported via our use of diff_opt_parse().
     +    these diffs between patches, we prefer the `-s` (or `--no-patch`) option
     +    that is automatically supported via our use of diff_opt_parse().
      
     +    And finally note: to support diff options, we have to call
     +    `parse_options()` such that it keeps unknown options, and then loop over
     +    those and let `diff_opt_parse()` handle them. After that loop, we have
     +    to call `parse_options()` again, to make sure that no unknown options
     +    are left.
     +
     +    Helped-by: Thomas Gummerer <t.gummerer@gmail.com>
     +    Helped-by: Eric Sunshine <sunshine@sunshineco.com>
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
      diff --git a/builtin/range-diff.c b/builtin/range-diff.c
     @@ -61,10 +69,10 @@
      +	diffopt.output_format = DIFF_FORMAT_PATCH;
      +
       	argc = parse_options(argc, argv, NULL, options,
     --			     builtin_range_diff_usage, 0);
     -+			     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);
     ++			     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN |
     ++			     PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
      +
     -+	for (i = j = 0; i < argc; ) {
     ++	for (i = j = 1; i < argc && strcmp("--", argv[i]); ) {
      +		int c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);
      +
      +		if (!c)
     @@ -74,9 +82,13 @@
      +	}
      +	argc = j;
      +	diff_setup_done(&diffopt);
     ++
     ++	/* Make sure that there are no unparsed options */
     ++	argc = parse_options(argc, argv, NULL,
     ++			     options + ARRAY_SIZE(options) - 1, /* OPT_END */
     + 			     builtin_range_diff_usage, 0);
       
       	if (argc == 2) {
     - 		if (!strstr(argv[0], ".."))
      @@
       		usage_with_options(builtin_range_diff_usage, options);
       	}
     @@ -165,8 +177,8 @@
      --- a/range-diff.h
      +++ b/range-diff.h
      @@
     - #ifndef BRANCH_DIFF_H
     - #define BRANCH_DIFF_H
     + #ifndef RANGE_DIFF_H
     + #define RANGE_DIFF_H
       
      +#include "diff.h"
      +
  6:  41ab875a3 =  6:  167ca02a3 range-diff: right-trim commit messages
  7:  a3dd99509 !  7:  ca8de8c75 range-diff: indent the diffs just like tbdiff
     @@ -39,7 +39,7 @@
      +	diffopt.output_prefix_data = &four_spaces;
       
       	argc = parse_options(argc, argv, NULL, options,
     - 			     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN);
     + 			     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN |
      @@
       
       	strbuf_release(&range1);
  8:  61b2ff2f7 =  8:  eb94d1982 range-diff: suppress the diff headers
  9:  9641ab5c0 !  9:  6330afad9 range-diff: adjust the output of the commit pairs
     @@ -2,6 +2,10 @@
      
          range-diff: adjust the output of the commit pairs
      
     +    This not only uses "dashed stand-ins" for "pairs" where one side is
     +    missing (i.e. unmatched commits that are present only in one of the two
     +    commit ranges), but also adds onelines for the reader's pleasure.
     +
          This change brings `git range-diff` yet another step closer to
          feature parity with tbdiff: it now shows the oneline, too, and indicates
          with `=` when the commits have identical diffs.
     @@ -61,15 +65,10 @@
      +		strbuf_addf(buf, " %d:  %s", b_util->i + 1,
      +			    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));
      +
     -+	commit = lookup_commit_reference(oid);
     ++	commit = lookup_commit_reference(the_repository, oid);
      +	if (commit) {
     -+		const char *commit_buffer = get_commit_buffer(commit, NULL);
     -+		const char *subject;
     -+
     -+		find_commit_subject(commit_buffer, &subject);
      +		strbuf_addch(buf, ' ');
     -+		format_subject(buf, subject, " ");
     -+		unuse_commit_buffer(commit, commit_buffer);
     ++		pp_commit_easy(CMIT_FMT_ONELINE, commit, buf);
      +	}
      +	strbuf_addch(buf, '\n');
      +
 10:  0a52f8878 = 10:  c296675eb range-diff: do not show "function names" in hunk headers
 11:  2b8d09020 ! 11:  85e0ab82f range-diff: add tests
     @@ -3,7 +3,7 @@
          range-diff: add tests
      
          These are essentially lifted from https://github.com/trast/tbdiff, with
     -    light touch-ups to account for the command now being names `git
     +    light touch-ups to account for the command now being named `git
          range-diff`.
      
          Apart from renaming `tbdiff` to `range-diff`, only one test case needed
 12:  fb83ce71a ! 12:  f48b62644 range-diff: use color for the commit pairs
     @@ -79,16 +79,14 @@
       	if (!b_util)
       		strbuf_addf(buf, " -:  %s", dashes->buf);
      @@
     - 		const char *commit_buffer = get_commit_buffer(commit, NULL);
     - 		const char *subject;
       
     + 	commit = lookup_commit_reference(the_repository, oid);
     + 	if (commit) {
      +		if (status == '!')
      +			strbuf_addf(buf, "%s%s", color_reset, color);
      +
     - 		find_commit_subject(commit_buffer, &subject);
       		strbuf_addch(buf, ' ');
     - 		format_subject(buf, subject, " ");
     - 		unuse_commit_buffer(commit, commit_buffer);
     + 		pp_commit_easy(CMIT_FMT_ONELINE, commit, buf);
       	}
      -	strbuf_addch(buf, '\n');
      +	strbuf_addf(buf, "%s\n", color_reset);
 13:  9ccb9516a = 13:  1ad74f939 color: add the meta color GIT_COLOR_REVERSE
 14:  9de5bd229 = 14:  39a0ecd28 diff: add an internal option to dual-color diffs of diffs
 15:  21b2f9e4b ! 15:  c32a24f6a range-diff: offer to dual-color the diffs
     @@ -30,8 +30,8 @@
       	};
       	int i, j, res = 0;
      @@
     - 	argc = j;
     - 	diff_setup_done(&diffopt);
     + 			     options + ARRAY_SIZE(options) - 1, /* OPT_END */
     + 			     builtin_range_diff_usage, 0);
       
      +	if (dual_color) {
      +		diffopt.use_color = 1;
 16:  f4252f2b2 <  -:  --------- range-diff --dual-color: fix bogus white-space warning
  -:  --------- > 16:  05947781f range-diff --dual-color: skip white-space warnings
 17:  9e09c6be6 = 17:  3147c4440 range-diff: populate the man page
 18:  9b3632324 ! 18:  b08e6d937 completion: support `git range-diff`
     @@ -18,16 +18,16 @@
       
      +_git_range_diff ()
      +{
     -+  case "$cur" in
     -+  --*)
     -+          __gitcomp "
     -+	  	--creation-factor= --dual-color
     -+                  $__git_diff_common_options
     -+                  "
     -+          return
     -+          ;;
     -+  esac
     -+  __git_complete_revlist
     ++	case "$cur" in
     ++	--*)
     ++		__gitcomp "
     ++			--creation-factor= --dual-color
     ++			$__git_diff_common_options
     ++		"
     ++		return
     ++		;;
     ++	esac
     ++	__git_complete_revlist
      +}
      +
       _git_rebase ()
 19:  07ec215e8 ! 19:  19406283e range-diff: left-pad patch numbers
     @@ -48,7 +48,7 @@
      +		strbuf_addf(buf, " %*d:  %s", patch_no_width, b_util->i + 1,
       			    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));
       
     - 	commit = lookup_commit_reference(oid);
     + 	commit = lookup_commit_reference(the_repository, oid);
      @@
       		   struct diff_options *diffopt)
       {
 20:  b370468e7 ! 20:  6b3552386 range-diff: make --dual-color the default mode
     @@ -87,8 +87,8 @@
       		OPT_END()
       	};
      @@
     - 	argc = j;
     - 	diff_setup_done(&diffopt);
     + 			     options + ARRAY_SIZE(options) - 1, /* OPT_END */
     + 			     builtin_range_diff_usage, 0);
       
      -	if (dual_color) {
      -		diffopt.use_color = 1;
     @@ -104,11 +104,11 @@
      --- a/contrib/completion/git-completion.bash
      +++ b/contrib/completion/git-completion.bash
      @@
     -   case "$cur" in
     -   --*)
     -           __gitcomp "
     --	  	--creation-factor= --dual-color
     -+	  	--creation-factor= --no-dual-color
     -                   $__git_diff_common_options
     -                   "
     -           return
     + 	case "$cur" in
     + 	--*)
     + 		__gitcomp "
     +-			--creation-factor= --dual-color
     ++			--creation-factor= --no-dual-color
     + 			$__git_diff_common_options
     + 		"
     + 		return
 21:  d8498fb32 ! 21:  ccf8c1bb2 range-diff: use dim/bold cues to improve dual color mode
     @@ -144,7 +144,7 @@
      +				set = diff_get_color_opt(o, DIFF_FILE_NEW_BOLD);
      +			else
      +				set = diff_get_color_opt(o, DIFF_CONTEXT_BOLD);
     - 			flags |= WS_IGNORE_FIRST_SPACE;
     + 			flags &= ~DIFF_SYMBOL_CONTENT_WS_MASK;
       		}
       		emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
      @@

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 387+ messages in thread

* [PATCH v5 01/21] linear-assignment: a function to solve least-cost assignment problems
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 02/21] Introduce `range-diff` to compare iterations of a topic branch Johannes Schindelin via GitGitGadget
                           ` (20 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

The problem solved by the code introduced in this commit goes like this:
given two sets of items, and a cost matrix which says how much it
"costs" to assign any given item of the first set to any given item of
the second, assign all items (except when the sets have different size)
in the cheapest way.

We use the Jonker-Volgenant algorithm to solve the assignment problem to
answer questions such as: given two different versions of a topic branch
(or iterations of a patch series), what is the best pairing of
commits/patches between the different versions?

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Makefile            |   1 +
 linear-assignment.c | 201 ++++++++++++++++++++++++++++++++++++++++++++
 linear-assignment.h |  22 +++++
 3 files changed, 224 insertions(+)
 create mode 100644 linear-assignment.c
 create mode 100644 linear-assignment.h

diff --git a/Makefile b/Makefile
index bc4fc8eea..1af719b44 100644
--- a/Makefile
+++ b/Makefile
@@ -870,6 +870,7 @@ LIB_OBJS += gpg-interface.o
 LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hashmap.o
+LIB_OBJS += linear-assignment.o
 LIB_OBJS += help.o
 LIB_OBJS += hex.o
 LIB_OBJS += ident.o
diff --git a/linear-assignment.c b/linear-assignment.c
new file mode 100644
index 000000000..9b3e56e28
--- /dev/null
+++ b/linear-assignment.c
@@ -0,0 +1,201 @@
+/*
+ * Based on: Jonker, R., & Volgenant, A. (1987). <i>A shortest augmenting path
+ * algorithm for dense and sparse linear assignment problems</i>. Computing,
+ * 38(4), 325-340.
+ */
+#include "cache.h"
+#include "linear-assignment.h"
+
+#define COST(column, row) cost[(column) + column_count * (row)]
+
+/*
+ * The parameter `cost` is the cost matrix: the cost to assign column j to row
+ * i is `cost[j + column_count * i].
+ */
+void compute_assignment(int column_count, int row_count, int *cost,
+			int *column2row, int *row2column)
+{
+	int *v, *d;
+	int *free_row, free_count = 0, saved_free_count, *pred, *col;
+	int i, j, phase;
+
+	memset(column2row, -1, sizeof(int) * column_count);
+	memset(row2column, -1, sizeof(int) * row_count);
+	ALLOC_ARRAY(v, column_count);
+
+	/* column reduction */
+	for (j = column_count - 1; j >= 0; j--) {
+		int i1 = 0;
+
+		for (i = 1; i < row_count; i++)
+			if (COST(j, i1) > COST(j, i))
+				i1 = i;
+		v[j] = COST(j, i1);
+		if (row2column[i1] == -1) {
+			/* row i1 unassigned */
+			row2column[i1] = j;
+			column2row[j] = i1;
+		} else {
+			if (row2column[i1] >= 0)
+				row2column[i1] = -2 - row2column[i1];
+			column2row[j] = -1;
+		}
+	}
+
+	/* reduction transfer */
+	ALLOC_ARRAY(free_row, row_count);
+	for (i = 0; i < row_count; i++) {
+		int j1 = row2column[i];
+		if (j1 == -1)
+			free_row[free_count++] = i;
+		else if (j1 < -1)
+			row2column[i] = -2 - j1;
+		else {
+			int min = COST(!j1, i) - v[!j1];
+			for (j = 1; j < column_count; j++)
+				if (j != j1 && min > COST(j, i) - v[j])
+					min = COST(j, i) - v[j];
+			v[j1] -= min;
+		}
+	}
+
+	if (free_count ==
+	    (column_count < row_count ? row_count - column_count : 0)) {
+		free(v);
+		free(free_row);
+		return;
+	}
+
+	/* augmenting row reduction */
+	for (phase = 0; phase < 2; phase++) {
+		int k = 0;
+
+		saved_free_count = free_count;
+		free_count = 0;
+		while (k < saved_free_count) {
+			int u1, u2;
+			int j1 = 0, j2, i0;
+
+			i = free_row[k++];
+			u1 = COST(j1, i) - v[j1];
+			j2 = -1;
+			u2 = INT_MAX;
+			for (j = 1; j < column_count; j++) {
+				int c = COST(j, i) - v[j];
+				if (u2 > c) {
+					if (u1 < c) {
+						u2 = c;
+						j2 = j;
+					} else {
+						u2 = u1;
+						u1 = c;
+						j2 = j1;
+						j1 = j;
+					}
+				}
+			}
+			if (j2 < 0) {
+				j2 = j1;
+				u2 = u1;
+			}
+
+			i0 = column2row[j1];
+			if (u1 < u2)
+				v[j1] -= u2 - u1;
+			else if (i0 >= 0) {
+				j1 = j2;
+				i0 = column2row[j1];
+			}
+
+			if (i0 >= 0) {
+				if (u1 < u2)
+					free_row[--k] = i0;
+				else
+					free_row[free_count++] = i0;
+			}
+			row2column[i] = j1;
+			column2row[j1] = i;
+		}
+	}
+
+	/* augmentation */
+	saved_free_count = free_count;
+	ALLOC_ARRAY(d, column_count);
+	ALLOC_ARRAY(pred, column_count);
+	ALLOC_ARRAY(col, column_count);
+	for (free_count = 0; free_count < saved_free_count; free_count++) {
+		int i1 = free_row[free_count], low = 0, up = 0, last, k;
+		int min, c, u1;
+
+		for (j = 0; j < column_count; j++) {
+			d[j] = COST(j, i1) - v[j];
+			pred[j] = i1;
+			col[j] = j;
+		}
+
+		j = -1;
+		do {
+			last = low;
+			min = d[col[up++]];
+			for (k = up; k < column_count; k++) {
+				j = col[k];
+				c = d[j];
+				if (c <= min) {
+					if (c < min) {
+						up = low;
+						min = c;
+					}
+					col[k] = col[up];
+					col[up++] = j;
+				}
+			}
+			for (k = low; k < up; k++)
+				if (column2row[col[k]] == -1)
+					goto update;
+
+			/* scan a row */
+			do {
+				int j1 = col[low++];
+
+				i = column2row[j1];
+				u1 = COST(j1, i) - v[j1] - min;
+				for (k = up; k < column_count; k++) {
+					j = col[k];
+					c = COST(j, i) - v[j] - u1;
+					if (c < d[j]) {
+						d[j] = c;
+						pred[j] = i;
+						if (c == min) {
+							if (column2row[j] == -1)
+								goto update;
+							col[k] = col[up];
+							col[up++] = j;
+						}
+					}
+				}
+			} while (low != up);
+		} while (low == up);
+
+update:
+		/* updating of the column pieces */
+		for (k = 0; k < last; k++) {
+			int j1 = col[k];
+			v[j1] += d[j1] - min;
+		}
+
+		/* augmentation */
+		do {
+			if (j < 0)
+				BUG("negative j: %d", j);
+			i = pred[j];
+			column2row[j] = i;
+			SWAP(j, row2column[i]);
+		} while (i1 != i);
+	}
+
+	free(col);
+	free(pred);
+	free(d);
+	free(v);
+	free(free_row);
+}
diff --git a/linear-assignment.h b/linear-assignment.h
new file mode 100644
index 000000000..1dfea7662
--- /dev/null
+++ b/linear-assignment.h
@@ -0,0 +1,22 @@
+#ifndef LINEAR_ASSIGNMENT_H
+#define LINEAR_ASSIGNMENT_H
+
+/*
+ * Compute an assignment of columns -> rows (and vice versa) such that every
+ * column is assigned to at most one row (and vice versa) minimizing the
+ * overall cost.
+ *
+ * The parameter `cost` is the cost matrix: the cost to assign column j to row
+ * i is `cost[j + column_count * i].
+ *
+ * The arrays column2row and row2column will be populated with the respective
+ * assignments (-1 for unassigned, which can happen only if column_count !=
+ * row_count).
+ */
+void compute_assignment(int column_count, int row_count, int *cost,
+			int *column2row, int *row2column);
+
+/* The maximal cost in the cost matrix (to prevent integer overflows). */
+#define COST_MAX (1<<16)
+
+#endif
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 02/21] Introduce `range-diff` to compare iterations of a topic branch
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 01/21] linear-assignment: a function to solve least-cost assignment problems Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 03/21] range-diff: first rudimentary implementation Johannes Schindelin via GitGitGadget
                           ` (19 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

This command does not do a whole lot so far, apart from showing a usage
that is oddly similar to that of `git tbdiff`. And for a good reason:
the next commits will turn `range-branch` into a full-blown replacement
for `tbdiff`.

At this point, we ignore tbdiff's color options, as they will all be
implemented later using diff_options.

Since f318d739159 (generate-cmds.sh: export all commands to
command-list.h, 2018-05-10), every new command *requires* a man page to
build right away, so let's also add a blank man page, too.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitignore                       |  1 +
 Documentation/git-range-diff.txt | 10 ++++++++++
 Makefile                         |  1 +
 builtin.h                        |  1 +
 builtin/range-diff.c             | 25 +++++++++++++++++++++++++
 command-list.txt                 |  1 +
 git.c                            |  1 +
 7 files changed, 40 insertions(+)
 create mode 100644 Documentation/git-range-diff.txt
 create mode 100644 builtin/range-diff.c

diff --git a/.gitignore b/.gitignore
index 3284a1e9b..cc0ad74b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -113,6 +113,7 @@
 /git-pull
 /git-push
 /git-quiltimport
+/git-range-diff
 /git-read-tree
 /git-rebase
 /git-rebase--am
diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
new file mode 100644
index 000000000..49f717db8
--- /dev/null
+++ b/Documentation/git-range-diff.txt
@@ -0,0 +1,10 @@
+git-range-diff(1)
+=================
+
+NAME
+----
+git-range-diff - Compare two commit ranges (e.g. two versions of a branch)
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Makefile b/Makefile
index 1af719b44..7ff7eba42 100644
--- a/Makefile
+++ b/Makefile
@@ -1063,6 +1063,7 @@ BUILTIN_OBJS += builtin/prune-packed.o
 BUILTIN_OBJS += builtin/prune.o
 BUILTIN_OBJS += builtin/pull.o
 BUILTIN_OBJS += builtin/push.o
+BUILTIN_OBJS += builtin/range-diff.o
 BUILTIN_OBJS += builtin/read-tree.o
 BUILTIN_OBJS += builtin/rebase--helper.o
 BUILTIN_OBJS += builtin/receive-pack.o
diff --git a/builtin.h b/builtin.h
index 0362f1ce2..99206df4b 100644
--- a/builtin.h
+++ b/builtin.h
@@ -201,6 +201,7 @@ extern int cmd_prune(int argc, const char **argv, const char *prefix);
 extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
 extern int cmd_pull(int argc, const char **argv, const char *prefix);
 extern int cmd_push(int argc, const char **argv, const char *prefix);
+extern int cmd_range_diff(int argc, const char **argv, const char *prefix);
 extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix);
 extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);
diff --git a/builtin/range-diff.c b/builtin/range-diff.c
new file mode 100644
index 000000000..36788ea4f
--- /dev/null
+++ b/builtin/range-diff.c
@@ -0,0 +1,25 @@
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+
+static const char * const builtin_range_diff_usage[] = {
+N_("git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
+N_("git range-diff [<options>] <old-tip>...<new-tip>"),
+N_("git range-diff [<options>] <base> <old-tip> <new-tip>"),
+NULL
+};
+
+int cmd_range_diff(int argc, const char **argv, const char *prefix)
+{
+	int creation_factor = 60;
+	struct option options[] = {
+		OPT_INTEGER(0, "creation-factor", &creation_factor,
+			    N_("Percentage by which creation is weighted")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, NULL, options,
+			     builtin_range_diff_usage, 0);
+
+	return 0;
+}
diff --git a/command-list.txt b/command-list.txt
index e1c26c1bb..a9dda3b8a 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -139,6 +139,7 @@ git-prune-packed                        plumbingmanipulators
 git-pull                                mainporcelain           remote
 git-push                                mainporcelain           remote
 git-quiltimport                         foreignscminterface
+git-range-diff                          mainporcelain
 git-read-tree                           plumbingmanipulators
 git-rebase                              mainporcelain           history
 git-receive-pack                        synchelpers
diff --git a/git.c b/git.c
index fc7d15d54..5b48cac3a 100644
--- a/git.c
+++ b/git.c
@@ -520,6 +520,7 @@ static struct cmd_struct commands[] = {
 	{ "prune-packed", cmd_prune_packed, RUN_SETUP },
 	{ "pull", cmd_pull, RUN_SETUP | NEED_WORK_TREE },
 	{ "push", cmd_push, RUN_SETUP },
+	{ "range-diff", cmd_range_diff, RUN_SETUP | USE_PAGER },
 	{ "read-tree", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX},
 	{ "rebase--helper", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE },
 	{ "receive-pack", cmd_receive_pack },
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 03/21] range-diff: first rudimentary implementation
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 01/21] linear-assignment: a function to solve least-cost assignment problems Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 02/21] Introduce `range-diff` to compare iterations of a topic branch Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 04/21] range-diff: improve the order of the shown commits Johannes Schindelin via GitGitGadget
                           ` (18 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

At this stage, `git range-diff` can determine corresponding commits
of two related commit ranges. This makes use of the recently introduced
implementation of the linear assignment algorithm.

The core of this patch is a straight port of the ideas of tbdiff, the
apparently dormant project at https://github.com/trast/tbdiff.

The output does not at all match `tbdiff`'s output yet, as this patch
really concentrates on getting the patch matching part right.

Note: due to differences in the diff algorithm (`tbdiff` uses the Python
module `difflib`, Git uses its xdiff fork), the cost matrix calculated
by `range-diff` is different (but very similar) to the one calculated
by `tbdiff`. Therefore, it is possible that they find different matching
commits in corner cases (e.g. when a patch was split into two patches of
roughly equal length).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Makefile             |   1 +
 builtin/range-diff.c |  45 ++++++-
 range-diff.c         | 311 +++++++++++++++++++++++++++++++++++++++++++
 range-diff.h         |   7 +
 4 files changed, 363 insertions(+), 1 deletion(-)
 create mode 100644 range-diff.c
 create mode 100644 range-diff.h

diff --git a/Makefile b/Makefile
index 7ff7eba42..72f16882e 100644
--- a/Makefile
+++ b/Makefile
@@ -925,6 +925,7 @@ LIB_OBJS += progress.o
 LIB_OBJS += prompt.o
 LIB_OBJS += protocol.o
 LIB_OBJS += quote.o
+LIB_OBJS += range-diff.o
 LIB_OBJS += reachable.o
 LIB_OBJS += read-cache.o
 LIB_OBJS += reflog-walk.o
diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 36788ea4f..94c1f362c 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "builtin.h"
 #include "parse-options.h"
+#include "range-diff.h"
 
 static const char * const builtin_range_diff_usage[] = {
 N_("git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
@@ -17,9 +18,51 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 			    N_("Percentage by which creation is weighted")),
 		OPT_END()
 	};
+	int res = 0;
+	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
 
 	argc = parse_options(argc, argv, NULL, options,
 			     builtin_range_diff_usage, 0);
 
-	return 0;
+	if (argc == 2) {
+		if (!strstr(argv[0], ".."))
+			die(_("no .. in range: '%s'"), argv[0]);
+		strbuf_addstr(&range1, argv[0]);
+
+		if (!strstr(argv[1], ".."))
+			die(_("no .. in range: '%s'"), argv[1]);
+		strbuf_addstr(&range2, argv[1]);
+	} else if (argc == 3) {
+		strbuf_addf(&range1, "%s..%s", argv[0], argv[1]);
+		strbuf_addf(&range2, "%s..%s", argv[0], argv[2]);
+	} else if (argc == 1) {
+		const char *b = strstr(argv[0], "..."), *a = argv[0];
+		int a_len;
+
+		if (!b) {
+			error(_("single arg format must be symmetric range"));
+			usage_with_options(builtin_range_diff_usage, options);
+		}
+
+		a_len = (int)(b - a);
+		if (!a_len) {
+			a = "HEAD";
+			a_len = strlen(a);
+		}
+		b += 3;
+		if (!*b)
+			b = "HEAD";
+		strbuf_addf(&range1, "%s..%.*s", b, a_len, a);
+		strbuf_addf(&range2, "%.*s..%s", a_len, a, b);
+	} else {
+		error(_("need two commit ranges"));
+		usage_with_options(builtin_range_diff_usage, options);
+	}
+
+	res = show_range_diff(range1.buf, range2.buf, creation_factor);
+
+	strbuf_release(&range1);
+	strbuf_release(&range2);
+
+	return res;
 }
diff --git a/range-diff.c b/range-diff.c
new file mode 100644
index 000000000..15d418afa
--- /dev/null
+++ b/range-diff.c
@@ -0,0 +1,311 @@
+#include "cache.h"
+#include "range-diff.h"
+#include "string-list.h"
+#include "run-command.h"
+#include "argv-array.h"
+#include "hashmap.h"
+#include "xdiff-interface.h"
+#include "linear-assignment.h"
+
+struct patch_util {
+	/* For the search for an exact match */
+	struct hashmap_entry e;
+	const char *diff, *patch;
+
+	int i;
+	int diffsize;
+	size_t diff_offset;
+	/* the index of the matching item in the other branch, or -1 */
+	int matching;
+	struct object_id oid;
+};
+
+/*
+ * Reads the patches into a string list, with the `util` field being populated
+ * as struct object_id (will need to be free()d).
+ */
+static int read_patches(const char *range, struct string_list *list)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+	FILE *in;
+	struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;
+	struct patch_util *util = NULL;
+	int in_header = 1;
+
+	argv_array_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
+			"--reverse", "--date-order", "--decorate=no",
+			"--no-abbrev-commit", range,
+			NULL);
+	cp.out = -1;
+	cp.no_stdin = 1;
+	cp.git_cmd = 1;
+
+	if (start_command(&cp))
+		return error_errno(_("could not start `log`"));
+	in = fdopen(cp.out, "r");
+	if (!in) {
+		error_errno(_("could not read `log` output"));
+		finish_command(&cp);
+		return -1;
+	}
+
+	while (strbuf_getline(&line, in) != EOF) {
+		const char *p;
+
+		if (skip_prefix(line.buf, "commit ", &p)) {
+			if (util) {
+				string_list_append(list, buf.buf)->util = util;
+				strbuf_reset(&buf);
+			}
+			util = xcalloc(sizeof(*util), 1);
+			if (get_oid(p, &util->oid)) {
+				error(_("could not parse commit '%s'"), p);
+				free(util);
+				string_list_clear(list, 1);
+				strbuf_release(&buf);
+				strbuf_release(&line);
+				fclose(in);
+				finish_command(&cp);
+				return -1;
+			}
+			util->matching = -1;
+			in_header = 1;
+			continue;
+		}
+
+		if (starts_with(line.buf, "diff --git")) {
+			in_header = 0;
+			strbuf_addch(&buf, '\n');
+			if (!util->diff_offset)
+				util->diff_offset = buf.len;
+			strbuf_addbuf(&buf, &line);
+		} else if (in_header) {
+			if (starts_with(line.buf, "Author: ")) {
+				strbuf_addbuf(&buf, &line);
+				strbuf_addstr(&buf, "\n\n");
+			} else if (starts_with(line.buf, "    ")) {
+				strbuf_addbuf(&buf, &line);
+				strbuf_addch(&buf, '\n');
+			}
+			continue;
+		} else if (starts_with(line.buf, "@@ "))
+			strbuf_addstr(&buf, "@@");
+		else if (!line.buf[0] || starts_with(line.buf, "index "))
+			/*
+			 * A completely blank (not ' \n', which is context)
+			 * line is not valid in a diff.  We skip it
+			 * silently, because this neatly handles the blank
+			 * separator line between commits in git-log
+			 * output.
+			 *
+			 * We also want to ignore the diff's `index` lines
+			 * because they contain exact blob hashes in which
+			 * we are not interested.
+			 */
+			continue;
+		else
+			strbuf_addbuf(&buf, &line);
+
+		strbuf_addch(&buf, '\n');
+		util->diffsize++;
+	}
+	fclose(in);
+	strbuf_release(&line);
+
+	if (util)
+		string_list_append(list, buf.buf)->util = util;
+	strbuf_release(&buf);
+
+	if (finish_command(&cp))
+		return -1;
+
+	return 0;
+}
+
+static int patch_util_cmp(const void *dummy, const struct patch_util *a,
+		     const struct patch_util *b, const char *keydata)
+{
+	return strcmp(a->diff, keydata ? keydata : b->diff);
+}
+
+static void find_exact_matches(struct string_list *a, struct string_list *b)
+{
+	struct hashmap map;
+	int i;
+
+	hashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);
+
+	/* First, add the patches of a to a hash map */
+	for (i = 0; i < a->nr; i++) {
+		struct patch_util *util = a->items[i].util;
+
+		util->i = i;
+		util->patch = a->items[i].string;
+		util->diff = util->patch + util->diff_offset;
+		hashmap_entry_init(util, strhash(util->diff));
+		hashmap_add(&map, util);
+	}
+
+	/* Now try to find exact matches in b */
+	for (i = 0; i < b->nr; i++) {
+		struct patch_util *util = b->items[i].util, *other;
+
+		util->i = i;
+		util->patch = b->items[i].string;
+		util->diff = util->patch + util->diff_offset;
+		hashmap_entry_init(util, strhash(util->diff));
+		other = hashmap_remove(&map, util, NULL);
+		if (other) {
+			if (other->matching >= 0)
+				BUG("already assigned!");
+
+			other->matching = i;
+			util->matching = other->i;
+		}
+	}
+
+	hashmap_free(&map, 0);
+}
+
+static void diffsize_consume(void *data, char *line, unsigned long len)
+{
+	(*(int *)data)++;
+}
+
+static int diffsize(const char *a, const char *b)
+{
+	xpparam_t pp = { 0 };
+	xdemitconf_t cfg = { 0 };
+	mmfile_t mf1, mf2;
+	int count = 0;
+
+	mf1.ptr = (char *)a;
+	mf1.size = strlen(a);
+	mf2.ptr = (char *)b;
+	mf2.size = strlen(b);
+
+	cfg.ctxlen = 3;
+	if (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))
+		return count;
+
+	error(_("failed to generate diff"));
+	return COST_MAX;
+}
+
+static void get_correspondences(struct string_list *a, struct string_list *b,
+				int creation_factor)
+{
+	int n = a->nr + b->nr;
+	int *cost, c, *a2b, *b2a;
+	int i, j;
+
+	ALLOC_ARRAY(cost, st_mult(n, n));
+	ALLOC_ARRAY(a2b, n);
+	ALLOC_ARRAY(b2a, n);
+
+	for (i = 0; i < a->nr; i++) {
+		struct patch_util *a_util = a->items[i].util;
+
+		for (j = 0; j < b->nr; j++) {
+			struct patch_util *b_util = b->items[j].util;
+
+			if (a_util->matching == j)
+				c = 0;
+			else if (a_util->matching < 0 && b_util->matching < 0)
+				c = diffsize(a_util->diff, b_util->diff);
+			else
+				c = COST_MAX;
+			cost[i + n * j] = c;
+		}
+
+		c = a_util->matching < 0 ?
+			a_util->diffsize * creation_factor / 100 : COST_MAX;
+		for (j = b->nr; j < n; j++)
+			cost[i + n * j] = c;
+	}
+
+	for (j = 0; j < b->nr; j++) {
+		struct patch_util *util = b->items[j].util;
+
+		c = util->matching < 0 ?
+			util->diffsize * creation_factor / 100 : COST_MAX;
+		for (i = a->nr; i < n; i++)
+			cost[i + n * j] = c;
+	}
+
+	for (i = a->nr; i < n; i++)
+		for (j = b->nr; j < n; j++)
+			cost[i + n * j] = 0;
+
+	compute_assignment(n, n, cost, a2b, b2a);
+
+	for (i = 0; i < a->nr; i++)
+		if (a2b[i] >= 0 && a2b[i] < b->nr) {
+			struct patch_util *a_util = a->items[i].util;
+			struct patch_util *b_util = b->items[a2b[i]].util;
+
+			a_util->matching = a2b[i];
+			b_util->matching = i;
+		}
+
+	free(cost);
+	free(a2b);
+	free(b2a);
+}
+
+static const char *short_oid(struct patch_util *util)
+{
+	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
+}
+
+static void output(struct string_list *a, struct string_list *b)
+{
+	int i;
+
+	for (i = 0; i < b->nr; i++) {
+		struct patch_util *util = b->items[i].util, *prev;
+
+		if (util->matching < 0)
+			printf("-: -------- > %d: %s\n",
+					i + 1, short_oid(util));
+		else {
+			prev = a->items[util->matching].util;
+			printf("%d: %s ! %d: %s\n",
+			       util->matching + 1, short_oid(prev),
+			       i + 1, short_oid(util));
+		}
+	}
+
+	for (i = 0; i < a->nr; i++) {
+		struct patch_util *util = a->items[i].util;
+
+		if (util->matching < 0)
+			printf("%d: %s < -: --------\n",
+			       i + 1, short_oid(util));
+	}
+}
+
+int show_range_diff(const char *range1, const char *range2,
+		    int creation_factor)
+{
+	int res = 0;
+
+	struct string_list branch1 = STRING_LIST_INIT_DUP;
+	struct string_list branch2 = STRING_LIST_INIT_DUP;
+
+	if (read_patches(range1, &branch1))
+		res = error(_("could not parse log for '%s'"), range1);
+	if (!res && read_patches(range2, &branch2))
+		res = error(_("could not parse log for '%s'"), range2);
+
+	if (!res) {
+		find_exact_matches(&branch1, &branch2);
+		get_correspondences(&branch1, &branch2, creation_factor);
+		output(&branch1, &branch2);
+	}
+
+	string_list_clear(&branch1, 1);
+	string_list_clear(&branch2, 1);
+
+	return res;
+}
diff --git a/range-diff.h b/range-diff.h
new file mode 100644
index 000000000..7b6eef303
--- /dev/null
+++ b/range-diff.h
@@ -0,0 +1,7 @@
+#ifndef RANGE_DIFF_H
+#define RANGE_DIFF_H
+
+int show_range_diff(const char *range1, const char *range2,
+		    int creation_factor);
+
+#endif
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 04/21] range-diff: improve the order of the shown commits
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                           ` (2 preceding siblings ...)
  2018-08-10 22:14         ` [PATCH v5 03/21] range-diff: first rudimentary implementation Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 05/21] range-diff: also show the diff between patches Johannes Schindelin via GitGitGadget
                           ` (17 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

This patch lets `git range-diff` use the same order as tbdiff.

The idea is simple: for left-to-right readers, it is natural to assume
that the `git range-diff` is performed between an older vs a newer
version of the branch. As such, the user is probably more interested in
the question "where did this come from?" rather than "where did that one
go?".

To that end, we list the commits in the order of the second commit range
("the newer version"), inserting the unmatched commits of the first
commit range as soon as all their predecessors have been shown.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 59 +++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 40 insertions(+), 19 deletions(-)

diff --git a/range-diff.c b/range-diff.c
index 15d418afa..2d94200d3 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -12,7 +12,7 @@ struct patch_util {
 	struct hashmap_entry e;
 	const char *diff, *patch;
 
-	int i;
+	int i, shown;
 	int diffsize;
 	size_t diff_offset;
 	/* the index of the matching item in the other branch, or -1 */
@@ -260,28 +260,49 @@ static const char *short_oid(struct patch_util *util)
 
 static void output(struct string_list *a, struct string_list *b)
 {
-	int i;
-
-	for (i = 0; i < b->nr; i++) {
-		struct patch_util *util = b->items[i].util, *prev;
+	int i = 0, j = 0;
+
+	/*
+	 * We assume the user is really more interested in the second argument
+	 * ("newer" version). To that end, we print the output in the order of
+	 * the RHS (the `b` parameter). To put the LHS (the `a` parameter)
+	 * commits that are no longer in the RHS into a good place, we place
+	 * them once we have shown all of their predecessors in the LHS.
+	 */
+
+	while (i < a->nr || j < b->nr) {
+		struct patch_util *a_util, *b_util;
+		a_util = i < a->nr ? a->items[i].util : NULL;
+		b_util = j < b->nr ? b->items[j].util : NULL;
+
+		/* Skip all the already-shown commits from the LHS. */
+		while (i < a->nr && a_util->shown)
+			a_util = ++i < a->nr ? a->items[i].util : NULL;
+
+		/* Show unmatched LHS commit whose predecessors were shown. */
+		if (i < a->nr && a_util->matching < 0) {
+			printf("%d: %s < -: --------\n",
+			       i + 1, short_oid(a_util));
+			i++;
+			continue;
+		}
 
-		if (util->matching < 0)
+		/* Show unmatched RHS commits. */
+		while (j < b->nr && b_util->matching < 0) {
 			printf("-: -------- > %d: %s\n",
-					i + 1, short_oid(util));
-		else {
-			prev = a->items[util->matching].util;
-			printf("%d: %s ! %d: %s\n",
-			       util->matching + 1, short_oid(prev),
-			       i + 1, short_oid(util));
+			       j + 1, short_oid(b_util));
+			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
-	}
-
-	for (i = 0; i < a->nr; i++) {
-		struct patch_util *util = a->items[i].util;
 
-		if (util->matching < 0)
-			printf("%d: %s < -: --------\n",
-			       i + 1, short_oid(util));
+		/* Show matching LHS/RHS pair. */
+		if (j < b->nr) {
+			a_util = a->items[b_util->matching].util;
+			printf("%d: %s ! %d: %s\n",
+			       b_util->matching + 1, short_oid(a_util),
+			       j + 1, short_oid(b_util));
+			a_util->shown = 1;
+			j++;
+		}
 	}
 }
 
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 05/21] range-diff: also show the diff between patches
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                           ` (3 preceding siblings ...)
  2018-08-10 22:14         ` [PATCH v5 04/21] range-diff: improve the order of the shown commits Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-12 21:47           ` Thomas Gummerer
  2018-08-10 22:14         ` [PATCH v5 06/21] range-diff: right-trim commit messages Johannes Schindelin via GitGitGadget
                           ` (16 subsequent siblings)
  21 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

Just like tbdiff, we now show the diff between matching patches. This is
a "diff of two diffs", so it can be a bit daunting to read for the
beginner.

An alternative would be to display an interdiff, i.e. the hypothetical
diff which is the result of first reverting the old diff and then
applying the new diff.

Especially when rebasing frequently, an interdiff is often not feasible,
though: if the old diff cannot be applied in reverse (due to a moving
upstream), an interdiff can simply not be inferred.

This commit brings `range-diff` closer to feature parity with regard
to tbdiff.

To make `git range-diff` respect e.g. color.diff.* settings, we have
to adjust git_branch_config() accordingly.

Note: while we now parse diff options such as --color, the effect is not
yet the same as in tbdiff, where also the commit pairs would be colored.
This is left for a later commit.

Note also: while tbdiff accepts the `--no-patches` option to suppress
these diffs between patches, we prefer the `-s` (or `--no-patch`) option
that is automatically supported via our use of diff_opt_parse().

And finally note: to support diff options, we have to call
`parse_options()` such that it keeps unknown options, and then loop over
those and let `diff_opt_parse()` handle them. After that loop, we have
to call `parse_options()` again, to make sure that no unknown options
are left.

Helped-by: Thomas Gummerer <t.gummerer@gmail.com>
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/range-diff.c | 29 +++++++++++++++++++++++++++--
 range-diff.c         | 34 +++++++++++++++++++++++++++++++---
 range-diff.h         |  4 +++-
 3 files changed, 61 insertions(+), 6 deletions(-)

diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 94c1f362c..19192ab31 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -2,6 +2,7 @@
 #include "builtin.h"
 #include "parse-options.h"
 #include "range-diff.h"
+#include "config.h"
 
 static const char * const builtin_range_diff_usage[] = {
 N_("git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
@@ -13,15 +14,38 @@ NULL
 int cmd_range_diff(int argc, const char **argv, const char *prefix)
 {
 	int creation_factor = 60;
+	struct diff_options diffopt = { NULL };
 	struct option options[] = {
 		OPT_INTEGER(0, "creation-factor", &creation_factor,
 			    N_("Percentage by which creation is weighted")),
 		OPT_END()
 	};
-	int res = 0;
+	int i, j, res = 0;
 	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
 
+	git_config(git_diff_ui_config, NULL);
+
+	diff_setup(&diffopt);
+	diffopt.output_format = DIFF_FORMAT_PATCH;
+
 	argc = parse_options(argc, argv, NULL, options,
+			     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN |
+			     PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
+
+	for (i = j = 1; i < argc && strcmp("--", argv[i]); ) {
+		int c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);
+
+		if (!c)
+			argv[j++] = argv[i++];
+		else
+			i += c;
+	}
+	argc = j;
+	diff_setup_done(&diffopt);
+
+	/* Make sure that there are no unparsed options */
+	argc = parse_options(argc, argv, NULL,
+			     options + ARRAY_SIZE(options) - 1, /* OPT_END */
 			     builtin_range_diff_usage, 0);
 
 	if (argc == 2) {
@@ -59,7 +83,8 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 		usage_with_options(builtin_range_diff_usage, options);
 	}
 
-	res = show_range_diff(range1.buf, range2.buf, creation_factor);
+	res = show_range_diff(range1.buf, range2.buf, creation_factor,
+			      &diffopt);
 
 	strbuf_release(&range1);
 	strbuf_release(&range2);
diff --git a/range-diff.c b/range-diff.c
index 2d94200d3..71883a4b7 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -6,6 +6,7 @@
 #include "hashmap.h"
 #include "xdiff-interface.h"
 #include "linear-assignment.h"
+#include "diffcore.h"
 
 struct patch_util {
 	/* For the search for an exact match */
@@ -258,7 +259,31 @@ static const char *short_oid(struct patch_util *util)
 	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
 }
 
-static void output(struct string_list *a, struct string_list *b)
+static struct diff_filespec *get_filespec(const char *name, const char *p)
+{
+	struct diff_filespec *spec = alloc_filespec(name);
+
+	fill_filespec(spec, &null_oid, 0, 0644);
+	spec->data = (char *)p;
+	spec->size = strlen(p);
+	spec->should_munmap = 0;
+	spec->is_stdin = 1;
+
+	return spec;
+}
+
+static void patch_diff(const char *a, const char *b,
+			      struct diff_options *diffopt)
+{
+	diff_queue(&diff_queued_diff,
+		   get_filespec("a", a), get_filespec("b", b));
+
+	diffcore_std(diffopt);
+	diff_flush(diffopt);
+}
+
+static void output(struct string_list *a, struct string_list *b,
+		   struct diff_options *diffopt)
 {
 	int i = 0, j = 0;
 
@@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)
 			printf("%d: %s ! %d: %s\n",
 			       b_util->matching + 1, short_oid(a_util),
 			       j + 1, short_oid(b_util));
+			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
+				patch_diff(a->items[b_util->matching].string,
+					   b->items[j].string, diffopt);
 			a_util->shown = 1;
 			j++;
 		}
@@ -307,7 +335,7 @@ static void output(struct string_list *a, struct string_list *b)
 }
 
 int show_range_diff(const char *range1, const char *range2,
-		    int creation_factor)
+		    int creation_factor, struct diff_options *diffopt)
 {
 	int res = 0;
 
@@ -322,7 +350,7 @@ int show_range_diff(const char *range1, const char *range2,
 	if (!res) {
 		find_exact_matches(&branch1, &branch2);
 		get_correspondences(&branch1, &branch2, creation_factor);
-		output(&branch1, &branch2);
+		output(&branch1, &branch2, diffopt);
 	}
 
 	string_list_clear(&branch1, 1);
diff --git a/range-diff.h b/range-diff.h
index 7b6eef303..2407d46a3 100644
--- a/range-diff.h
+++ b/range-diff.h
@@ -1,7 +1,9 @@
 #ifndef RANGE_DIFF_H
 #define RANGE_DIFF_H
 
+#include "diff.h"
+
 int show_range_diff(const char *range1, const char *range2,
-		    int creation_factor);
+		    int creation_factor, struct diff_options *diffopt);
 
 #endif
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 06/21] range-diff: right-trim commit messages
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                           ` (4 preceding siblings ...)
  2018-08-10 22:14         ` [PATCH v5 05/21] range-diff: also show the diff between patches Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 07/21] range-diff: indent the diffs just like tbdiff Johannes Schindelin via GitGitGadget
                           ` (15 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When comparing commit messages, we need to keep in mind that they are
indented by four spaces. That is, empty lines are no longer empty, but
have "trailing whitespace". When displaying them in color, that results
in those nagging red lines.

Let's just right-trim the lines in the commit message, it's not like
trailing white-space in the commit messages are important enough to care
about in `git range-diff`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/range-diff.c b/range-diff.c
index 71883a4b7..1ecee2c09 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -85,6 +85,7 @@ static int read_patches(const char *range, struct string_list *list)
 				strbuf_addbuf(&buf, &line);
 				strbuf_addstr(&buf, "\n\n");
 			} else if (starts_with(line.buf, "    ")) {
+				strbuf_rtrim(&line);
 				strbuf_addbuf(&buf, &line);
 				strbuf_addch(&buf, '\n');
 			}
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 07/21] range-diff: indent the diffs just like tbdiff
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                           ` (5 preceding siblings ...)
  2018-08-10 22:14         ` [PATCH v5 06/21] range-diff: right-trim commit messages Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 08/21] range-diff: suppress the diff headers Johannes Schindelin via GitGitGadget
                           ` (14 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

The main information in the `range-diff` view comes from the list of
matching and non-matching commits, the diffs are additional information.
Indenting them helps with the reading flow.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/range-diff.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 19192ab31..f6df3f19a 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -11,6 +11,11 @@ N_("git range-diff [<options>] <base> <old-tip> <new-tip>"),
 NULL
 };
 
+static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)
+{
+	return data;
+}
+
 int cmd_range_diff(int argc, const char **argv, const char *prefix)
 {
 	int creation_factor = 60;
@@ -21,12 +26,16 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 	int i, j, res = 0;
+	struct strbuf four_spaces = STRBUF_INIT;
 	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
 
 	git_config(git_diff_ui_config, NULL);
 
 	diff_setup(&diffopt);
 	diffopt.output_format = DIFF_FORMAT_PATCH;
+	diffopt.output_prefix = output_prefix_cb;
+	strbuf_addstr(&four_spaces, "    ");
+	diffopt.output_prefix_data = &four_spaces;
 
 	argc = parse_options(argc, argv, NULL, options,
 			     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN |
@@ -88,6 +97,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 
 	strbuf_release(&range1);
 	strbuf_release(&range2);
+	strbuf_release(&four_spaces);
 
 	return res;
 }
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 08/21] range-diff: suppress the diff headers
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                           ` (6 preceding siblings ...)
  2018-08-10 22:14         ` [PATCH v5 07/21] range-diff: indent the diffs just like tbdiff Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 09/21] range-diff: adjust the output of the commit pairs Johannes Schindelin via GitGitGadget
                           ` (13 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When showing the diff between corresponding patches of the two branch
versions, we have to make up a fake filename to run the diff machinery.

That filename does not carry any meaningful information, hence tbdiff
suppresses it. So we should, too.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/range-diff.c | 1 +
 diff.c               | 5 ++++-
 diff.h               | 1 +
 3 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index f6df3f19a..bc7a2fb76 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -33,6 +33,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 
 	diff_setup(&diffopt);
 	diffopt.output_format = DIFF_FORMAT_PATCH;
+	diffopt.flags.suppress_diff_headers = 1;
 	diffopt.output_prefix = output_prefix_cb;
 	strbuf_addstr(&four_spaces, "    ");
 	diffopt.output_prefix_data = &four_spaces;
diff --git a/diff.c b/diff.c
index 04d044bbb..9c4bd9fa1 100644
--- a/diff.c
+++ b/diff.c
@@ -3395,13 +3395,16 @@ static void builtin_diff(const char *name_a,
 		memset(&xpp, 0, sizeof(xpp));
 		memset(&xecfg, 0, sizeof(xecfg));
 		memset(&ecbdata, 0, sizeof(ecbdata));
+		if (o->flags.suppress_diff_headers)
+			lbl[0] = NULL;
 		ecbdata.label_path = lbl;
 		ecbdata.color_diff = want_color(o->use_color);
 		ecbdata.ws_rule = whitespace_rule(name_b);
 		if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
 			check_blank_at_eof(&mf1, &mf2, &ecbdata);
 		ecbdata.opt = o;
-		ecbdata.header = header.len ? &header : NULL;
+		if (header.len && !o->flags.suppress_diff_headers)
+			ecbdata.header = &header;
 		xpp.flags = o->xdl_opts;
 		xpp.anchors = o->anchors;
 		xpp.anchors_nr = o->anchors_nr;
diff --git a/diff.h b/diff.h
index a14895bb8..d88ceb357 100644
--- a/diff.h
+++ b/diff.h
@@ -94,6 +94,7 @@ struct diff_flags {
 	unsigned funccontext:1;
 	unsigned default_follow_renames:1;
 	unsigned stat_with_summary:1;
+	unsigned suppress_diff_headers:1;
 };
 
 static inline void diff_flags_or(struct diff_flags *a,
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 09/21] range-diff: adjust the output of the commit pairs
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                           ` (7 preceding siblings ...)
  2018-08-10 22:14         ` [PATCH v5 08/21] range-diff: suppress the diff headers Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 10/21] range-diff: do not show "function names" in hunk headers Johannes Schindelin via GitGitGadget
                           ` (12 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

This not only uses "dashed stand-ins" for "pairs" where one side is
missing (i.e. unmatched commits that are present only in one of the two
commit ranges), but also adds onelines for the reader's pleasure.

This change brings `git range-diff` yet another step closer to
feature parity with tbdiff: it now shows the oneline, too, and indicates
with `=` when the commits have identical diffs.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 59 ++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 50 insertions(+), 9 deletions(-)

diff --git a/range-diff.c b/range-diff.c
index 1ecee2c09..23aa61af5 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -7,6 +7,8 @@
 #include "xdiff-interface.h"
 #include "linear-assignment.h"
 #include "diffcore.h"
+#include "commit.h"
+#include "pretty.h"
 
 struct patch_util {
 	/* For the search for an exact match */
@@ -255,9 +257,49 @@ static void get_correspondences(struct string_list *a, struct string_list *b,
 	free(b2a);
 }
 
-static const char *short_oid(struct patch_util *util)
+static void output_pair_header(struct strbuf *buf,
+			       struct strbuf *dashes,
+			       struct patch_util *a_util,
+			       struct patch_util *b_util)
 {
-	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
+	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
+	struct commit *commit;
+
+	if (!dashes->len)
+		strbuf_addchars(dashes, '-',
+				strlen(find_unique_abbrev(oid,
+							  DEFAULT_ABBREV)));
+
+	strbuf_reset(buf);
+	if (!a_util)
+		strbuf_addf(buf, "-:  %s ", dashes->buf);
+	else
+		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
+			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
+
+	if (!a_util)
+		strbuf_addch(buf, '>');
+	else if (!b_util)
+		strbuf_addch(buf, '<');
+	else if (strcmp(a_util->patch, b_util->patch))
+		strbuf_addch(buf, '!');
+	else
+		strbuf_addch(buf, '=');
+
+	if (!b_util)
+		strbuf_addf(buf, " -:  %s", dashes->buf);
+	else
+		strbuf_addf(buf, " %d:  %s", b_util->i + 1,
+			    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));
+
+	commit = lookup_commit_reference(the_repository, oid);
+	if (commit) {
+		strbuf_addch(buf, ' ');
+		pp_commit_easy(CMIT_FMT_ONELINE, commit, buf);
+	}
+	strbuf_addch(buf, '\n');
+
+	fwrite(buf->buf, buf->len, 1, stdout);
 }
 
 static struct diff_filespec *get_filespec(const char *name, const char *p)
@@ -286,6 +328,7 @@ static void patch_diff(const char *a, const char *b,
 static void output(struct string_list *a, struct string_list *b,
 		   struct diff_options *diffopt)
 {
+	struct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;
 	int i = 0, j = 0;
 
 	/*
@@ -307,25 +350,21 @@ static void output(struct string_list *a, struct string_list *b,
 
 		/* Show unmatched LHS commit whose predecessors were shown. */
 		if (i < a->nr && a_util->matching < 0) {
-			printf("%d: %s < -: --------\n",
-			       i + 1, short_oid(a_util));
+			output_pair_header(&buf, &dashes, a_util, NULL);
 			i++;
 			continue;
 		}
 
 		/* Show unmatched RHS commits. */
 		while (j < b->nr && b_util->matching < 0) {
-			printf("-: -------- > %d: %s\n",
-			       j + 1, short_oid(b_util));
+			output_pair_header(&buf, &dashes, NULL, b_util);
 			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
 
 		/* Show matching LHS/RHS pair. */
 		if (j < b->nr) {
 			a_util = a->items[b_util->matching].util;
-			printf("%d: %s ! %d: %s\n",
-			       b_util->matching + 1, short_oid(a_util),
-			       j + 1, short_oid(b_util));
+			output_pair_header(&buf, &dashes, a_util, b_util);
 			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
 				patch_diff(a->items[b_util->matching].string,
 					   b->items[j].string, diffopt);
@@ -333,6 +372,8 @@ static void output(struct string_list *a, struct string_list *b,
 			j++;
 		}
 	}
+	strbuf_release(&buf);
+	strbuf_release(&dashes);
 }
 
 int show_range_diff(const char *range1, const char *range2,
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 10/21] range-diff: do not show "function names" in hunk headers
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                           ` (8 preceding siblings ...)
  2018-08-10 22:14         ` [PATCH v5 09/21] range-diff: adjust the output of the commit pairs Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 11/21] range-diff: add tests Thomas Rast via GitGitGadget
                           ` (11 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

We are comparing complete, formatted commit messages with patches. There
are no function names here, so stop looking for them.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/range-diff.c b/range-diff.c
index 23aa61af5..6d75563f4 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -9,6 +9,7 @@
 #include "diffcore.h"
 #include "commit.h"
 #include "pretty.h"
+#include "userdiff.h"
 
 struct patch_util {
 	/* For the search for an exact match */
@@ -302,6 +303,10 @@ static void output_pair_header(struct strbuf *buf,
 	fwrite(buf->buf, buf->len, 1, stdout);
 }
 
+static struct userdiff_driver no_func_name = {
+	.funcname = { "$^", 0 }
+};
+
 static struct diff_filespec *get_filespec(const char *name, const char *p)
 {
 	struct diff_filespec *spec = alloc_filespec(name);
@@ -311,6 +316,7 @@ static struct diff_filespec *get_filespec(const char *name, const char *p)
 	spec->size = strlen(p);
 	spec->should_munmap = 0;
 	spec->is_stdin = 1;
+	spec->driver = &no_func_name;
 
 	return spec;
 }
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 11/21] range-diff: add tests
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                           ` (9 preceding siblings ...)
  2018-08-10 22:14         ` [PATCH v5 10/21] range-diff: do not show "function names" in hunk headers Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Thomas Rast via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 12/21] range-diff: use color for the commit pairs Johannes Schindelin via GitGitGadget
                           ` (10 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Thomas Rast via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Thomas Rast

From: Thomas Rast <tr@thomasrast.ch>

These are essentially lifted from https://github.com/trast/tbdiff, with
light touch-ups to account for the command now being named `git
range-diff`.

Apart from renaming `tbdiff` to `range-diff`, only one test case needed
to be adjusted: 11 - 'changed message'.

The underlying reason it had to be adjusted is that diff generation is
sometimes ambiguous. In this case, a comment line and an empty line are
added, but it is ambiguous whether they were added after the existing
empty line, or whether an empty line and the comment line are added
*before* the existing empty line. And apparently xdiff picks a different
option here than Python's difflib.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/.gitattributes       |   1 +
 t/t3206-range-diff.sh  | 145 ++++++++++
 t/t3206/history.export | 604 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 750 insertions(+)
 create mode 100755 t/t3206-range-diff.sh
 create mode 100644 t/t3206/history.export

diff --git a/t/.gitattributes b/t/.gitattributes
index 3bd959ae5..b17bf71b8 100644
--- a/t/.gitattributes
+++ b/t/.gitattributes
@@ -1,6 +1,7 @@
 t[0-9][0-9][0-9][0-9]/* -whitespace
 /diff-lib/* eol=lf
 /t0110/url-* binary
+/t3206/* eol=lf
 /t3900/*.txt eol=lf
 /t3901/*.txt eol=lf
 /t4034/*/* eol=lf
diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
new file mode 100755
index 000000000..2237c7f4a
--- /dev/null
+++ b/t/t3206-range-diff.sh
@@ -0,0 +1,145 @@
+#!/bin/sh
+
+test_description='range-diff tests'
+
+. ./test-lib.sh
+
+# Note that because of the range-diff's heuristics, test_commit does more
+# harm than good.  We need some real history.
+
+test_expect_success 'setup' '
+	git fast-import < "$TEST_DIRECTORY"/t3206/history.export
+'
+
+test_expect_success 'simple A..B A..C (unmodified)' '
+	git range-diff --no-color master..topic master..unmodified \
+		>actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  35b9b25 s/5/A/
+	2:  fccce22 = 2:  de345ab s/4/A/
+	3:  147e64e = 3:  9af6654 s/11/B/
+	4:  a63e992 = 4:  2901f77 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'simple B...C (unmodified)' '
+	git range-diff --no-color topic...unmodified >actual &&
+	# same "expected" as above
+	test_cmp expected actual
+'
+
+test_expect_success 'simple A B C (unmodified)' '
+	git range-diff --no-color master topic unmodified >actual &&
+	# same "expected" as above
+	test_cmp expected actual
+'
+
+test_expect_success 'trivial reordering' '
+	git range-diff --no-color master topic reordered >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  aca177a s/5/A/
+	3:  147e64e = 2:  14ad629 s/11/B/
+	4:  a63e992 = 3:  ee58208 s/12/B/
+	2:  fccce22 = 4:  307b27a s/4/A/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'removed a commit' '
+	git range-diff --no-color master topic removed >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  7657159 s/5/A/
+	2:  fccce22 < -:  ------- s/4/A/
+	3:  147e64e = 2:  43d84d3 s/11/B/
+	4:  a63e992 = 3:  a740396 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'added a commit' '
+	git range-diff --no-color master topic added >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  2716022 s/5/A/
+	2:  fccce22 = 2:  b62accd s/4/A/
+	-:  ------- > 3:  df46cfa s/6/A/
+	3:  147e64e = 4:  3e64548 s/11/B/
+	4:  a63e992 = 5:  12b4063 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'new base, A B C' '
+	git range-diff --no-color master topic rebased >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  cc9c443 s/5/A/
+	2:  fccce22 = 2:  c5d9641 s/4/A/
+	3:  147e64e = 3:  28cc2b6 s/11/B/
+	4:  a63e992 = 4:  5628ab7 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'new base, B...C' '
+	# this syntax includes the commits from master!
+	git range-diff --no-color topic...rebased >actual &&
+	cat >expected <<-EOF &&
+	-:  ------- > 1:  a31b12e unrelated
+	1:  4de457d = 2:  cc9c443 s/5/A/
+	2:  fccce22 = 3:  c5d9641 s/4/A/
+	3:  147e64e = 4:  28cc2b6 s/11/B/
+	4:  a63e992 = 5:  5628ab7 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'changed commit' '
+	git range-diff --no-color topic...changed >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  a4b3333 s/5/A/
+	2:  fccce22 = 2:  f51d370 s/4/A/
+	3:  147e64e ! 3:  0559556 s/11/B/
+	    @@ -10,7 +10,7 @@
+	      9
+	      10
+	     -11
+	    -+B
+	    ++BB
+	      12
+	      13
+	      14
+	4:  a63e992 ! 4:  d966c5c s/12/B/
+	    @@ -8,7 +8,7 @@
+	     @@
+	      9
+	      10
+	    - B
+	    + BB
+	     -12
+	     +B
+	      13
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'changed message' '
+	git range-diff --no-color topic...changed-message >actual &&
+	sed s/Z/\ /g >expected <<-EOF &&
+	1:  4de457d = 1:  f686024 s/5/A/
+	2:  fccce22 ! 2:  4ab067d s/4/A/
+	    @@ -2,6 +2,8 @@
+	    Z
+	    Z    s/4/A/
+	    Z
+	    +    Also a silly comment here!
+	    +
+	    Zdiff --git a/file b/file
+	    Z--- a/file
+	    Z+++ b/file
+	3:  147e64e = 3:  b9cb956 s/11/B/
+	4:  a63e992 = 4:  8add5f1 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_done
diff --git a/t/t3206/history.export b/t/t3206/history.export
new file mode 100644
index 000000000..b8ffff094
--- /dev/null
+++ b/t/t3206/history.export
@@ -0,0 +1,604 @@
+blob
+mark :1
+data 51
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+reset refs/heads/removed
+commit refs/heads/removed
+mark :2
+author Thomas Rast <trast@inf.ethz.ch> 1374424921 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374484724 +0200
+data 8
+initial
+M 100644 :1 file
+
+blob
+mark :3
+data 51
+1
+2
+3
+4
+A
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :4
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+blob
+mark :5
+data 51
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :6
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+data 7
+s/4/A/
+from :4
+M 100644 :5 file
+
+blob
+mark :7
+data 50
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+B
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :8
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+data 8
+s/11/B/
+from :6
+M 100644 :7 file
+
+blob
+mark :9
+data 49
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+B
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :10
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+data 8
+s/12/B/
+from :8
+M 100644 :9 file
+
+blob
+mark :11
+data 10
+unrelated
+
+commit refs/heads/master
+mark :12
+author Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200
+data 10
+unrelated
+from :2
+M 100644 :11 otherfile
+
+commit refs/heads/rebased
+mark :13
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485137 +0200
+data 7
+s/5/A/
+from :12
+M 100644 :3 file
+
+commit refs/heads/rebased
+mark :14
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
+data 7
+s/4/A/
+from :13
+M 100644 :5 file
+
+commit refs/heads/rebased
+mark :15
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
+data 8
+s/11/B/
+from :14
+M 100644 :7 file
+
+commit refs/heads/rebased
+mark :16
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
+data 8
+s/12/B/
+from :15
+M 100644 :9 file
+
+commit refs/heads/added
+mark :17
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/added
+mark :18
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 7
+s/4/A/
+from :17
+M 100644 :5 file
+
+blob
+mark :19
+data 51
+1
+2
+3
+A
+A
+A
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/added
+mark :20
+author Thomas Rast <trast@inf.ethz.ch> 1374485186 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 7
+s/6/A/
+from :18
+M 100644 :19 file
+
+blob
+mark :21
+data 50
+1
+2
+3
+A
+A
+A
+7
+8
+9
+10
+B
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/added
+mark :22
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 8
+s/11/B/
+from :20
+M 100644 :21 file
+
+blob
+mark :23
+data 49
+1
+2
+3
+A
+A
+A
+7
+8
+9
+10
+B
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/added
+mark :24
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 8
+s/12/B/
+from :22
+M 100644 :23 file
+
+commit refs/heads/reordered
+mark :25
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+blob
+mark :26
+data 50
+1
+2
+3
+4
+A
+6
+7
+8
+9
+10
+B
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/reordered
+mark :27
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 8
+s/11/B/
+from :25
+M 100644 :26 file
+
+blob
+mark :28
+data 49
+1
+2
+3
+4
+A
+6
+7
+8
+9
+10
+B
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/reordered
+mark :29
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 8
+s/12/B/
+from :27
+M 100644 :28 file
+
+commit refs/heads/reordered
+mark :30
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 7
+s/4/A/
+from :29
+M 100644 :9 file
+
+commit refs/heads/changed
+mark :31
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/changed
+mark :32
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 7
+s/4/A/
+from :31
+M 100644 :5 file
+
+blob
+mark :33
+data 51
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+BB
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/changed
+mark :34
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 8
+s/11/B/
+from :32
+M 100644 :33 file
+
+blob
+mark :35
+data 50
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+BB
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/changed
+mark :36
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 8
+s/12/B/
+from :34
+M 100644 :35 file
+
+commit refs/heads/changed-message
+mark :37
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/changed-message
+mark :38
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200
+data 35
+s/4/A/
+
+Also a silly comment here!
+from :37
+M 100644 :5 file
+
+commit refs/heads/changed-message
+mark :39
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200
+data 8
+s/11/B/
+from :38
+M 100644 :7 file
+
+commit refs/heads/changed-message
+mark :40
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200
+data 8
+s/12/B/
+from :39
+M 100644 :9 file
+
+commit refs/heads/unmodified
+mark :41
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/unmodified
+mark :42
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200
+data 7
+s/4/A/
+from :41
+M 100644 :5 file
+
+commit refs/heads/unmodified
+mark :43
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200
+data 8
+s/11/B/
+from :42
+M 100644 :7 file
+
+commit refs/heads/unmodified
+mark :44
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200
+data 8
+s/12/B/
+from :43
+M 100644 :9 file
+
+commit refs/heads/removed
+mark :45
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/removed
+mark :46
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
+data 8
+s/11/B/
+from :45
+M 100644 :26 file
+
+commit refs/heads/removed
+mark :47
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
+data 8
+s/12/B/
+from :46
+M 100644 :28 file
+
+reset refs/heads/removed
+from :47
+
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 12/21] range-diff: use color for the commit pairs
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                           ` (10 preceding siblings ...)
  2018-08-10 22:14         ` [PATCH v5 11/21] range-diff: add tests Thomas Rast via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 13/21] color: add the meta color GIT_COLOR_REVERSE Johannes Schindelin via GitGitGadget
                           ` (9 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

Arguably the most important part of `git range-diff`'s output is the
list of commits in the two branches, together with their relationships.

For that reason, tbdiff introduced color-coding that is pretty
intuitive, especially for unchanged patches (all dim yellow, like the
first line in `git show`'s output) vs modified patches (old commit is
red, new commit is green). Let's imitate that color scheme.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 51 ++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 38 insertions(+), 13 deletions(-)

diff --git a/range-diff.c b/range-diff.c
index 6d75563f4..b1663da7c 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -258,34 +258,53 @@ static void get_correspondences(struct string_list *a, struct string_list *b,
 	free(b2a);
 }
 
-static void output_pair_header(struct strbuf *buf,
+static void output_pair_header(struct diff_options *diffopt,
+			       struct strbuf *buf,
 			       struct strbuf *dashes,
 			       struct patch_util *a_util,
 			       struct patch_util *b_util)
 {
 	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
 	struct commit *commit;
+	char status;
+	const char *color_reset = diff_get_color_opt(diffopt, DIFF_RESET);
+	const char *color_old = diff_get_color_opt(diffopt, DIFF_FILE_OLD);
+	const char *color_new = diff_get_color_opt(diffopt, DIFF_FILE_NEW);
+	const char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT);
+	const char *color;
 
 	if (!dashes->len)
 		strbuf_addchars(dashes, '-',
 				strlen(find_unique_abbrev(oid,
 							  DEFAULT_ABBREV)));
 
+	if (!b_util) {
+		color = color_old;
+		status = '<';
+	} else if (!a_util) {
+		color = color_new;
+		status = '>';
+	} else if (strcmp(a_util->patch, b_util->patch)) {
+		color = color_commit;
+		status = '!';
+	} else {
+		color = color_commit;
+		status = '=';
+	}
+
 	strbuf_reset(buf);
+	strbuf_addstr(buf, status == '!' ? color_old : color);
 	if (!a_util)
 		strbuf_addf(buf, "-:  %s ", dashes->buf);
 	else
 		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
 			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
 
-	if (!a_util)
-		strbuf_addch(buf, '>');
-	else if (!b_util)
-		strbuf_addch(buf, '<');
-	else if (strcmp(a_util->patch, b_util->patch))
-		strbuf_addch(buf, '!');
-	else
-		strbuf_addch(buf, '=');
+	if (status == '!')
+		strbuf_addf(buf, "%s%s", color_reset, color);
+	strbuf_addch(buf, status);
+	if (status == '!')
+		strbuf_addf(buf, "%s%s", color_reset, color_new);
 
 	if (!b_util)
 		strbuf_addf(buf, " -:  %s", dashes->buf);
@@ -295,10 +314,13 @@ static void output_pair_header(struct strbuf *buf,
 
 	commit = lookup_commit_reference(the_repository, oid);
 	if (commit) {
+		if (status == '!')
+			strbuf_addf(buf, "%s%s", color_reset, color);
+
 		strbuf_addch(buf, ' ');
 		pp_commit_easy(CMIT_FMT_ONELINE, commit, buf);
 	}
-	strbuf_addch(buf, '\n');
+	strbuf_addf(buf, "%s\n", color_reset);
 
 	fwrite(buf->buf, buf->len, 1, stdout);
 }
@@ -356,21 +378,24 @@ static void output(struct string_list *a, struct string_list *b,
 
 		/* Show unmatched LHS commit whose predecessors were shown. */
 		if (i < a->nr && a_util->matching < 0) {
-			output_pair_header(&buf, &dashes, a_util, NULL);
+			output_pair_header(diffopt,
+					   &buf, &dashes, a_util, NULL);
 			i++;
 			continue;
 		}
 
 		/* Show unmatched RHS commits. */
 		while (j < b->nr && b_util->matching < 0) {
-			output_pair_header(&buf, &dashes, NULL, b_util);
+			output_pair_header(diffopt,
+					   &buf, &dashes, NULL, b_util);
 			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
 
 		/* Show matching LHS/RHS pair. */
 		if (j < b->nr) {
 			a_util = a->items[b_util->matching].util;
-			output_pair_header(&buf, &dashes, a_util, b_util);
+			output_pair_header(diffopt,
+					   &buf, &dashes, a_util, b_util);
 			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
 				patch_diff(a->items[b_util->matching].string,
 					   b->items[j].string, diffopt);
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 13/21] color: add the meta color GIT_COLOR_REVERSE
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                           ` (11 preceding siblings ...)
  2018-08-10 22:14         ` [PATCH v5 12/21] range-diff: use color for the commit pairs Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 14/21] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin via GitGitGadget
                           ` (8 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

This "color" simply reverts background and foreground. It will be used
in the upcoming "dual color" mode of `git range-diff`, where we will
reverse colors for the -/+ markers and the fragment headers of the
"outer" diff.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 color.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/color.h b/color.h
index 5b744e1bc..33e786342 100644
--- a/color.h
+++ b/color.h
@@ -44,6 +44,7 @@ struct strbuf;
 #define GIT_COLOR_BG_CYAN	"\033[46m"
 #define GIT_COLOR_FAINT		"\033[2m"
 #define GIT_COLOR_FAINT_ITALIC	"\033[2;3m"
+#define GIT_COLOR_REVERSE	"\033[7m"
 
 /* A special value meaning "no color selected" */
 #define GIT_COLOR_NIL "NIL"
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 14/21] diff: add an internal option to dual-color diffs of diffs
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                           ` (12 preceding siblings ...)
  2018-08-10 22:14         ` [PATCH v5 13/21] color: add the meta color GIT_COLOR_REVERSE Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 15/21] range-diff: offer to dual-color the diffs Johannes Schindelin via GitGitGadget
                           ` (7 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When diffing diffs, it can be quite daunting to figure out what the heck
is going on, as there are nested +/- signs.

Let's make this easier by adding a flag in diff_options that allows
color-coding the outer diff sign with inverted colors, so that the
preimage and postimage is colored like the diff it is.

Of course, this really only makes sense when the preimage and postimage
*are* diffs. So let's not expose this flag via a command-line option for
now.

This is a feature that was invented by git-tbdiff, and it will be used
by `git range-diff` in the next commit, by offering it via a new option:
`--dual-color`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 diff.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++-----------
 diff.h |  1 +
 2 files changed, 69 insertions(+), 15 deletions(-)

diff --git a/diff.c b/diff.c
index 9c4bd9fa1..e6c857abf 100644
--- a/diff.c
+++ b/diff.c
@@ -609,14 +609,18 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,
 	ecbdata->blank_at_eof_in_postimage = (at - l2) + 1;
 }
 
-static void emit_line_0(struct diff_options *o, const char *set, const char *reset,
+static void emit_line_0(struct diff_options *o,
+			const char *set, unsigned reverse, const char *reset,
 			int first, const char *line, int len)
 {
 	int has_trailing_newline, has_trailing_carriage_return;
 	int nofirst;
 	FILE *file = o->file;
 
-	fputs(diff_line_prefix(o), file);
+	if (first)
+		fputs(diff_line_prefix(o), file);
+	else if (!len)
+		return;
 
 	if (len == 0) {
 		has_trailing_newline = (first == '\n');
@@ -634,8 +638,10 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res
 	}
 
 	if (len || !nofirst) {
+		if (reverse && want_color(o->use_color))
+			fputs(GIT_COLOR_REVERSE, file);
 		fputs(set, file);
-		if (!nofirst)
+		if (first && !nofirst)
 			fputc(first, file);
 		fwrite(line, len, 1, file);
 		fputs(reset, file);
@@ -649,7 +655,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res
 static void emit_line(struct diff_options *o, const char *set, const char *reset,
 		      const char *line, int len)
 {
-	emit_line_0(o, set, reset, line[0], line+1, len-1);
+	emit_line_0(o, set, 0, reset, line[0], line+1, len-1);
 }
 
 enum diff_symbol {
@@ -1168,7 +1174,8 @@ static void dim_moved_lines(struct diff_options *o)
 
 static void emit_line_ws_markup(struct diff_options *o,
 				const char *set, const char *reset,
-				const char *line, int len, char sign,
+				const char *line, int len,
+				const char *set_sign, char sign,
 				unsigned ws_rule, int blank_at_eof)
 {
 	const char *ws = NULL;
@@ -1179,14 +1186,20 @@ static void emit_line_ws_markup(struct diff_options *o,
 			ws = NULL;
 	}
 
-	if (!ws)
-		emit_line_0(o, set, reset, sign, line, len);
-	else if (blank_at_eof)
+	if (!ws && !set_sign)
+		emit_line_0(o, set, 0, reset, sign, line, len);
+	else if (!ws) {
+		/* Emit just the prefix, then the rest. */
+		emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
+			    sign, "", 0);
+		emit_line_0(o, set, 0, reset, 0, line, len);
+	} else if (blank_at_eof)
 		/* Blank line at EOF - paint '+' as well */
-		emit_line_0(o, ws, reset, sign, line, len);
+		emit_line_0(o, ws, 0, reset, sign, line, len);
 	else {
 		/* Emit just the prefix, then the rest. */
-		emit_line_0(o, set, reset, sign, "", 0);
+		emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
+			    sign, "", 0);
 		ws_check_emit(line, len, ws_rule,
 			      o->file, set, reset, ws);
 	}
@@ -1196,7 +1209,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 					 struct emitted_diff_symbol *eds)
 {
 	static const char *nneof = " No newline at end of file\n";
-	const char *context, *reset, *set, *meta, *fraginfo;
+	const char *context, *reset, *set, *set_sign, *meta, *fraginfo;
 	struct strbuf sb = STRBUF_INIT;
 
 	enum diff_symbol s = eds->s;
@@ -1209,7 +1222,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 		context = diff_get_color_opt(o, DIFF_CONTEXT);
 		reset = diff_get_color_opt(o, DIFF_RESET);
 		putc('\n', o->file);
-		emit_line_0(o, context, reset, '\\',
+		emit_line_0(o, context, 0, reset, '\\',
 			    nneof, strlen(nneof));
 		break;
 	case DIFF_SYMBOL_SUBMODULE_HEADER:
@@ -1236,7 +1249,18 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 	case DIFF_SYMBOL_CONTEXT:
 		set = diff_get_color_opt(o, DIFF_CONTEXT);
 		reset = diff_get_color_opt(o, DIFF_RESET);
-		emit_line_ws_markup(o, set, reset, line, len, ' ',
+		set_sign = NULL;
+		if (o->flags.dual_color_diffed_diffs) {
+			char c = !len ? 0 : line[0];
+
+			if (c == '+')
+				set = diff_get_color_opt(o, DIFF_FILE_NEW);
+			else if (c == '@')
+				set = diff_get_color_opt(o, DIFF_FRAGINFO);
+			else if (c == '-')
+				set = diff_get_color_opt(o, DIFF_FILE_OLD);
+		}
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',
 				    flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);
 		break;
 	case DIFF_SYMBOL_PLUS:
@@ -1263,7 +1287,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 			set = diff_get_color_opt(o, DIFF_FILE_NEW);
 		}
 		reset = diff_get_color_opt(o, DIFF_RESET);
-		emit_line_ws_markup(o, set, reset, line, len, '+',
+		if (!o->flags.dual_color_diffed_diffs)
+			set_sign = NULL;
+		else {
+			char c = !len ? 0 : line[0];
+
+			set_sign = set;
+			if (c == '-')
+				set = diff_get_color_opt(o, DIFF_FILE_OLD);
+			else if (c == '@')
+				set = diff_get_color_opt(o, DIFF_FRAGINFO);
+			else if (c != '+')
+				set = diff_get_color_opt(o, DIFF_CONTEXT);
+		}
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK,
 				    flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);
 		break;
@@ -1291,7 +1328,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 			set = diff_get_color_opt(o, DIFF_FILE_OLD);
 		}
 		reset = diff_get_color_opt(o, DIFF_RESET);
-		emit_line_ws_markup(o, set, reset, line, len, '-',
+		if (!o->flags.dual_color_diffed_diffs)
+			set_sign = NULL;
+		else {
+			char c = !len ? 0 : line[0];
+
+			set_sign = set;
+			if (c == '+')
+				set = diff_get_color_opt(o, DIFF_FILE_NEW);
+			else if (c == '@')
+				set = diff_get_color_opt(o, DIFF_FRAGINFO);
+			else if (c != '-')
+				set = diff_get_color_opt(o, DIFF_CONTEXT);
+		}
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, '-',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);
 		break;
 	case DIFF_SYMBOL_WORDS_PORCELAIN:
@@ -1482,6 +1532,7 @@ static void emit_hunk_header(struct emit_callback *ecbdata,
 	const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
 	const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);
 	const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
+	const char *reverse = ecbdata->color_diff ? GIT_COLOR_REVERSE : "";
 	static const char atat[2] = { '@', '@' };
 	const char *cp, *ep;
 	struct strbuf msgbuf = STRBUF_INIT;
@@ -1502,6 +1553,8 @@ static void emit_hunk_header(struct emit_callback *ecbdata,
 	ep += 2; /* skip over @@ */
 
 	/* The hunk header in fraginfo color */
+	if (ecbdata->opt->flags.dual_color_diffed_diffs)
+		strbuf_addstr(&msgbuf, reverse);
 	strbuf_addstr(&msgbuf, frag);
 	strbuf_add(&msgbuf, line, ep - line);
 	strbuf_addstr(&msgbuf, reset);
diff --git a/diff.h b/diff.h
index d88ceb357..cca4f9d6c 100644
--- a/diff.h
+++ b/diff.h
@@ -95,6 +95,7 @@ struct diff_flags {
 	unsigned default_follow_renames:1;
 	unsigned stat_with_summary:1;
 	unsigned suppress_diff_headers:1;
+	unsigned dual_color_diffed_diffs:1;
 };
 
 static inline void diff_flags_or(struct diff_flags *a,
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 15/21] range-diff: offer to dual-color the diffs
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                           ` (13 preceding siblings ...)
  2018-08-10 22:14         ` [PATCH v5 14/21] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 16/21] range-diff --dual-color: skip white-space warnings Johannes Schindelin via GitGitGadget
                           ` (6 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When showing what changed between old and new commits, we show a diff of
the patches. This diff is a diff between diffs, therefore there are
nested +/- signs, and it can be relatively hard to understand what is
going on.

With the --dual-color option, the preimage and the postimage are colored
like the diffs they are, and the *outer* +/- sign is inverted for
clarity.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/range-diff.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index bc7a2fb76..da3ad3eba 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -20,9 +20,12 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 {
 	int creation_factor = 60;
 	struct diff_options diffopt = { NULL };
+	int dual_color = 0;
 	struct option options[] = {
 		OPT_INTEGER(0, "creation-factor", &creation_factor,
 			    N_("Percentage by which creation is weighted")),
+		OPT_BOOL(0, "dual-color", &dual_color,
+			    N_("color both diff and diff-between-diffs")),
 		OPT_END()
 	};
 	int i, j, res = 0;
@@ -58,6 +61,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 			     options + ARRAY_SIZE(options) - 1, /* OPT_END */
 			     builtin_range_diff_usage, 0);
 
+	if (dual_color) {
+		diffopt.use_color = 1;
+		diffopt.flags.dual_color_diffed_diffs = 1;
+	}
+
 	if (argc == 2) {
 		if (!strstr(argv[0], ".."))
 			die(_("no .. in range: '%s'"), argv[0]);
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 16/21] range-diff --dual-color: skip white-space warnings
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                           ` (14 preceding siblings ...)
  2018-08-10 22:14         ` [PATCH v5 15/21] range-diff: offer to dual-color the diffs Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 17/21] range-diff: populate the man page Johannes Schindelin via GitGitGadget
                           ` (5 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When displaying a diff of diffs, it is possible that there is an outer
`+` before a context line. That happens when the context changed between
old and new commit. When that context line starts with a tab (after the
space that marks it as context line), our diff machinery spits out a
white-space error (space before tab), but in this case, that is
incorrect.

Rather than adding a specific whitespace flag that specifically ignores
the first space in the output (and might miss other problems with the
white-space warnings), let's just skip handling white-space errors in
dual color mode to begin with.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 diff.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/diff.c b/diff.c
index e6c857abf..ea8ecae04 100644
--- a/diff.c
+++ b/diff.c
@@ -1299,6 +1299,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 				set = diff_get_color_opt(o, DIFF_FRAGINFO);
 			else if (c != '+')
 				set = diff_get_color_opt(o, DIFF_CONTEXT);
+			flags &= ~DIFF_SYMBOL_CONTENT_WS_MASK;
 		}
 		emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK,
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 17/21] range-diff: populate the man page
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                           ` (15 preceding siblings ...)
  2018-08-10 22:14         ` [PATCH v5 16/21] range-diff --dual-color: skip white-space warnings Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 18/21] completion: support `git range-diff` Johannes Schindelin via GitGitGadget
                           ` (4 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

The bulk of this patch consists of a heavily butchered version of
tbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from
https://github.com/trast/tbdiff.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/git-range-diff.txt | 229 +++++++++++++++++++++++++++++++
 1 file changed, 229 insertions(+)

diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
index 49f717db8..bebb47d42 100644
--- a/Documentation/git-range-diff.txt
+++ b/Documentation/git-range-diff.txt
@@ -5,6 +5,235 @@ NAME
 ----
 git-range-diff - Compare two commit ranges (e.g. two versions of a branch)
 
+SYNOPSIS
+--------
+[verse]
+'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]
+	[--dual-color] [--creation-factor=<factor>]
+	( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
+
+DESCRIPTION
+-----------
+
+This command shows the differences between two versions of a patch
+series, or more generally, two commit ranges (ignoring merge commits).
+
+To that end, it first finds pairs of commits from both commit ranges
+that correspond with each other. Two commits are said to correspond when
+the diff between their patches (i.e. the author information, the commit
+message and the commit diff) is reasonably small compared to the
+patches' size. See ``Algorithm`` below for details.
+
+Finally, the list of matching commits is shown in the order of the
+second commit range, with unmatched commits being inserted just after
+all of their ancestors have been shown.
+
+
+OPTIONS
+-------
+--dual-color::
+	When the commit diffs differ, recreate the original diffs'
+	coloring, and add outer -/+ diff markers with the *background*
+	being red/green to make it easier to see e.g. when there was a
+	change in what exact lines were added.
+
+--creation-factor=<percent>::
+	Set the creation/deletion cost fudge factor to `<percent>`.
+	Defaults to 60. Try a larger value if `git range-diff` erroneously
+	considers a large change a total rewrite (deletion of one commit
+	and addition of another), and a smaller one in the reverse case.
+	See the ``Algorithm`` section below for an explanation why this is
+	needed.
+
+<range1> <range2>::
+	Compare the commits specified by the two ranges, where
+	`<range1>` is considered an older version of `<range2>`.
+
+<rev1>...<rev2>::
+	Equivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.
+
+<base> <rev1> <rev2>::
+	Equivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.
+	Note that `<base>` does not need to be the exact branch point
+	of the branches. Example: after rebasing a branch `my-topic`,
+	`git range-diff my-topic@{u} my-topic@{1} my-topic` would
+	show the differences introduced by the rebase.
+
+`git range-diff` also accepts the regular diff options (see
+linkgit:git-diff[1]), most notably the `--color=[<when>]` and
+`--no-color` options. These options are used when generating the "diff
+between patches", i.e. to compare the author, commit message and diff of
+corresponding old/new commits. There is currently no means to tweak the
+diff options passed to `git log` when generating those patches.
+
+
+CONFIGURATION
+-------------
+This command uses the `diff.color.*` and `pager.range-diff` settings
+(the latter is on by default).
+See linkgit:git-config[1].
+
+
+EXAMPLES
+--------
+
+When a rebase required merge conflicts to be resolved, compare the changes
+introduced by the rebase directly afterwards using:
+
+------------
+$ git range-diff @{u} @{1} @
+------------
+
+
+A typical output of `git range-diff` would look like this:
+
+------------
+-:  ------- > 1:  0ddba11 Prepare for the inevitable!
+1:  c0debee = 2:  cab005e Add a helpful message at the start
+2:  f00dbal ! 3:  decafe1 Describe a bug
+    @@ -1,3 +1,3 @@
+     Author: A U Thor <author@example.com>
+
+    -TODO: Describe a bug
+    +Describe a bug
+    @@ -324,5 +324,6
+      This is expected.
+
+    -+What is unexpected is that it will also crash.
+    ++Unexpectedly, it also crashes. This is a bug, and the jury is
+    ++still out there how to fix it best. See ticket #314 for details.
+
+      Contact
+3:  bedead < -:  ------- TO-UNDO
+------------
+
+In this example, there are 3 old and 3 new commits, where the developer
+removed the 3rd, added a new one before the first two, and modified the
+commit message of the 2nd commit as well its diff.
+
+When the output goes to a terminal, it is color-coded by default, just
+like regular `git diff`'s output. In addition, the first line (adding a
+commit) is green, the last line (deleting a commit) is red, the second
+line (with a perfect match) is yellow like the commit header of `git
+show`'s output, and the third line colors the old commit red, the new
+one green and the rest like `git show`'s commit header.
+
+The color-coded diff is actually a bit hard to read, though, as it
+colors the entire lines red or green. The line that added "What is
+unexpected" in the old commit, for example, is completely red, even if
+the intent of the old commit was to add something.
+
+To help with that, use the `--dual-color` mode. In this mode, the diff
+of diffs will retain the original diff colors, and prefix the lines with
+-/+ markers that have their *background* red or green, to make it more
+obvious that they describe how the diff itself changed.
+
+
+Algorithm
+---------
+
+The general idea is this: we generate a cost matrix between the commits
+in both commit ranges, then solve the least-cost assignment.
+
+The cost matrix is populated thusly: for each pair of commits, both
+diffs are generated and the "diff of diffs" is generated, with 3 context
+lines, then the number of lines in that diff is used as cost.
+
+To avoid false positives (e.g. when a patch has been removed, and an
+unrelated patch has been added between two iterations of the same patch
+series), the cost matrix is extended to allow for that, by adding
+fixed-cost entries for wholesale deletes/adds.
+
+Example: Let commits `1--2` be the first iteration of a patch series and
+`A--C` the second iteration. Let's assume that `A` is a cherry-pick of
+`2,` and `C` is a cherry-pick of `1` but with a small modification (say,
+a fixed typo). Visualize the commits as a bipartite graph:
+
+------------
+    1            A
+
+    2            B
+
+		 C
+------------
+
+We are looking for a "best" explanation of the new series in terms of
+the old one. We can represent an "explanation" as an edge in the graph:
+
+
+------------
+    1            A
+	       /
+    2 --------'  B
+
+		 C
+------------
+
+This explanation comes for "free" because there was no change. Similarly
+`C` could be explained using `1`, but that comes at some cost c>0
+because of the modification:
+
+------------
+    1 ----.      A
+	  |    /
+    2 ----+---'  B
+	  |
+	  `----- C
+	  c>0
+------------
+
+In mathematical terms, what we are looking for is some sort of a minimum
+cost bipartite matching; `1` is matched to `C` at some cost, etc. The
+underlying graph is in fact a complete bipartite graph; the cost we
+associate with every edge is the size of the diff between the two
+commits' patches. To explain also new commits, we introduce dummy nodes
+on both sides:
+
+------------
+    1 ----.      A
+	  |    /
+    2 ----+---'  B
+	  |
+    o     `----- C
+	  c>0
+    o            o
+
+    o            o
+------------
+
+The cost of an edge `o--C` is the size of `C`'s diff, modified by a
+fudge factor that should be smaller than 100%. The cost of an edge
+`o--o` is free. The fudge factor is necessary because even if `1` and
+`C` have nothing in common, they may still share a few empty lines and
+such, possibly making the assignment `1--C`, `o--o` slightly cheaper
+than `1--o`, `o--C` even if `1` and `C` have nothing in common. With the
+fudge factor we require a much larger common part to consider patches as
+corresponding.
+
+The overall time needed to compute this algorithm is the time needed to
+compute n+m commit diffs and then n*m diffs of patches, plus the time
+needed to compute the least-cost assigment between n and m diffs. Git
+uses an implementation of the Jonker-Volgenant algorithm to solve the
+assignment problem, which has cubic runtime complexity. The matching
+found in this case will look like this:
+
+------------
+    1 ----.      A
+	  |    /
+    2 ----+---'  B
+       .--+-----'
+    o -'  `----- C
+	  c>0
+    o ---------- o
+
+    o ---------- o
+------------
+
+
+SEE ALSO
+--------
+linkgit:git-log[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 18/21] completion: support `git range-diff`
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                           ` (16 preceding siblings ...)
  2018-08-10 22:14         ` [PATCH v5 17/21] range-diff: populate the man page Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 19/21] range-diff: left-pad patch numbers Johannes Schindelin via GitGitGadget
                           ` (3 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

Tab completion of `git range-diff` is very convenient, especially
given that the revision arguments to specify the commit ranges to
compare are typically more complex than, say, what is normally passed
to `git log`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 contrib/completion/git-completion.bash | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 94c95516e..3d4ec3432 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1976,6 +1976,20 @@ _git_push ()
 	__git_complete_remote_or_refspec
 }
 
+_git_range_diff ()
+{
+	case "$cur" in
+	--*)
+		__gitcomp "
+			--creation-factor= --dual-color
+			$__git_diff_common_options
+		"
+		return
+		;;
+	esac
+	__git_complete_revlist
+}
+
 _git_rebase ()
 {
 	__git_find_repo_path
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 19/21] range-diff: left-pad patch numbers
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                           ` (17 preceding siblings ...)
  2018-08-10 22:14         ` [PATCH v5 18/21] completion: support `git range-diff` Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 20/21] range-diff: make --dual-color the default mode Johannes Schindelin via GitGitGadget
                           ` (2 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

As pointed out by Elijah Newren, tbdiff has this neat little alignment
trick where it outputs the commit pairs with patch numbers that are
padded to the maximal patch number's width:

	  1: cafedead =   1: acefade first patch
	[...]
	314: beefeada < 314: facecab up to PI!

Let's do the same in range-diff, too.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/range-diff.c b/range-diff.c
index b1663da7c..b6b9abac2 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -259,6 +259,7 @@ static void get_correspondences(struct string_list *a, struct string_list *b,
 }
 
 static void output_pair_header(struct diff_options *diffopt,
+			       int patch_no_width,
 			       struct strbuf *buf,
 			       struct strbuf *dashes,
 			       struct patch_util *a_util,
@@ -295,9 +296,9 @@ static void output_pair_header(struct diff_options *diffopt,
 	strbuf_reset(buf);
 	strbuf_addstr(buf, status == '!' ? color_old : color);
 	if (!a_util)
-		strbuf_addf(buf, "-:  %s ", dashes->buf);
+		strbuf_addf(buf, "%*s:  %s ", patch_no_width, "-", dashes->buf);
 	else
-		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
+		strbuf_addf(buf, "%*d:  %s ", patch_no_width, a_util->i + 1,
 			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
 
 	if (status == '!')
@@ -307,9 +308,9 @@ static void output_pair_header(struct diff_options *diffopt,
 		strbuf_addf(buf, "%s%s", color_reset, color_new);
 
 	if (!b_util)
-		strbuf_addf(buf, " -:  %s", dashes->buf);
+		strbuf_addf(buf, " %*s:  %s", patch_no_width, "-", dashes->buf);
 	else
-		strbuf_addf(buf, " %d:  %s", b_util->i + 1,
+		strbuf_addf(buf, " %*d:  %s", patch_no_width, b_util->i + 1,
 			    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));
 
 	commit = lookup_commit_reference(the_repository, oid);
@@ -357,6 +358,7 @@ static void output(struct string_list *a, struct string_list *b,
 		   struct diff_options *diffopt)
 {
 	struct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;
+	int patch_no_width = decimal_width(1 + (a->nr > b->nr ? a->nr : b->nr));
 	int i = 0, j = 0;
 
 	/*
@@ -378,7 +380,7 @@ static void output(struct string_list *a, struct string_list *b,
 
 		/* Show unmatched LHS commit whose predecessors were shown. */
 		if (i < a->nr && a_util->matching < 0) {
-			output_pair_header(diffopt,
+			output_pair_header(diffopt, patch_no_width,
 					   &buf, &dashes, a_util, NULL);
 			i++;
 			continue;
@@ -386,7 +388,7 @@ static void output(struct string_list *a, struct string_list *b,
 
 		/* Show unmatched RHS commits. */
 		while (j < b->nr && b_util->matching < 0) {
-			output_pair_header(diffopt,
+			output_pair_header(diffopt, patch_no_width,
 					   &buf, &dashes, NULL, b_util);
 			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
@@ -394,7 +396,7 @@ static void output(struct string_list *a, struct string_list *b,
 		/* Show matching LHS/RHS pair. */
 		if (j < b->nr) {
 			a_util = a->items[b_util->matching].util;
-			output_pair_header(diffopt,
+			output_pair_header(diffopt, patch_no_width,
 					   &buf, &dashes, a_util, b_util);
 			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
 				patch_diff(a->items[b_util->matching].string,
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 20/21] range-diff: make --dual-color the default mode
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                           ` (18 preceding siblings ...)
  2018-08-10 22:14         ` [PATCH v5 19/21] range-diff: left-pad patch numbers Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-10 22:14         ` [PATCH v5 21/21] range-diff: use dim/bold cues to improve dual color mode Johannes Schindelin via GitGitGadget
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

After using this command extensively for the last two months, this
developer came to the conclusion that even if the dual color mode still
leaves a lot of room for confusion about what was actually changed, the
non-dual color mode is substantially worse in that regard.

Therefore, we really want to make the dual color mode the default.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/git-range-diff.txt       | 32 +++++++++++++++-----------
 builtin/range-diff.c                   | 10 ++++----
 contrib/completion/git-completion.bash |  2 +-
 3 files changed, 25 insertions(+), 19 deletions(-)

diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
index bebb47d42..82c71c682 100644
--- a/Documentation/git-range-diff.txt
+++ b/Documentation/git-range-diff.txt
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]
-	[--dual-color] [--creation-factor=<factor>]
+	[--no-dual-color] [--creation-factor=<factor>]
 	( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
 
 DESCRIPTION
@@ -31,11 +31,14 @@ all of their ancestors have been shown.
 
 OPTIONS
 -------
---dual-color::
-	When the commit diffs differ, recreate the original diffs'
-	coloring, and add outer -/+ diff markers with the *background*
-	being red/green to make it easier to see e.g. when there was a
-	change in what exact lines were added.
+--no-dual-color::
+	When the commit diffs differ, `git range-diff` recreates the
+	original diffs' coloring, and adds outer -/+ diff markers with
+	the *background* being red/green to make it easier to see e.g.
+	when there was a change in what exact lines were added. This is
+	known to `range-diff` as "dual coloring". Use `--no-dual-color`
+	to revert to color all lines according to the outer diff markers
+	(and completely ignore the inner diff when it comes to color).
 
 --creation-factor=<percent>::
 	Set the creation/deletion cost fudge factor to `<percent>`.
@@ -118,15 +121,16 @@ line (with a perfect match) is yellow like the commit header of `git
 show`'s output, and the third line colors the old commit red, the new
 one green and the rest like `git show`'s commit header.
 
-The color-coded diff is actually a bit hard to read, though, as it
-colors the entire lines red or green. The line that added "What is
-unexpected" in the old commit, for example, is completely red, even if
-the intent of the old commit was to add something.
+A naive color-coded diff of diffs is actually a bit hard to read,
+though, as it colors the entire lines red or green. The line that added
+"What is unexpected" in the old commit, for example, is completely red,
+even if the intent of the old commit was to add something.
 
-To help with that, use the `--dual-color` mode. In this mode, the diff
-of diffs will retain the original diff colors, and prefix the lines with
--/+ markers that have their *background* red or green, to make it more
-obvious that they describe how the diff itself changed.
+To help with that, `range` uses the `--dual-color` mode by default. In
+this mode, the diff of diffs will retain the original diff colors, and
+prefix the lines with -/+ markers that have their *background* red or
+green, to make it more obvious that they describe how the diff itself
+changed.
 
 
 Algorithm
diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index da3ad3eba..ef3ba22e2 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -20,11 +20,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 {
 	int creation_factor = 60;
 	struct diff_options diffopt = { NULL };
-	int dual_color = 0;
+	int simple_color = -1;
 	struct option options[] = {
 		OPT_INTEGER(0, "creation-factor", &creation_factor,
 			    N_("Percentage by which creation is weighted")),
-		OPT_BOOL(0, "dual-color", &dual_color,
+		OPT_BOOL(0, "no-dual-color", &simple_color,
 			    N_("color both diff and diff-between-diffs")),
 		OPT_END()
 	};
@@ -61,8 +61,10 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 			     options + ARRAY_SIZE(options) - 1, /* OPT_END */
 			     builtin_range_diff_usage, 0);
 
-	if (dual_color) {
-		diffopt.use_color = 1;
+	if (simple_color < 1) {
+		if (!simple_color)
+			/* force color when --dual-color was used */
+			diffopt.use_color = 1;
 		diffopt.flags.dual_color_diffed_diffs = 1;
 	}
 
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 3d4ec3432..d63d2dffd 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1981,7 +1981,7 @@ _git_range_diff ()
 	case "$cur" in
 	--*)
 		__gitcomp "
-			--creation-factor= --dual-color
+			--creation-factor= --no-dual-color
 			$__git_diff_common_options
 		"
 		return
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v5 21/21] range-diff: use dim/bold cues to improve dual color mode
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                           ` (19 preceding siblings ...)
  2018-08-10 22:14         ` [PATCH v5 20/21] range-diff: make --dual-color the default mode Johannes Schindelin via GitGitGadget
@ 2018-08-10 22:14         ` Johannes Schindelin via GitGitGadget
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-10 22:14 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

It *is* a confusing thing to look at a diff of diffs. All too easy is it
to mix up whether the -/+ markers refer to the "inner" or the "outer"
diff, i.e. whether a `+` indicates that a line was added by either the
old or the new diff (or both), or whether the new diff does something
different than the old diff.

To make things easier to process for normal developers, we introduced
the dual color mode which colors the lines according to the commit diff,
i.e. lines that are added by a commit (whether old, new, or both) are
colored in green. In non-dual color mode, the lines would be colored
according to the outer diff: if the old commit added a line, it would be
colored red (because that line addition is only present in the first
commit range that was specified on the command-line, i.e. the "old"
commit, but not in the second commit range, i.e. the "new" commit).

However, this dual color mode is still not making things clear enough,
as we are looking at two levels of diffs, and we still only pick a color
according to *one* of them (the outer diff marker is colored
differently, of course, but in particular with deep indentation, it is
easy to lose track of that outer diff marker's background color).

Therefore, let's add another dimension to the mix. Still use
green/red/normal according to the commit diffs, but now also dim the
lines that were only in the old commit, and use bold face for the lines
that are only in the new commit.

That way, it is much easier not to lose track of, say, when we are
looking at a line that was added in the previous iteration of a patch
series but the new iteration adds a slightly different version: the
obsolete change will be dimmed, the current version of the patch will be
bold.

At least this developer has a much easier time reading the range-diffs
that way.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/config.txt         |  6 ++++--
 Documentation/git-range-diff.txt | 17 +++++++++++++----
 color.h                          |  6 ++++++
 diff.c                           | 28 ++++++++++++++++++++++------
 diff.h                           |  8 +++++++-
 5 files changed, 52 insertions(+), 13 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 63365dcf3..90241ed77 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1193,8 +1193,10 @@ color.diff.<slot>::
 	(highlighting whitespace errors), `oldMoved` (deleted lines),
 	`newMoved` (added lines), `oldMovedDimmed`, `oldMovedAlternative`,
 	`oldMovedAlternativeDimmed`, `newMovedDimmed`, `newMovedAlternative`
-	and `newMovedAlternativeDimmed` (See the '<mode>'
-	setting of '--color-moved' in linkgit:git-diff[1] for details).
+	`newMovedAlternativeDimmed` (See the '<mode>'
+	setting of '--color-moved' in linkgit:git-diff[1] for details),
+	`contextDimmed`, `oldDimmed`, `newDimmed`, `contextBold`,
+	`oldBold`, and `newBold` (see linkgit:git-range-diff[1] for details).
 
 color.decorate.<slot>::
 	Use customized color for 'git log --decorate' output.  `<slot>` is one
diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
index 82c71c682..f693930fd 100644
--- a/Documentation/git-range-diff.txt
+++ b/Documentation/git-range-diff.txt
@@ -35,10 +35,19 @@ OPTIONS
 	When the commit diffs differ, `git range-diff` recreates the
 	original diffs' coloring, and adds outer -/+ diff markers with
 	the *background* being red/green to make it easier to see e.g.
-	when there was a change in what exact lines were added. This is
-	known to `range-diff` as "dual coloring". Use `--no-dual-color`
-	to revert to color all lines according to the outer diff markers
-	(and completely ignore the inner diff when it comes to color).
+	when there was a change in what exact lines were added.
++
+Additionally, the commit diff lines that are only present in the first commit
+range are shown "dimmed" (this can be overridden using the `color.diff.<slot>`
+config setting where `<slot>` is one of `contextDimmed`, `oldDimmed` and
+`newDimmed`), and the commit diff lines that are only present in the second
+commit range are shown in bold (which can be overridden using the config
+settings `color.diff.<slot>` with `<slot>` being one of `contextBold`,
+`oldBold` or `newBold`).
++
+This is known to `range-diff` as "dual coloring". Use `--no-dual-color`
+to revert to color all lines according to the outer diff markers
+(and completely ignore the inner diff when it comes to color).
 
 --creation-factor=<percent>::
 	Set the creation/deletion cost fudge factor to `<percent>`.
diff --git a/color.h b/color.h
index 33e786342..98894d6a1 100644
--- a/color.h
+++ b/color.h
@@ -36,6 +36,12 @@ struct strbuf;
 #define GIT_COLOR_BOLD_BLUE	"\033[1;34m"
 #define GIT_COLOR_BOLD_MAGENTA	"\033[1;35m"
 #define GIT_COLOR_BOLD_CYAN	"\033[1;36m"
+#define GIT_COLOR_FAINT_RED	"\033[2;31m"
+#define GIT_COLOR_FAINT_GREEN	"\033[2;32m"
+#define GIT_COLOR_FAINT_YELLOW	"\033[2;33m"
+#define GIT_COLOR_FAINT_BLUE	"\033[2;34m"
+#define GIT_COLOR_FAINT_MAGENTA	"\033[2;35m"
+#define GIT_COLOR_FAINT_CYAN	"\033[2;36m"
 #define GIT_COLOR_BG_RED	"\033[41m"
 #define GIT_COLOR_BG_GREEN	"\033[42m"
 #define GIT_COLOR_BG_YELLOW	"\033[43m"
diff --git a/diff.c b/diff.c
index ea8ecae04..ae1314952 100644
--- a/diff.c
+++ b/diff.c
@@ -70,6 +70,12 @@ static char diff_colors[][COLOR_MAXLEN] = {
 	GIT_COLOR_BOLD_YELLOW,	/* NEW_MOVED ALTERNATIVE */
 	GIT_COLOR_FAINT,	/* NEW_MOVED_DIM */
 	GIT_COLOR_FAINT_ITALIC,	/* NEW_MOVED_ALTERNATIVE_DIM */
+	GIT_COLOR_FAINT,	/* CONTEXT_DIM */
+	GIT_COLOR_FAINT_RED,	/* OLD_DIM */
+	GIT_COLOR_FAINT_GREEN,	/* NEW_DIM */
+	GIT_COLOR_BOLD,		/* CONTEXT_BOLD */
+	GIT_COLOR_BOLD_RED,	/* OLD_BOLD */
+	GIT_COLOR_BOLD_GREEN,	/* NEW_BOLD */
 };
 
 static const char *color_diff_slots[] = {
@@ -89,6 +95,12 @@ static const char *color_diff_slots[] = {
 	[DIFF_FILE_NEW_MOVED_ALT]     = "newMovedAlternative",
 	[DIFF_FILE_NEW_MOVED_DIM]     = "newMovedDimmed",
 	[DIFF_FILE_NEW_MOVED_ALT_DIM] = "newMovedAlternativeDimmed",
+	[DIFF_CONTEXT_DIM]	      = "contextDimmed",
+	[DIFF_FILE_OLD_DIM]	      = "oldDimmed",
+	[DIFF_FILE_NEW_DIM]	      = "newDimmed",
+	[DIFF_CONTEXT_BOLD]	      = "contextBold",
+	[DIFF_FILE_OLD_BOLD]	      = "oldBold",
+	[DIFF_FILE_NEW_BOLD]	      = "newBold",
 };
 
 static NORETURN void die_want_option(const char *option_name)
@@ -1294,11 +1306,13 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 
 			set_sign = set;
 			if (c == '-')
-				set = diff_get_color_opt(o, DIFF_FILE_OLD);
+				set = diff_get_color_opt(o, DIFF_FILE_OLD_BOLD);
 			else if (c == '@')
 				set = diff_get_color_opt(o, DIFF_FRAGINFO);
-			else if (c != '+')
-				set = diff_get_color_opt(o, DIFF_CONTEXT);
+			else if (c == '+')
+				set = diff_get_color_opt(o, DIFF_FILE_NEW_BOLD);
+			else
+				set = diff_get_color_opt(o, DIFF_CONTEXT_BOLD);
 			flags &= ~DIFF_SYMBOL_CONTENT_WS_MASK;
 		}
 		emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
@@ -1336,11 +1350,13 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 
 			set_sign = set;
 			if (c == '+')
-				set = diff_get_color_opt(o, DIFF_FILE_NEW);
+				set = diff_get_color_opt(o, DIFF_FILE_NEW_DIM);
 			else if (c == '@')
 				set = diff_get_color_opt(o, DIFF_FRAGINFO);
-			else if (c != '-')
-				set = diff_get_color_opt(o, DIFF_CONTEXT);
+			else if (c == '-')
+				set = diff_get_color_opt(o, DIFF_FILE_OLD_DIM);
+			else
+				set = diff_get_color_opt(o, DIFF_CONTEXT_DIM);
 		}
 		emit_line_ws_markup(o, set, reset, line, len, set_sign, '-',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);
diff --git a/diff.h b/diff.h
index cca4f9d6c..e1e54256c 100644
--- a/diff.h
+++ b/diff.h
@@ -248,7 +248,13 @@ enum color_diff {
 	DIFF_FILE_NEW_MOVED = 13,
 	DIFF_FILE_NEW_MOVED_ALT = 14,
 	DIFF_FILE_NEW_MOVED_DIM = 15,
-	DIFF_FILE_NEW_MOVED_ALT_DIM = 16
+	DIFF_FILE_NEW_MOVED_ALT_DIM = 16,
+	DIFF_CONTEXT_DIM = 17,
+	DIFF_FILE_OLD_DIM = 18,
+	DIFF_FILE_NEW_DIM = 19,
+	DIFF_CONTEXT_BOLD = 20,
+	DIFF_FILE_OLD_BOLD = 21,
+	DIFF_FILE_NEW_BOLD = 22,
 };
 const char *diff_get_color(int diff_use_color, enum color_diff ix);
 #define diff_get_color_opt(o, ix) \
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 387+ messages in thread

* Re: [PATCH v5 05/21] range-diff: also show the diff between patches
  2018-08-10 22:14         ` [PATCH v5 05/21] range-diff: also show the diff between patches Johannes Schindelin via GitGitGadget
@ 2018-08-12 21:47           ` Thomas Gummerer
  2018-08-13  9:46             ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Thomas Gummerer @ 2018-08-12 21:47 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: git, Junio C Hamano, Johannes Schindelin

Hi Dscho,

On 08/10, Johannes Schindelin via GitGitGadget wrote:
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
>
> [..]
> 
> @@ -13,15 +14,38 @@ NULL
>  int cmd_range_diff(int argc, const char **argv, const char *prefix)
>  {
>  	int creation_factor = 60;
> +	struct diff_options diffopt = { NULL };
>  	struct option options[] = {
>  		OPT_INTEGER(0, "creation-factor", &creation_factor,
>  			    N_("Percentage by which creation is weighted")),
>  		OPT_END()
>  	};
> -	int res = 0;
> +	int i, j, res = 0;
>  	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
>  
> +	git_config(git_diff_ui_config, NULL);
> +
> +	diff_setup(&diffopt);
> +	diffopt.output_format = DIFF_FORMAT_PATCH;
> +
>  	argc = parse_options(argc, argv, NULL, options,
> +			     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN |
> +			     PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
> +
> +	for (i = j = 1; i < argc && strcmp("--", argv[i]); ) {
> +		int c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);
> +
> +		if (!c)
> +			argv[j++] = argv[i++];
> +		else
> +			i += c;
> +	}

I don't think this handles "--" quite as would be expected.  Trying to
use "git range-diff -- js/range-diff-v4...HEAD" I get:

    $ ./git range-diff -- js/range-diff-v4...HEAD
    error: need two commit ranges
    usage: git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>
       or: git range-diff [<options>] <old-tip>...<new-tip>
       or: git range-diff [<options>] <base> <old-tip> <new-tip>

        --creation-factor <n>
                              Percentage by which creation is weighted
        --no-dual-color       color both diff and diff-between-diffs

while what I would have expected is to actually get a range diff.
This happens because after we break out of the loop we don't add the
actual ranges to argv, but just skip them instead.

I think something like the following should be squashed in to this
patch.

--->8---
diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index ef3ba22e29..132574c57a 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -53,6 +53,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
                else
                        i += c;
        }
+       if (i < argc && !strcmp("--", argv[i])) {
+               i++; j++;
+               while (i < argc)
+                       argv[j++] = argv[i++];
+       }
        argc = j;
        diff_setup_done(&diffopt);
 
--->8---

> +	argc = j;
> +	diff_setup_done(&diffopt);
> +
> +	/* Make sure that there are no unparsed options */
> +	argc = parse_options(argc, argv, NULL,
> +			     options + ARRAY_SIZE(options) - 1, /* OPT_END */
>  			     builtin_range_diff_usage, 0);
>  
>  	if (argc == 2) {
> @@ -59,7 +83,8 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
>  		usage_with_options(builtin_range_diff_usage, options);
>  	}
>  
> -	res = show_range_diff(range1.buf, range2.buf, creation_factor);
> +	res = show_range_diff(range1.buf, range2.buf, creation_factor,
> +			      &diffopt);
>  
>  	strbuf_release(&range1);
>  	strbuf_release(&range2);
> diff --git a/range-diff.c b/range-diff.c
> index 2d94200d3..71883a4b7 100644
> --- a/range-diff.c
> +++ b/range-diff.c
> @@ -6,6 +6,7 @@
>  #include "hashmap.h"
>  #include "xdiff-interface.h"
>  #include "linear-assignment.h"
> +#include "diffcore.h"
>  
>  struct patch_util {
>  	/* For the search for an exact match */
> @@ -258,7 +259,31 @@ static const char *short_oid(struct patch_util *util)
>  	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
>  }
>  
> -static void output(struct string_list *a, struct string_list *b)
> +static struct diff_filespec *get_filespec(const char *name, const char *p)
> +{
> +	struct diff_filespec *spec = alloc_filespec(name);
> +
> +	fill_filespec(spec, &null_oid, 0, 0644);
> +	spec->data = (char *)p;
> +	spec->size = strlen(p);
> +	spec->should_munmap = 0;
> +	spec->is_stdin = 1;
> +
> +	return spec;
> +}
> +
> +static void patch_diff(const char *a, const char *b,
> +			      struct diff_options *diffopt)
> +{
> +	diff_queue(&diff_queued_diff,
> +		   get_filespec("a", a), get_filespec("b", b));
> +
> +	diffcore_std(diffopt);
> +	diff_flush(diffopt);
> +}
> +
> +static void output(struct string_list *a, struct string_list *b,
> +		   struct diff_options *diffopt)
>  {
>  	int i = 0, j = 0;
>  
> @@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)
>  			printf("%d: %s ! %d: %s\n",
>  			       b_util->matching + 1, short_oid(a_util),
>  			       j + 1, short_oid(b_util));
> +			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
> +				patch_diff(a->items[b_util->matching].string,
> +					   b->items[j].string, diffopt);
>  			a_util->shown = 1;
>  			j++;
>  		}
> @@ -307,7 +335,7 @@ static void output(struct string_list *a, struct string_list *b)
>  }
>  
>  int show_range_diff(const char *range1, const char *range2,
> -		    int creation_factor)
> +		    int creation_factor, struct diff_options *diffopt)
>  {
>  	int res = 0;
>  
> @@ -322,7 +350,7 @@ int show_range_diff(const char *range1, const char *range2,
>  	if (!res) {
>  		find_exact_matches(&branch1, &branch2);
>  		get_correspondences(&branch1, &branch2, creation_factor);
> -		output(&branch1, &branch2);
> +		output(&branch1, &branch2, diffopt);
>  	}
>  
>  	string_list_clear(&branch1, 1);
> diff --git a/range-diff.h b/range-diff.h
> index 7b6eef303..2407d46a3 100644
> --- a/range-diff.h
> +++ b/range-diff.h
> @@ -1,7 +1,9 @@
>  #ifndef RANGE_DIFF_H
>  #define RANGE_DIFF_H
>  
> +#include "diff.h"
> +
>  int show_range_diff(const char *range1, const char *range2,
> -		    int creation_factor);
> +		    int creation_factor, struct diff_options *diffopt);
>  
>  #endif
> -- 
> gitgitgadget
> 

^ permalink raw reply related	[flat|nested] 387+ messages in thread

* Re: [PATCH v5 05/21] range-diff: also show the diff between patches
  2018-08-12 21:47           ` Thomas Gummerer
@ 2018-08-13  9:46             ` Johannes Schindelin
  2018-08-13 18:01               ` Thomas Gummerer
  0 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-08-13  9:46 UTC (permalink / raw)
  To: Thomas Gummerer; +Cc: Johannes Schindelin via GitGitGadget, git, Junio C Hamano

Hi Thomas,

On Sun, 12 Aug 2018, Thomas Gummerer wrote:

> On 08/10, Johannes Schindelin via GitGitGadget wrote:
> > From: Johannes Schindelin <johannes.schindelin@gmx.de>
> >
> > [..]
> > 
> > @@ -13,15 +14,38 @@ NULL
> >  int cmd_range_diff(int argc, const char **argv, const char *prefix)
> >  {
> >  	int creation_factor = 60;
> > +	struct diff_options diffopt = { NULL };
> >  	struct option options[] = {
> >  		OPT_INTEGER(0, "creation-factor", &creation_factor,
> >  			    N_("Percentage by which creation is weighted")),
> >  		OPT_END()
> >  	};
> > -	int res = 0;
> > +	int i, j, res = 0;
> >  	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
> >  
> > +	git_config(git_diff_ui_config, NULL);
> > +
> > +	diff_setup(&diffopt);
> > +	diffopt.output_format = DIFF_FORMAT_PATCH;
> > +
> >  	argc = parse_options(argc, argv, NULL, options,
> > +			     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN |
> > +			     PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
> > +
> > +	for (i = j = 1; i < argc && strcmp("--", argv[i]); ) {
> > +		int c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);
> > +
> > +		if (!c)
> > +			argv[j++] = argv[i++];
> > +		else
> > +			i += c;
> > +	}
> 
> I don't think this handles "--" quite as would be expected.  Trying to
> use "git range-diff -- js/range-diff-v4...HEAD" I get:
> 
>     $ ./git range-diff -- js/range-diff-v4...HEAD
>     error: need two commit ranges
>     usage: git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>
>        or: git range-diff [<options>] <old-tip>...<new-tip>
>        or: git range-diff [<options>] <base> <old-tip> <new-tip>
> 
>         --creation-factor <n>
>                               Percentage by which creation is weighted
>         --no-dual-color       color both diff and diff-between-diffs
> 
> while what I would have expected is to actually get a range diff.
> This happens because after we break out of the loop we don't add the
> actual ranges to argv, but just skip them instead.

Ouch, good point.

> I think something like the following should be squashed in to this
> patch.
> 
> --->8---
> diff --git a/builtin/range-diff.c b/builtin/range-diff.c
> index ef3ba22e29..132574c57a 100644
> --- a/builtin/range-diff.c
> +++ b/builtin/range-diff.c
> @@ -53,6 +53,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
>                 else
>                         i += c;
>         }
> +       if (i < argc && !strcmp("--", argv[i])) {
> +               i++; j++;
> +               while (i < argc)
> +                       argv[j++] = argv[i++];
> +       }
>         argc = j;
>         diff_setup_done(&diffopt);

I do not think that is correct. The original idea was for the first
`parse_options()` call to keep the dashdash, for the second one to keep
the dashdash, too, and for the final one to swallow it.

Also, if `i < argc` at this point, we already know that `argv[i]` refers
to the dashdash, otherwise the previous loop would not have exited early.

I went with this simple version instead:

	while (i < argc)
		argv[j++] = argv[i++];

Thanks!
Dscho

> --->8---
> 
> > +	argc = j;
> > +	diff_setup_done(&diffopt);
> > +
> > +	/* Make sure that there are no unparsed options */
> > +	argc = parse_options(argc, argv, NULL,
> > +			     options + ARRAY_SIZE(options) - 1, /* OPT_END */
> >  			     builtin_range_diff_usage, 0);
> >  
> >  	if (argc == 2) {
> > @@ -59,7 +83,8 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
> >  		usage_with_options(builtin_range_diff_usage, options);
> >  	}
> >  
> > -	res = show_range_diff(range1.buf, range2.buf, creation_factor);
> > +	res = show_range_diff(range1.buf, range2.buf, creation_factor,
> > +			      &diffopt);
> >  
> >  	strbuf_release(&range1);
> >  	strbuf_release(&range2);
> > diff --git a/range-diff.c b/range-diff.c
> > index 2d94200d3..71883a4b7 100644
> > --- a/range-diff.c
> > +++ b/range-diff.c
> > @@ -6,6 +6,7 @@
> >  #include "hashmap.h"
> >  #include "xdiff-interface.h"
> >  #include "linear-assignment.h"
> > +#include "diffcore.h"
> >  
> >  struct patch_util {
> >  	/* For the search for an exact match */
> > @@ -258,7 +259,31 @@ static const char *short_oid(struct patch_util *util)
> >  	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
> >  }
> >  
> > -static void output(struct string_list *a, struct string_list *b)
> > +static struct diff_filespec *get_filespec(const char *name, const char *p)
> > +{
> > +	struct diff_filespec *spec = alloc_filespec(name);
> > +
> > +	fill_filespec(spec, &null_oid, 0, 0644);
> > +	spec->data = (char *)p;
> > +	spec->size = strlen(p);
> > +	spec->should_munmap = 0;
> > +	spec->is_stdin = 1;
> > +
> > +	return spec;
> > +}
> > +
> > +static void patch_diff(const char *a, const char *b,
> > +			      struct diff_options *diffopt)
> > +{
> > +	diff_queue(&diff_queued_diff,
> > +		   get_filespec("a", a), get_filespec("b", b));
> > +
> > +	diffcore_std(diffopt);
> > +	diff_flush(diffopt);
> > +}
> > +
> > +static void output(struct string_list *a, struct string_list *b,
> > +		   struct diff_options *diffopt)
> >  {
> >  	int i = 0, j = 0;
> >  
> > @@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)
> >  			printf("%d: %s ! %d: %s\n",
> >  			       b_util->matching + 1, short_oid(a_util),
> >  			       j + 1, short_oid(b_util));
> > +			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
> > +				patch_diff(a->items[b_util->matching].string,
> > +					   b->items[j].string, diffopt);
> >  			a_util->shown = 1;
> >  			j++;
> >  		}
> > @@ -307,7 +335,7 @@ static void output(struct string_list *a, struct string_list *b)
> >  }
> >  
> >  int show_range_diff(const char *range1, const char *range2,
> > -		    int creation_factor)
> > +		    int creation_factor, struct diff_options *diffopt)
> >  {
> >  	int res = 0;
> >  
> > @@ -322,7 +350,7 @@ int show_range_diff(const char *range1, const char *range2,
> >  	if (!res) {
> >  		find_exact_matches(&branch1, &branch2);
> >  		get_correspondences(&branch1, &branch2, creation_factor);
> > -		output(&branch1, &branch2);
> > +		output(&branch1, &branch2, diffopt);
> >  	}
> >  
> >  	string_list_clear(&branch1, 1);
> > diff --git a/range-diff.h b/range-diff.h
> > index 7b6eef303..2407d46a3 100644
> > --- a/range-diff.h
> > +++ b/range-diff.h
> > @@ -1,7 +1,9 @@
> >  #ifndef RANGE_DIFF_H
> >  #define RANGE_DIFF_H
> >  
> > +#include "diff.h"
> > +
> >  int show_range_diff(const char *range1, const char *range2,
> > -		    int creation_factor);
> > +		    int creation_factor, struct diff_options *diffopt);
> >  
> >  #endif
> > -- 
> > gitgitgadget
> > 
> 

^ permalink raw reply	[flat|nested] 387+ messages in thread

* [PATCH v6 00/21] Add range-diff, a tbdiff lookalike
  2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                           ` (20 preceding siblings ...)
  2018-08-10 22:14         ` [PATCH v5 21/21] range-diff: use dim/bold cues to improve dual color mode Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:32         ` Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 01/21] linear-assignment: a function to solve least-cost assignment problems Johannes Schindelin via GitGitGadget
                             ` (21 more replies)
  21 siblings, 22 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:32 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

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.

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.

While at it, I forward-ported AEvar's patch to force --decorate=no because 
git -p tbdiff would fail otherwise.

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 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:

        base=^{/Start.the.merging-rebase}
        tag=v2.17.0.windows.1
        pre=$tag$base^2
        git range-diff $pre$base..$pre $tag$base..$tag

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.

Changes since v4:

 * Fixed a typo in the commit message of "range-diff: add tests" that was
   introduced in v4.
 * White-space fixes.
 * Fixed the length of the first header underline in the man page.
 * Changed the preprocessor guard in linear-assignment.h to reflect the new
   name (instead of the old name, which was hungarian.h).
 * Likewise, changed the preprocessor guards in range-diff.h to hide the
   history of the thrice-renamed command.
 * Fixed indentation in the completion.
 * Instead of trying to paper over white-space error handling that does not
   apply to "diffs of diffs", dual color mode now simply disables all
   white-space warnings.
 * When showing the "single arg must be symmetric range" error message, git
   range-diff now also shows the usage.
 * Adjusted the commit message of "range-diff: adjust the output of the
   commit pairs" to avoid the surprise of the reviewer when onelines are
   printed all of a sudden, too.
 * "range-diff: adjust the output of the commit pairs" is now using a
   simpler way to print onelines.
 * We are now sandwiching the diff_opt_parse() loop between two 
   parse_options(), to make sure that we caught all options, and that the -- 
   separator is handled.
 * Adjusted the lookup_commit_reference() call to the newest master (it now
   takes a the_repository parameter).

Changes since v3:

 * 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).
 * The documentation was adjusted a bit more in the patch that makes 
   --dual-color the default.
 * Clarified the calculation of the cost matrix, as per Stefan Beller's
   request.
 * The man page now spells out that merge commits are ignored in the commit
   ranges (not merges per se).
 * The code in linear-assignment.c was adjusted to use the SWAP() macro.
 * 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.
 * A bogus indentation change was backed out from the patch introducing the
   first rudimentary implementation.
 * Instead of merely warning about missing .. in the 2-parameter invocation,
   we now exit with the error message.
 * 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).
 * A lengthy if...else if...else if...else was simplified (from a logical
   point of view) by reordering it.
 * The unnecessarily static variable dashes was turned into a local variable
   of the caller.
 * The commit message talking about the new man page still referred to git
   branch --diff, which has been fixed.
 * A forgotten t7910 reference was changed to t3206.
 * An unbalanced double-tick was fixed in the man page.
 * Fixed grammar both of the commit message and the description of the 
   --no-dual-color option.
 * 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).
 * 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.
 * 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".

Changes since v2:

 * Right-aligned the patch numbers in the commit pairs.
 * Used ALLOC_ARRAY() in hungarian.c instead of xmalloc(sizeof()*size).
 * Changed compute_assignment()s return type from int to void, as it always
   succeeds.
 * Changed the Hungarian Algorithm to use an integer cost matrix.
 * Changed the --creation-weight option to --creation-factor where is an
   integer.
 * Retitled 1/19 and 2/19 to better conform with the current conventions, as
   pointed out (and suggested) by Junio.
 * Shut up Coverity, and at the same time avoided passing the unnecessary i 
   and j parameters to output_pair_header().
 * Removed support for the --no-patches option: we inherit diff_options'
   support for -s already (and much more).
 * 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.
 * Instead of overriding the fragment header color, the dual color mode will
   now reverse the "outer" fragment headers, too.
 * 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.
 * Pretty much re-wrote the completion, to support the new --diff mode of
   git-branch. See below: it was reverted for range-diff.
 * Renamed t7910 to t3206, to be closer to the git-branch tests.
 * Ensured that git_diff_ui_config() gets called, and therefore color.diff.*
   respected.
 * Avoided leaking four_spaces.
 * Fixed a declaration in a for (;;) statement (which Junio had as a fixup!
   that I almost missed).
 * Renamed branch --diff, which had been renamed from branch-diff (which was
   picked to avoid re-using tbdiff) to range-diff.
 * Renamed hungarian.c and its header to linear-assignment.c
 * Made --dual-color the default, and changed it to still auto-detect
   whether color should be used rather than forcing it

Johannes Schindelin (20):
  linear-assignment: a function to solve least-cost assignment problems
  Introduce `range-diff` to compare iterations of a topic branch
  range-diff: first rudimentary implementation
  range-diff: improve the order of the shown commits
  range-diff: also show the diff between patches
  range-diff: right-trim commit messages
  range-diff: indent the diffs just like tbdiff
  range-diff: suppress the diff headers
  range-diff: adjust the output of the commit pairs
  range-diff: do not show "function names" in hunk headers
  range-diff: use color for the commit pairs
  color: add the meta color GIT_COLOR_REVERSE
  diff: add an internal option to dual-color diffs of diffs
  range-diff: offer to dual-color the diffs
  range-diff --dual-color: skip white-space warnings
  range-diff: populate the man page
  completion: support `git range-diff`
  range-diff: left-pad patch numbers
  range-diff: make --dual-color the default mode
  range-diff: use dim/bold cues to improve dual color mode

Thomas Rast (1):
  range-diff: add tests

 .gitignore                             |   1 +
 Documentation/config.txt               |   6 +-
 Documentation/git-range-diff.txt       | 252 +++++++++++
 Makefile                               |   3 +
 builtin.h                              |   1 +
 builtin/range-diff.c                   | 116 +++++
 color.h                                |   7 +
 command-list.txt                       |   1 +
 contrib/completion/git-completion.bash |  14 +
 diff.c                                 | 105 ++++-
 diff.h                                 |  10 +-
 git.c                                  |   1 +
 linear-assignment.c                    | 201 ++++++++
 linear-assignment.h                    |  22 +
 range-diff.c                           | 435 ++++++++++++++++++
 range-diff.h                           |   9 +
 t/.gitattributes                       |   1 +
 t/t3206-range-diff.sh                  | 145 ++++++
 t/t3206/history.export                 | 604 +++++++++++++++++++++++++
 19 files changed, 1915 insertions(+), 19 deletions(-)
 create mode 100644 Documentation/git-range-diff.txt
 create mode 100644 builtin/range-diff.c
 create mode 100644 linear-assignment.c
 create mode 100644 linear-assignment.h
 create mode 100644 range-diff.c
 create mode 100644 range-diff.h
 create mode 100755 t/t3206-range-diff.sh
 create mode 100644 t/t3206/history.export


base-commit: 1d89318c48d233d52f1db230cf622935ac3c69fa
Published-As: https://github.com/gitgitgadget/git/releases/tags/pr-1%2Fdscho%2Fbranch-diff-v6
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1/dscho/branch-diff-v6
Pull-Request: https://github.com/gitgitgadget/git/pull/1

Range-diff vs v5:

  1:  f168da3a3 =  1:  f168da3a3 linear-assignment: a function to solve least-cost assignment problems
  2:  33758f361 =  2:  33758f361 Introduce `range-diff` to compare iterations of a topic branch
  3:  08b8c3fc4 =  3:  08b8c3fc4 range-diff: first rudimentary implementation
  4:  7b9091968 =  4:  7b9091968 range-diff: improve the order of the shown commits
  5:  9e1e66007 !  5:  8515d2f75 range-diff: also show the diff between patches
     @@ -80,6 +80,8 @@
      +		else
      +			i += c;
      +	}
     ++	while (i < argc)
     ++		argv[j++] = argv[i++];
      +	argc = j;
      +	diff_setup_done(&diffopt);
      +
  6:  167ca02a3 =  6:  a10ca0163 range-diff: right-trim commit messages
  7:  ca8de8c75 =  7:  f81cbef2c range-diff: indent the diffs just like tbdiff
  8:  eb94d1982 =  8:  458090ffd range-diff: suppress the diff headers
  9:  6330afad9 =  9:  d3be03a44 range-diff: adjust the output of the commit pairs
 10:  c296675eb = 10:  94b44dfe6 range-diff: do not show "function names" in hunk headers
 11:  85e0ab82f = 11:  1477c58e4 range-diff: add tests
 12:  f48b62644 = 12:  32492c159 range-diff: use color for the commit pairs
 13:  1ad74f939 = 13:  969a196f4 color: add the meta color GIT_COLOR_REVERSE
 14:  39a0ecd28 = 14:  f1c86f606 diff: add an internal option to dual-color diffs of diffs
 15:  c32a24f6a = 15:  3c7b9f339 range-diff: offer to dual-color the diffs
 16:  05947781f = 16:  c56c51c8b range-diff --dual-color: skip white-space warnings
 17:  3147c4440 = 17:  8c5543a06 range-diff: populate the man page
 18:  b08e6d937 = 18:  16e3cf27b completion: support `git range-diff`
 19:  19406283e = 19:  d9b09abcf range-diff: left-pad patch numbers
 20:  6b3552386 = 20:  f6fd3955e range-diff: make --dual-color the default mode
 21:  ccf8c1bb2 = 21:  699cd712e range-diff: use dim/bold cues to improve dual color mode

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 387+ messages in thread

* [PATCH v6 01/21] linear-assignment: a function to solve least-cost assignment problems
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 02/21] Introduce `range-diff` to compare iterations of a topic branch Johannes Schindelin via GitGitGadget
                             ` (20 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

The problem solved by the code introduced in this commit goes like this:
given two sets of items, and a cost matrix which says how much it
"costs" to assign any given item of the first set to any given item of
the second, assign all items (except when the sets have different size)
in the cheapest way.

We use the Jonker-Volgenant algorithm to solve the assignment problem to
answer questions such as: given two different versions of a topic branch
(or iterations of a patch series), what is the best pairing of
commits/patches between the different versions?

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Makefile            |   1 +
 linear-assignment.c | 201 ++++++++++++++++++++++++++++++++++++++++++++
 linear-assignment.h |  22 +++++
 3 files changed, 224 insertions(+)
 create mode 100644 linear-assignment.c
 create mode 100644 linear-assignment.h

diff --git a/Makefile b/Makefile
index bc4fc8eea..1af719b44 100644
--- a/Makefile
+++ b/Makefile
@@ -870,6 +870,7 @@ LIB_OBJS += gpg-interface.o
 LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hashmap.o
+LIB_OBJS += linear-assignment.o
 LIB_OBJS += help.o
 LIB_OBJS += hex.o
 LIB_OBJS += ident.o
diff --git a/linear-assignment.c b/linear-assignment.c
new file mode 100644
index 000000000..9b3e56e28
--- /dev/null
+++ b/linear-assignment.c
@@ -0,0 +1,201 @@
+/*
+ * Based on: Jonker, R., & Volgenant, A. (1987). <i>A shortest augmenting path
+ * algorithm for dense and sparse linear assignment problems</i>. Computing,
+ * 38(4), 325-340.
+ */
+#include "cache.h"
+#include "linear-assignment.h"
+
+#define COST(column, row) cost[(column) + column_count * (row)]
+
+/*
+ * The parameter `cost` is the cost matrix: the cost to assign column j to row
+ * i is `cost[j + column_count * i].
+ */
+void compute_assignment(int column_count, int row_count, int *cost,
+			int *column2row, int *row2column)
+{
+	int *v, *d;
+	int *free_row, free_count = 0, saved_free_count, *pred, *col;
+	int i, j, phase;
+
+	memset(column2row, -1, sizeof(int) * column_count);
+	memset(row2column, -1, sizeof(int) * row_count);
+	ALLOC_ARRAY(v, column_count);
+
+	/* column reduction */
+	for (j = column_count - 1; j >= 0; j--) {
+		int i1 = 0;
+
+		for (i = 1; i < row_count; i++)
+			if (COST(j, i1) > COST(j, i))
+				i1 = i;
+		v[j] = COST(j, i1);
+		if (row2column[i1] == -1) {
+			/* row i1 unassigned */
+			row2column[i1] = j;
+			column2row[j] = i1;
+		} else {
+			if (row2column[i1] >= 0)
+				row2column[i1] = -2 - row2column[i1];
+			column2row[j] = -1;
+		}
+	}
+
+	/* reduction transfer */
+	ALLOC_ARRAY(free_row, row_count);
+	for (i = 0; i < row_count; i++) {
+		int j1 = row2column[i];
+		if (j1 == -1)
+			free_row[free_count++] = i;
+		else if (j1 < -1)
+			row2column[i] = -2 - j1;
+		else {
+			int min = COST(!j1, i) - v[!j1];
+			for (j = 1; j < column_count; j++)
+				if (j != j1 && min > COST(j, i) - v[j])
+					min = COST(j, i) - v[j];
+			v[j1] -= min;
+		}
+	}
+
+	if (free_count ==
+	    (column_count < row_count ? row_count - column_count : 0)) {
+		free(v);
+		free(free_row);
+		return;
+	}
+
+	/* augmenting row reduction */
+	for (phase = 0; phase < 2; phase++) {
+		int k = 0;
+
+		saved_free_count = free_count;
+		free_count = 0;
+		while (k < saved_free_count) {
+			int u1, u2;
+			int j1 = 0, j2, i0;
+
+			i = free_row[k++];
+			u1 = COST(j1, i) - v[j1];
+			j2 = -1;
+			u2 = INT_MAX;
+			for (j = 1; j < column_count; j++) {
+				int c = COST(j, i) - v[j];
+				if (u2 > c) {
+					if (u1 < c) {
+						u2 = c;
+						j2 = j;
+					} else {
+						u2 = u1;
+						u1 = c;
+						j2 = j1;
+						j1 = j;
+					}
+				}
+			}
+			if (j2 < 0) {
+				j2 = j1;
+				u2 = u1;
+			}
+
+			i0 = column2row[j1];
+			if (u1 < u2)
+				v[j1] -= u2 - u1;
+			else if (i0 >= 0) {
+				j1 = j2;
+				i0 = column2row[j1];
+			}
+
+			if (i0 >= 0) {
+				if (u1 < u2)
+					free_row[--k] = i0;
+				else
+					free_row[free_count++] = i0;
+			}
+			row2column[i] = j1;
+			column2row[j1] = i;
+		}
+	}
+
+	/* augmentation */
+	saved_free_count = free_count;
+	ALLOC_ARRAY(d, column_count);
+	ALLOC_ARRAY(pred, column_count);
+	ALLOC_ARRAY(col, column_count);
+	for (free_count = 0; free_count < saved_free_count; free_count++) {
+		int i1 = free_row[free_count], low = 0, up = 0, last, k;
+		int min, c, u1;
+
+		for (j = 0; j < column_count; j++) {
+			d[j] = COST(j, i1) - v[j];
+			pred[j] = i1;
+			col[j] = j;
+		}
+
+		j = -1;
+		do {
+			last = low;
+			min = d[col[up++]];
+			for (k = up; k < column_count; k++) {
+				j = col[k];
+				c = d[j];
+				if (c <= min) {
+					if (c < min) {
+						up = low;
+						min = c;
+					}
+					col[k] = col[up];
+					col[up++] = j;
+				}
+			}
+			for (k = low; k < up; k++)
+				if (column2row[col[k]] == -1)
+					goto update;
+
+			/* scan a row */
+			do {
+				int j1 = col[low++];
+
+				i = column2row[j1];
+				u1 = COST(j1, i) - v[j1] - min;
+				for (k = up; k < column_count; k++) {
+					j = col[k];
+					c = COST(j, i) - v[j] - u1;
+					if (c < d[j]) {
+						d[j] = c;
+						pred[j] = i;
+						if (c == min) {
+							if (column2row[j] == -1)
+								goto update;
+							col[k] = col[up];
+							col[up++] = j;
+						}
+					}
+				}
+			} while (low != up);
+		} while (low == up);
+
+update:
+		/* updating of the column pieces */
+		for (k = 0; k < last; k++) {
+			int j1 = col[k];
+			v[j1] += d[j1] - min;
+		}
+
+		/* augmentation */
+		do {
+			if (j < 0)
+				BUG("negative j: %d", j);
+			i = pred[j];
+			column2row[j] = i;
+			SWAP(j, row2column[i]);
+		} while (i1 != i);
+	}
+
+	free(col);
+	free(pred);
+	free(d);
+	free(v);
+	free(free_row);
+}
diff --git a/linear-assignment.h b/linear-assignment.h
new file mode 100644
index 000000000..1dfea7662
--- /dev/null
+++ b/linear-assignment.h
@@ -0,0 +1,22 @@
+#ifndef LINEAR_ASSIGNMENT_H
+#define LINEAR_ASSIGNMENT_H
+
+/*
+ * Compute an assignment of columns -> rows (and vice versa) such that every
+ * column is assigned to at most one row (and vice versa) minimizing the
+ * overall cost.
+ *
+ * The parameter `cost` is the cost matrix: the cost to assign column j to row
+ * i is `cost[j + column_count * i].
+ *
+ * The arrays column2row and row2column will be populated with the respective
+ * assignments (-1 for unassigned, which can happen only if column_count !=
+ * row_count).
+ */
+void compute_assignment(int column_count, int row_count, int *cost,
+			int *column2row, int *row2column);
+
+/* The maximal cost in the cost matrix (to prevent integer overflows). */
+#define COST_MAX (1<<16)
+
+#endif
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 02/21] Introduce `range-diff` to compare iterations of a topic branch
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 01/21] linear-assignment: a function to solve least-cost assignment problems Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 03/21] range-diff: first rudimentary implementation Johannes Schindelin via GitGitGadget
                             ` (19 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

This command does not do a whole lot so far, apart from showing a usage
that is oddly similar to that of `git tbdiff`. And for a good reason:
the next commits will turn `range-branch` into a full-blown replacement
for `tbdiff`.

At this point, we ignore tbdiff's color options, as they will all be
implemented later using diff_options.

Since f318d739159 (generate-cmds.sh: export all commands to
command-list.h, 2018-05-10), every new command *requires* a man page to
build right away, so let's also add a blank man page, too.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitignore                       |  1 +
 Documentation/git-range-diff.txt | 10 ++++++++++
 Makefile                         |  1 +
 builtin.h                        |  1 +
 builtin/range-diff.c             | 25 +++++++++++++++++++++++++
 command-list.txt                 |  1 +
 git.c                            |  1 +
 7 files changed, 40 insertions(+)
 create mode 100644 Documentation/git-range-diff.txt
 create mode 100644 builtin/range-diff.c

diff --git a/.gitignore b/.gitignore
index 3284a1e9b..cc0ad74b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -113,6 +113,7 @@
 /git-pull
 /git-push
 /git-quiltimport
+/git-range-diff
 /git-read-tree
 /git-rebase
 /git-rebase--am
diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
new file mode 100644
index 000000000..49f717db8
--- /dev/null
+++ b/Documentation/git-range-diff.txt
@@ -0,0 +1,10 @@
+git-range-diff(1)
+=================
+
+NAME
+----
+git-range-diff - Compare two commit ranges (e.g. two versions of a branch)
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Makefile b/Makefile
index 1af719b44..7ff7eba42 100644
--- a/Makefile
+++ b/Makefile
@@ -1063,6 +1063,7 @@ BUILTIN_OBJS += builtin/prune-packed.o
 BUILTIN_OBJS += builtin/prune.o
 BUILTIN_OBJS += builtin/pull.o
 BUILTIN_OBJS += builtin/push.o
+BUILTIN_OBJS += builtin/range-diff.o
 BUILTIN_OBJS += builtin/read-tree.o
 BUILTIN_OBJS += builtin/rebase--helper.o
 BUILTIN_OBJS += builtin/receive-pack.o
diff --git a/builtin.h b/builtin.h
index 0362f1ce2..99206df4b 100644
--- a/builtin.h
+++ b/builtin.h
@@ -201,6 +201,7 @@ extern int cmd_prune(int argc, const char **argv, const char *prefix);
 extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
 extern int cmd_pull(int argc, const char **argv, const char *prefix);
 extern int cmd_push(int argc, const char **argv, const char *prefix);
+extern int cmd_range_diff(int argc, const char **argv, const char *prefix);
 extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix);
 extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);
diff --git a/builtin/range-diff.c b/builtin/range-diff.c
new file mode 100644
index 000000000..36788ea4f
--- /dev/null
+++ b/builtin/range-diff.c
@@ -0,0 +1,25 @@
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+
+static const char * const builtin_range_diff_usage[] = {
+N_("git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
+N_("git range-diff [<options>] <old-tip>...<new-tip>"),
+N_("git range-diff [<options>] <base> <old-tip> <new-tip>"),
+NULL
+};
+
+int cmd_range_diff(int argc, const char **argv, const char *prefix)
+{
+	int creation_factor = 60;
+	struct option options[] = {
+		OPT_INTEGER(0, "creation-factor", &creation_factor,
+			    N_("Percentage by which creation is weighted")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, NULL, options,
+			     builtin_range_diff_usage, 0);
+
+	return 0;
+}
diff --git a/command-list.txt b/command-list.txt
index e1c26c1bb..a9dda3b8a 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -139,6 +139,7 @@ git-prune-packed                        plumbingmanipulators
 git-pull                                mainporcelain           remote
 git-push                                mainporcelain           remote
 git-quiltimport                         foreignscminterface
+git-range-diff                          mainporcelain
 git-read-tree                           plumbingmanipulators
 git-rebase                              mainporcelain           history
 git-receive-pack                        synchelpers
diff --git a/git.c b/git.c
index fc7d15d54..5b48cac3a 100644
--- a/git.c
+++ b/git.c
@@ -520,6 +520,7 @@ static struct cmd_struct commands[] = {
 	{ "prune-packed", cmd_prune_packed, RUN_SETUP },
 	{ "pull", cmd_pull, RUN_SETUP | NEED_WORK_TREE },
 	{ "push", cmd_push, RUN_SETUP },
+	{ "range-diff", cmd_range_diff, RUN_SETUP | USE_PAGER },
 	{ "read-tree", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX},
 	{ "rebase--helper", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE },
 	{ "receive-pack", cmd_receive_pack },
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 03/21] range-diff: first rudimentary implementation
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 01/21] linear-assignment: a function to solve least-cost assignment problems Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 02/21] Introduce `range-diff` to compare iterations of a topic branch Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2019-03-05  6:29             ` Junio C Hamano
  2018-08-13 11:33           ` [PATCH v6 04/21] range-diff: improve the order of the shown commits Johannes Schindelin via GitGitGadget
                             ` (18 subsequent siblings)
  21 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

At this stage, `git range-diff` can determine corresponding commits
of two related commit ranges. This makes use of the recently introduced
implementation of the linear assignment algorithm.

The core of this patch is a straight port of the ideas of tbdiff, the
apparently dormant project at https://github.com/trast/tbdiff.

The output does not at all match `tbdiff`'s output yet, as this patch
really concentrates on getting the patch matching part right.

Note: due to differences in the diff algorithm (`tbdiff` uses the Python
module `difflib`, Git uses its xdiff fork), the cost matrix calculated
by `range-diff` is different (but very similar) to the one calculated
by `tbdiff`. Therefore, it is possible that they find different matching
commits in corner cases (e.g. when a patch was split into two patches of
roughly equal length).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Makefile             |   1 +
 builtin/range-diff.c |  45 ++++++-
 range-diff.c         | 311 +++++++++++++++++++++++++++++++++++++++++++
 range-diff.h         |   7 +
 4 files changed, 363 insertions(+), 1 deletion(-)
 create mode 100644 range-diff.c
 create mode 100644 range-diff.h

diff --git a/Makefile b/Makefile
index 7ff7eba42..72f16882e 100644
--- a/Makefile
+++ b/Makefile
@@ -925,6 +925,7 @@ LIB_OBJS += progress.o
 LIB_OBJS += prompt.o
 LIB_OBJS += protocol.o
 LIB_OBJS += quote.o
+LIB_OBJS += range-diff.o
 LIB_OBJS += reachable.o
 LIB_OBJS += read-cache.o
 LIB_OBJS += reflog-walk.o
diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 36788ea4f..94c1f362c 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "builtin.h"
 #include "parse-options.h"
+#include "range-diff.h"
 
 static const char * const builtin_range_diff_usage[] = {
 N_("git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
@@ -17,9 +18,51 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 			    N_("Percentage by which creation is weighted")),
 		OPT_END()
 	};
+	int res = 0;
+	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
 
 	argc = parse_options(argc, argv, NULL, options,
 			     builtin_range_diff_usage, 0);
 
-	return 0;
+	if (argc == 2) {
+		if (!strstr(argv[0], ".."))
+			die(_("no .. in range: '%s'"), argv[0]);
+		strbuf_addstr(&range1, argv[0]);
+
+		if (!strstr(argv[1], ".."))
+			die(_("no .. in range: '%s'"), argv[1]);
+		strbuf_addstr(&range2, argv[1]);
+	} else if (argc == 3) {
+		strbuf_addf(&range1, "%s..%s", argv[0], argv[1]);
+		strbuf_addf(&range2, "%s..%s", argv[0], argv[2]);
+	} else if (argc == 1) {
+		const char *b = strstr(argv[0], "..."), *a = argv[0];
+		int a_len;
+
+		if (!b) {
+			error(_("single arg format must be symmetric range"));
+			usage_with_options(builtin_range_diff_usage, options);
+		}
+
+		a_len = (int)(b - a);
+		if (!a_len) {
+			a = "HEAD";
+			a_len = strlen(a);
+		}
+		b += 3;
+		if (!*b)
+			b = "HEAD";
+		strbuf_addf(&range1, "%s..%.*s", b, a_len, a);
+		strbuf_addf(&range2, "%.*s..%s", a_len, a, b);
+	} else {
+		error(_("need two commit ranges"));
+		usage_with_options(builtin_range_diff_usage, options);
+	}
+
+	res = show_range_diff(range1.buf, range2.buf, creation_factor);
+
+	strbuf_release(&range1);
+	strbuf_release(&range2);
+
+	return res;
 }
diff --git a/range-diff.c b/range-diff.c
new file mode 100644
index 000000000..15d418afa
--- /dev/null
+++ b/range-diff.c
@@ -0,0 +1,311 @@
+#include "cache.h"
+#include "range-diff.h"
+#include "string-list.h"
+#include "run-command.h"
+#include "argv-array.h"
+#include "hashmap.h"
+#include "xdiff-interface.h"
+#include "linear-assignment.h"
+
+struct patch_util {
+	/* For the search for an exact match */
+	struct hashmap_entry e;
+	const char *diff, *patch;
+
+	int i;
+	int diffsize;
+	size_t diff_offset;
+	/* the index of the matching item in the other branch, or -1 */
+	int matching;
+	struct object_id oid;
+};
+
+/*
+ * Reads the patches into a string list, with the `util` field being populated
+ * as struct object_id (will need to be free()d).
+ */
+static int read_patches(const char *range, struct string_list *list)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+	FILE *in;
+	struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;
+	struct patch_util *util = NULL;
+	int in_header = 1;
+
+	argv_array_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
+			"--reverse", "--date-order", "--decorate=no",
+			"--no-abbrev-commit", range,
+			NULL);
+	cp.out = -1;
+	cp.no_stdin = 1;
+	cp.git_cmd = 1;
+
+	if (start_command(&cp))
+		return error_errno(_("could not start `log`"));
+	in = fdopen(cp.out, "r");
+	if (!in) {
+		error_errno(_("could not read `log` output"));
+		finish_command(&cp);
+		return -1;
+	}
+
+	while (strbuf_getline(&line, in) != EOF) {
+		const char *p;
+
+		if (skip_prefix(line.buf, "commit ", &p)) {
+			if (util) {
+				string_list_append(list, buf.buf)->util = util;
+				strbuf_reset(&buf);
+			}
+			util = xcalloc(sizeof(*util), 1);
+			if (get_oid(p, &util->oid)) {
+				error(_("could not parse commit '%s'"), p);
+				free(util);
+				string_list_clear(list, 1);
+				strbuf_release(&buf);
+				strbuf_release(&line);
+				fclose(in);
+				finish_command(&cp);
+				return -1;
+			}
+			util->matching = -1;
+			in_header = 1;
+			continue;
+		}
+
+		if (starts_with(line.buf, "diff --git")) {
+			in_header = 0;
+			strbuf_addch(&buf, '\n');
+			if (!util->diff_offset)
+				util->diff_offset = buf.len;
+			strbuf_addbuf(&buf, &line);
+		} else if (in_header) {
+			if (starts_with(line.buf, "Author: ")) {
+				strbuf_addbuf(&buf, &line);
+				strbuf_addstr(&buf, "\n\n");
+			} else if (starts_with(line.buf, "    ")) {
+				strbuf_addbuf(&buf, &line);
+				strbuf_addch(&buf, '\n');
+			}
+			continue;
+		} else if (starts_with(line.buf, "@@ "))
+			strbuf_addstr(&buf, "@@");
+		else if (!line.buf[0] || starts_with(line.buf, "index "))
+			/*
+			 * A completely blank (not ' \n', which is context)
+			 * line is not valid in a diff.  We skip it
+			 * silently, because this neatly handles the blank
+			 * separator line between commits in git-log
+			 * output.
+			 *
+			 * We also want to ignore the diff's `index` lines
+			 * because they contain exact blob hashes in which
+			 * we are not interested.
+			 */
+			continue;
+		else
+			strbuf_addbuf(&buf, &line);
+
+		strbuf_addch(&buf, '\n');
+		util->diffsize++;
+	}
+	fclose(in);
+	strbuf_release(&line);
+
+	if (util)
+		string_list_append(list, buf.buf)->util = util;
+	strbuf_release(&buf);
+
+	if (finish_command(&cp))
+		return -1;
+
+	return 0;
+}
+
+static int patch_util_cmp(const void *dummy, const struct patch_util *a,
+		     const struct patch_util *b, const char *keydata)
+{
+	return strcmp(a->diff, keydata ? keydata : b->diff);
+}
+
+static void find_exact_matches(struct string_list *a, struct string_list *b)
+{
+	struct hashmap map;
+	int i;
+
+	hashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);
+
+	/* First, add the patches of a to a hash map */
+	for (i = 0; i < a->nr; i++) {
+		struct patch_util *util = a->items[i].util;
+
+		util->i = i;
+		util->patch = a->items[i].string;
+		util->diff = util->patch + util->diff_offset;
+		hashmap_entry_init(util, strhash(util->diff));
+		hashmap_add(&map, util);
+	}
+
+	/* Now try to find exact matches in b */
+	for (i = 0; i < b->nr; i++) {
+		struct patch_util *util = b->items[i].util, *other;
+
+		util->i = i;
+		util->patch = b->items[i].string;
+		util->diff = util->patch + util->diff_offset;
+		hashmap_entry_init(util, strhash(util->diff));
+		other = hashmap_remove(&map, util, NULL);
+		if (other) {
+			if (other->matching >= 0)
+				BUG("already assigned!");
+
+			other->matching = i;
+			util->matching = other->i;
+		}
+	}
+
+	hashmap_free(&map, 0);
+}
+
+static void diffsize_consume(void *data, char *line, unsigned long len)
+{
+	(*(int *)data)++;
+}
+
+static int diffsize(const char *a, const char *b)
+{
+	xpparam_t pp = { 0 };
+	xdemitconf_t cfg = { 0 };
+	mmfile_t mf1, mf2;
+	int count = 0;
+
+	mf1.ptr = (char *)a;
+	mf1.size = strlen(a);
+	mf2.ptr = (char *)b;
+	mf2.size = strlen(b);
+
+	cfg.ctxlen = 3;
+	if (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))
+		return count;
+
+	error(_("failed to generate diff"));
+	return COST_MAX;
+}
+
+static void get_correspondences(struct string_list *a, struct string_list *b,
+				int creation_factor)
+{
+	int n = a->nr + b->nr;
+	int *cost, c, *a2b, *b2a;
+	int i, j;
+
+	ALLOC_ARRAY(cost, st_mult(n, n));
+	ALLOC_ARRAY(a2b, n);
+	ALLOC_ARRAY(b2a, n);
+
+	for (i = 0; i < a->nr; i++) {
+		struct patch_util *a_util = a->items[i].util;
+
+		for (j = 0; j < b->nr; j++) {
+			struct patch_util *b_util = b->items[j].util;
+
+			if (a_util->matching == j)
+				c = 0;
+			else if (a_util->matching < 0 && b_util->matching < 0)
+				c = diffsize(a_util->diff, b_util->diff);
+			else
+				c = COST_MAX;
+			cost[i + n * j] = c;
+		}
+
+		c = a_util->matching < 0 ?
+			a_util->diffsize * creation_factor / 100 : COST_MAX;
+		for (j = b->nr; j < n; j++)
+			cost[i + n * j] = c;
+	}
+
+	for (j = 0; j < b->nr; j++) {
+		struct patch_util *util = b->items[j].util;
+
+		c = util->matching < 0 ?
+			util->diffsize * creation_factor / 100 : COST_MAX;
+		for (i = a->nr; i < n; i++)
+			cost[i + n * j] = c;
+	}
+
+	for (i = a->nr; i < n; i++)
+		for (j = b->nr; j < n; j++)
+			cost[i + n * j] = 0;
+
+	compute_assignment(n, n, cost, a2b, b2a);
+
+	for (i = 0; i < a->nr; i++)
+		if (a2b[i] >= 0 && a2b[i] < b->nr) {
+			struct patch_util *a_util = a->items[i].util;
+			struct patch_util *b_util = b->items[a2b[i]].util;
+
+			a_util->matching = a2b[i];
+			b_util->matching = i;
+		}
+
+	free(cost);
+	free(a2b);
+	free(b2a);
+}
+
+static const char *short_oid(struct patch_util *util)
+{
+	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
+}
+
+static void output(struct string_list *a, struct string_list *b)
+{
+	int i;
+
+	for (i = 0; i < b->nr; i++) {
+		struct patch_util *util = b->items[i].util, *prev;
+
+		if (util->matching < 0)
+			printf("-: -------- > %d: %s\n",
+					i + 1, short_oid(util));
+		else {
+			prev = a->items[util->matching].util;
+			printf("%d: %s ! %d: %s\n",
+			       util->matching + 1, short_oid(prev),
+			       i + 1, short_oid(util));
+		}
+	}
+
+	for (i = 0; i < a->nr; i++) {
+		struct patch_util *util = a->items[i].util;
+
+		if (util->matching < 0)
+			printf("%d: %s < -: --------\n",
+			       i + 1, short_oid(util));
+	}
+}
+
+int show_range_diff(const char *range1, const char *range2,
+		    int creation_factor)
+{
+	int res = 0;
+
+	struct string_list branch1 = STRING_LIST_INIT_DUP;
+	struct string_list branch2 = STRING_LIST_INIT_DUP;
+
+	if (read_patches(range1, &branch1))
+		res = error(_("could not parse log for '%s'"), range1);
+	if (!res && read_patches(range2, &branch2))
+		res = error(_("could not parse log for '%s'"), range2);
+
+	if (!res) {
+		find_exact_matches(&branch1, &branch2);
+		get_correspondences(&branch1, &branch2, creation_factor);
+		output(&branch1, &branch2);
+	}
+
+	string_list_clear(&branch1, 1);
+	string_list_clear(&branch2, 1);
+
+	return res;
+}
diff --git a/range-diff.h b/range-diff.h
new file mode 100644
index 000000000..7b6eef303
--- /dev/null
+++ b/range-diff.h
@@ -0,0 +1,7 @@
+#ifndef RANGE_DIFF_H
+#define RANGE_DIFF_H
+
+int show_range_diff(const char *range1, const char *range2,
+		    int creation_factor);
+
+#endif
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 04/21] range-diff: improve the order of the shown commits
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                             ` (2 preceding siblings ...)
  2018-08-13 11:33           ` [PATCH v6 03/21] range-diff: first rudimentary implementation Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 05/21] range-diff: also show the diff between patches Johannes Schindelin via GitGitGadget
                             ` (17 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

This patch lets `git range-diff` use the same order as tbdiff.

The idea is simple: for left-to-right readers, it is natural to assume
that the `git range-diff` is performed between an older vs a newer
version of the branch. As such, the user is probably more interested in
the question "where did this come from?" rather than "where did that one
go?".

To that end, we list the commits in the order of the second commit range
("the newer version"), inserting the unmatched commits of the first
commit range as soon as all their predecessors have been shown.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 59 +++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 40 insertions(+), 19 deletions(-)

diff --git a/range-diff.c b/range-diff.c
index 15d418afa..2d94200d3 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -12,7 +12,7 @@ struct patch_util {
 	struct hashmap_entry e;
 	const char *diff, *patch;
 
-	int i;
+	int i, shown;
 	int diffsize;
 	size_t diff_offset;
 	/* the index of the matching item in the other branch, or -1 */
@@ -260,28 +260,49 @@ static const char *short_oid(struct patch_util *util)
 
 static void output(struct string_list *a, struct string_list *b)
 {
-	int i;
-
-	for (i = 0; i < b->nr; i++) {
-		struct patch_util *util = b->items[i].util, *prev;
+	int i = 0, j = 0;
+
+	/*
+	 * We assume the user is really more interested in the second argument
+	 * ("newer" version). To that end, we print the output in the order of
+	 * the RHS (the `b` parameter). To put the LHS (the `a` parameter)
+	 * commits that are no longer in the RHS into a good place, we place
+	 * them once we have shown all of their predecessors in the LHS.
+	 */
+
+	while (i < a->nr || j < b->nr) {
+		struct patch_util *a_util, *b_util;
+		a_util = i < a->nr ? a->items[i].util : NULL;
+		b_util = j < b->nr ? b->items[j].util : NULL;
+
+		/* Skip all the already-shown commits from the LHS. */
+		while (i < a->nr && a_util->shown)
+			a_util = ++i < a->nr ? a->items[i].util : NULL;
+
+		/* Show unmatched LHS commit whose predecessors were shown. */
+		if (i < a->nr && a_util->matching < 0) {
+			printf("%d: %s < -: --------\n",
+			       i + 1, short_oid(a_util));
+			i++;
+			continue;
+		}
 
-		if (util->matching < 0)
+		/* Show unmatched RHS commits. */
+		while (j < b->nr && b_util->matching < 0) {
 			printf("-: -------- > %d: %s\n",
-					i + 1, short_oid(util));
-		else {
-			prev = a->items[util->matching].util;
-			printf("%d: %s ! %d: %s\n",
-			       util->matching + 1, short_oid(prev),
-			       i + 1, short_oid(util));
+			       j + 1, short_oid(b_util));
+			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
-	}
-
-	for (i = 0; i < a->nr; i++) {
-		struct patch_util *util = a->items[i].util;
 
-		if (util->matching < 0)
-			printf("%d: %s < -: --------\n",
-			       i + 1, short_oid(util));
+		/* Show matching LHS/RHS pair. */
+		if (j < b->nr) {
+			a_util = a->items[b_util->matching].util;
+			printf("%d: %s ! %d: %s\n",
+			       b_util->matching + 1, short_oid(a_util),
+			       j + 1, short_oid(b_util));
+			a_util->shown = 1;
+			j++;
+		}
 	}
 }
 
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 05/21] range-diff: also show the diff between patches
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                             ` (3 preceding siblings ...)
  2018-08-13 11:33           ` [PATCH v6 04/21] range-diff: improve the order of the shown commits Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 06/21] range-diff: right-trim commit messages Johannes Schindelin via GitGitGadget
                             ` (16 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

Just like tbdiff, we now show the diff between matching patches. This is
a "diff of two diffs", so it can be a bit daunting to read for the
beginner.

An alternative would be to display an interdiff, i.e. the hypothetical
diff which is the result of first reverting the old diff and then
applying the new diff.

Especially when rebasing frequently, an interdiff is often not feasible,
though: if the old diff cannot be applied in reverse (due to a moving
upstream), an interdiff can simply not be inferred.

This commit brings `range-diff` closer to feature parity with regard
to tbdiff.

To make `git range-diff` respect e.g. color.diff.* settings, we have
to adjust git_branch_config() accordingly.

Note: while we now parse diff options such as --color, the effect is not
yet the same as in tbdiff, where also the commit pairs would be colored.
This is left for a later commit.

Note also: while tbdiff accepts the `--no-patches` option to suppress
these diffs between patches, we prefer the `-s` (or `--no-patch`) option
that is automatically supported via our use of diff_opt_parse().

And finally note: to support diff options, we have to call
`parse_options()` such that it keeps unknown options, and then loop over
those and let `diff_opt_parse()` handle them. After that loop, we have
to call `parse_options()` again, to make sure that no unknown options
are left.

Helped-by: Thomas Gummerer <t.gummerer@gmail.com>
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/range-diff.c | 31 +++++++++++++++++++++++++++++--
 range-diff.c         | 34 +++++++++++++++++++++++++++++++---
 range-diff.h         |  4 +++-
 3 files changed, 63 insertions(+), 6 deletions(-)

diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 94c1f362c..3b06ed944 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -2,6 +2,7 @@
 #include "builtin.h"
 #include "parse-options.h"
 #include "range-diff.h"
+#include "config.h"
 
 static const char * const builtin_range_diff_usage[] = {
 N_("git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
@@ -13,15 +14,40 @@ NULL
 int cmd_range_diff(int argc, const char **argv, const char *prefix)
 {
 	int creation_factor = 60;
+	struct diff_options diffopt = { NULL };
 	struct option options[] = {
 		OPT_INTEGER(0, "creation-factor", &creation_factor,
 			    N_("Percentage by which creation is weighted")),
 		OPT_END()
 	};
-	int res = 0;
+	int i, j, res = 0;
 	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
 
+	git_config(git_diff_ui_config, NULL);
+
+	diff_setup(&diffopt);
+	diffopt.output_format = DIFF_FORMAT_PATCH;
+
 	argc = parse_options(argc, argv, NULL, options,
+			     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN |
+			     PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
+
+	for (i = j = 1; i < argc && strcmp("--", argv[i]); ) {
+		int c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);
+
+		if (!c)
+			argv[j++] = argv[i++];
+		else
+			i += c;
+	}
+	while (i < argc)
+		argv[j++] = argv[i++];
+	argc = j;
+	diff_setup_done(&diffopt);
+
+	/* Make sure that there are no unparsed options */
+	argc = parse_options(argc, argv, NULL,
+			     options + ARRAY_SIZE(options) - 1, /* OPT_END */
 			     builtin_range_diff_usage, 0);
 
 	if (argc == 2) {
@@ -59,7 +85,8 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 		usage_with_options(builtin_range_diff_usage, options);
 	}
 
-	res = show_range_diff(range1.buf, range2.buf, creation_factor);
+	res = show_range_diff(range1.buf, range2.buf, creation_factor,
+			      &diffopt);
 
 	strbuf_release(&range1);
 	strbuf_release(&range2);
diff --git a/range-diff.c b/range-diff.c
index 2d94200d3..71883a4b7 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -6,6 +6,7 @@
 #include "hashmap.h"
 #include "xdiff-interface.h"
 #include "linear-assignment.h"
+#include "diffcore.h"
 
 struct patch_util {
 	/* For the search for an exact match */
@@ -258,7 +259,31 @@ static const char *short_oid(struct patch_util *util)
 	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
 }
 
-static void output(struct string_list *a, struct string_list *b)
+static struct diff_filespec *get_filespec(const char *name, const char *p)
+{
+	struct diff_filespec *spec = alloc_filespec(name);
+
+	fill_filespec(spec, &null_oid, 0, 0644);
+	spec->data = (char *)p;
+	spec->size = strlen(p);
+	spec->should_munmap = 0;
+	spec->is_stdin = 1;
+
+	return spec;
+}
+
+static void patch_diff(const char *a, const char *b,
+			      struct diff_options *diffopt)
+{
+	diff_queue(&diff_queued_diff,
+		   get_filespec("a", a), get_filespec("b", b));
+
+	diffcore_std(diffopt);
+	diff_flush(diffopt);
+}
+
+static void output(struct string_list *a, struct string_list *b,
+		   struct diff_options *diffopt)
 {
 	int i = 0, j = 0;
 
@@ -300,6 +325,9 @@ static void output(struct string_list *a, struct string_list *b)
 			printf("%d: %s ! %d: %s\n",
 			       b_util->matching + 1, short_oid(a_util),
 			       j + 1, short_oid(b_util));
+			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
+				patch_diff(a->items[b_util->matching].string,
+					   b->items[j].string, diffopt);
 			a_util->shown = 1;
 			j++;
 		}
@@ -307,7 +335,7 @@ static void output(struct string_list *a, struct string_list *b)
 }
 
 int show_range_diff(const char *range1, const char *range2,
-		    int creation_factor)
+		    int creation_factor, struct diff_options *diffopt)
 {
 	int res = 0;
 
@@ -322,7 +350,7 @@ int show_range_diff(const char *range1, const char *range2,
 	if (!res) {
 		find_exact_matches(&branch1, &branch2);
 		get_correspondences(&branch1, &branch2, creation_factor);
-		output(&branch1, &branch2);
+		output(&branch1, &branch2, diffopt);
 	}
 
 	string_list_clear(&branch1, 1);
diff --git a/range-diff.h b/range-diff.h
index 7b6eef303..2407d46a3 100644
--- a/range-diff.h
+++ b/range-diff.h
@@ -1,7 +1,9 @@
 #ifndef RANGE_DIFF_H
 #define RANGE_DIFF_H
 
+#include "diff.h"
+
 int show_range_diff(const char *range1, const char *range2,
-		    int creation_factor);
+		    int creation_factor, struct diff_options *diffopt);
 
 #endif
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 06/21] range-diff: right-trim commit messages
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                             ` (4 preceding siblings ...)
  2018-08-13 11:33           ` [PATCH v6 05/21] range-diff: also show the diff between patches Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 07/21] range-diff: indent the diffs just like tbdiff Johannes Schindelin via GitGitGadget
                             ` (15 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When comparing commit messages, we need to keep in mind that they are
indented by four spaces. That is, empty lines are no longer empty, but
have "trailing whitespace". When displaying them in color, that results
in those nagging red lines.

Let's just right-trim the lines in the commit message, it's not like
trailing white-space in the commit messages are important enough to care
about in `git range-diff`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/range-diff.c b/range-diff.c
index 71883a4b7..1ecee2c09 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -85,6 +85,7 @@ static int read_patches(const char *range, struct string_list *list)
 				strbuf_addbuf(&buf, &line);
 				strbuf_addstr(&buf, "\n\n");
 			} else if (starts_with(line.buf, "    ")) {
+				strbuf_rtrim(&line);
 				strbuf_addbuf(&buf, &line);
 				strbuf_addch(&buf, '\n');
 			}
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 07/21] range-diff: indent the diffs just like tbdiff
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                             ` (5 preceding siblings ...)
  2018-08-13 11:33           ` [PATCH v6 06/21] range-diff: right-trim commit messages Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 08/21] range-diff: suppress the diff headers Johannes Schindelin via GitGitGadget
                             ` (14 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

The main information in the `range-diff` view comes from the list of
matching and non-matching commits, the diffs are additional information.
Indenting them helps with the reading flow.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/range-diff.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 3b06ed944..f0598005a 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -11,6 +11,11 @@ N_("git range-diff [<options>] <base> <old-tip> <new-tip>"),
 NULL
 };
 
+static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)
+{
+	return data;
+}
+
 int cmd_range_diff(int argc, const char **argv, const char *prefix)
 {
 	int creation_factor = 60;
@@ -21,12 +26,16 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 	int i, j, res = 0;
+	struct strbuf four_spaces = STRBUF_INIT;
 	struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
 
 	git_config(git_diff_ui_config, NULL);
 
 	diff_setup(&diffopt);
 	diffopt.output_format = DIFF_FORMAT_PATCH;
+	diffopt.output_prefix = output_prefix_cb;
+	strbuf_addstr(&four_spaces, "    ");
+	diffopt.output_prefix_data = &four_spaces;
 
 	argc = parse_options(argc, argv, NULL, options,
 			     builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN |
@@ -90,6 +99,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 
 	strbuf_release(&range1);
 	strbuf_release(&range2);
+	strbuf_release(&four_spaces);
 
 	return res;
 }
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 08/21] range-diff: suppress the diff headers
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                             ` (6 preceding siblings ...)
  2018-08-13 11:33           ` [PATCH v6 07/21] range-diff: indent the diffs just like tbdiff Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 09/21] range-diff: adjust the output of the commit pairs Johannes Schindelin via GitGitGadget
                             ` (13 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When showing the diff between corresponding patches of the two branch
versions, we have to make up a fake filename to run the diff machinery.

That filename does not carry any meaningful information, hence tbdiff
suppresses it. So we should, too.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/range-diff.c | 1 +
 diff.c               | 5 ++++-
 diff.h               | 1 +
 3 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index f0598005a..76659d0b3 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -33,6 +33,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 
 	diff_setup(&diffopt);
 	diffopt.output_format = DIFF_FORMAT_PATCH;
+	diffopt.flags.suppress_diff_headers = 1;
 	diffopt.output_prefix = output_prefix_cb;
 	strbuf_addstr(&four_spaces, "    ");
 	diffopt.output_prefix_data = &four_spaces;
diff --git a/diff.c b/diff.c
index 04d044bbb..9c4bd9fa1 100644
--- a/diff.c
+++ b/diff.c
@@ -3395,13 +3395,16 @@ static void builtin_diff(const char *name_a,
 		memset(&xpp, 0, sizeof(xpp));
 		memset(&xecfg, 0, sizeof(xecfg));
 		memset(&ecbdata, 0, sizeof(ecbdata));
+		if (o->flags.suppress_diff_headers)
+			lbl[0] = NULL;
 		ecbdata.label_path = lbl;
 		ecbdata.color_diff = want_color(o->use_color);
 		ecbdata.ws_rule = whitespace_rule(name_b);
 		if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
 			check_blank_at_eof(&mf1, &mf2, &ecbdata);
 		ecbdata.opt = o;
-		ecbdata.header = header.len ? &header : NULL;
+		if (header.len && !o->flags.suppress_diff_headers)
+			ecbdata.header = &header;
 		xpp.flags = o->xdl_opts;
 		xpp.anchors = o->anchors;
 		xpp.anchors_nr = o->anchors_nr;
diff --git a/diff.h b/diff.h
index a14895bb8..d88ceb357 100644
--- a/diff.h
+++ b/diff.h
@@ -94,6 +94,7 @@ struct diff_flags {
 	unsigned funccontext:1;
 	unsigned default_follow_renames:1;
 	unsigned stat_with_summary:1;
+	unsigned suppress_diff_headers:1;
 };
 
 static inline void diff_flags_or(struct diff_flags *a,
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 09/21] range-diff: adjust the output of the commit pairs
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                             ` (7 preceding siblings ...)
  2018-08-13 11:33           ` [PATCH v6 08/21] range-diff: suppress the diff headers Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 10/21] range-diff: do not show "function names" in hunk headers Johannes Schindelin via GitGitGadget
                             ` (12 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

This not only uses "dashed stand-ins" for "pairs" where one side is
missing (i.e. unmatched commits that are present only in one of the two
commit ranges), but also adds onelines for the reader's pleasure.

This change brings `git range-diff` yet another step closer to
feature parity with tbdiff: it now shows the oneline, too, and indicates
with `=` when the commits have identical diffs.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 59 ++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 50 insertions(+), 9 deletions(-)

diff --git a/range-diff.c b/range-diff.c
index 1ecee2c09..23aa61af5 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -7,6 +7,8 @@
 #include "xdiff-interface.h"
 #include "linear-assignment.h"
 #include "diffcore.h"
+#include "commit.h"
+#include "pretty.h"
 
 struct patch_util {
 	/* For the search for an exact match */
@@ -255,9 +257,49 @@ static void get_correspondences(struct string_list *a, struct string_list *b,
 	free(b2a);
 }
 
-static const char *short_oid(struct patch_util *util)
+static void output_pair_header(struct strbuf *buf,
+			       struct strbuf *dashes,
+			       struct patch_util *a_util,
+			       struct patch_util *b_util)
 {
-	return find_unique_abbrev(&util->oid, DEFAULT_ABBREV);
+	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
+	struct commit *commit;
+
+	if (!dashes->len)
+		strbuf_addchars(dashes, '-',
+				strlen(find_unique_abbrev(oid,
+							  DEFAULT_ABBREV)));
+
+	strbuf_reset(buf);
+	if (!a_util)
+		strbuf_addf(buf, "-:  %s ", dashes->buf);
+	else
+		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
+			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
+
+	if (!a_util)
+		strbuf_addch(buf, '>');
+	else if (!b_util)
+		strbuf_addch(buf, '<');
+	else if (strcmp(a_util->patch, b_util->patch))
+		strbuf_addch(buf, '!');
+	else
+		strbuf_addch(buf, '=');
+
+	if (!b_util)
+		strbuf_addf(buf, " -:  %s", dashes->buf);
+	else
+		strbuf_addf(buf, " %d:  %s", b_util->i + 1,
+			    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));
+
+	commit = lookup_commit_reference(the_repository, oid);
+	if (commit) {
+		strbuf_addch(buf, ' ');
+		pp_commit_easy(CMIT_FMT_ONELINE, commit, buf);
+	}
+	strbuf_addch(buf, '\n');
+
+	fwrite(buf->buf, buf->len, 1, stdout);
 }
 
 static struct diff_filespec *get_filespec(const char *name, const char *p)
@@ -286,6 +328,7 @@ static void patch_diff(const char *a, const char *b,
 static void output(struct string_list *a, struct string_list *b,
 		   struct diff_options *diffopt)
 {
+	struct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;
 	int i = 0, j = 0;
 
 	/*
@@ -307,25 +350,21 @@ static void output(struct string_list *a, struct string_list *b,
 
 		/* Show unmatched LHS commit whose predecessors were shown. */
 		if (i < a->nr && a_util->matching < 0) {
-			printf("%d: %s < -: --------\n",
-			       i + 1, short_oid(a_util));
+			output_pair_header(&buf, &dashes, a_util, NULL);
 			i++;
 			continue;
 		}
 
 		/* Show unmatched RHS commits. */
 		while (j < b->nr && b_util->matching < 0) {
-			printf("-: -------- > %d: %s\n",
-			       j + 1, short_oid(b_util));
+			output_pair_header(&buf, &dashes, NULL, b_util);
 			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
 
 		/* Show matching LHS/RHS pair. */
 		if (j < b->nr) {
 			a_util = a->items[b_util->matching].util;
-			printf("%d: %s ! %d: %s\n",
-			       b_util->matching + 1, short_oid(a_util),
-			       j + 1, short_oid(b_util));
+			output_pair_header(&buf, &dashes, a_util, b_util);
 			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
 				patch_diff(a->items[b_util->matching].string,
 					   b->items[j].string, diffopt);
@@ -333,6 +372,8 @@ static void output(struct string_list *a, struct string_list *b,
 			j++;
 		}
 	}
+	strbuf_release(&buf);
+	strbuf_release(&dashes);
 }
 
 int show_range_diff(const char *range1, const char *range2,
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 10/21] range-diff: do not show "function names" in hunk headers
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                             ` (8 preceding siblings ...)
  2018-08-13 11:33           ` [PATCH v6 09/21] range-diff: adjust the output of the commit pairs Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 11/21] range-diff: add tests Thomas Rast via GitGitGadget
                             ` (11 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

We are comparing complete, formatted commit messages with patches. There
are no function names here, so stop looking for them.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/range-diff.c b/range-diff.c
index 23aa61af5..6d75563f4 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -9,6 +9,7 @@
 #include "diffcore.h"
 #include "commit.h"
 #include "pretty.h"
+#include "userdiff.h"
 
 struct patch_util {
 	/* For the search for an exact match */
@@ -302,6 +303,10 @@ static void output_pair_header(struct strbuf *buf,
 	fwrite(buf->buf, buf->len, 1, stdout);
 }
 
+static struct userdiff_driver no_func_name = {
+	.funcname = { "$^", 0 }
+};
+
 static struct diff_filespec *get_filespec(const char *name, const char *p)
 {
 	struct diff_filespec *spec = alloc_filespec(name);
@@ -311,6 +316,7 @@ static struct diff_filespec *get_filespec(const char *name, const char *p)
 	spec->size = strlen(p);
 	spec->should_munmap = 0;
 	spec->is_stdin = 1;
+	spec->driver = &no_func_name;
 
 	return spec;
 }
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 11/21] range-diff: add tests
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                             ` (9 preceding siblings ...)
  2018-08-13 11:33           ` [PATCH v6 10/21] range-diff: do not show "function names" in hunk headers Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Thomas Rast via GitGitGadget
  2018-08-13 18:35             ` Thomas Gummerer
  2018-08-13 11:33           ` [PATCH v6 12/21] range-diff: use color for the commit pairs Johannes Schindelin via GitGitGadget
                             ` (10 subsequent siblings)
  21 siblings, 1 reply; 387+ messages in thread
From: Thomas Rast via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Thomas Rast

From: Thomas Rast <tr@thomasrast.ch>

These are essentially lifted from https://github.com/trast/tbdiff, with
light touch-ups to account for the command now being named `git
range-diff`.

Apart from renaming `tbdiff` to `range-diff`, only one test case needed
to be adjusted: 11 - 'changed message'.

The underlying reason it had to be adjusted is that diff generation is
sometimes ambiguous. In this case, a comment line and an empty line are
added, but it is ambiguous whether they were added after the existing
empty line, or whether an empty line and the comment line are added
*before* the existing empty line. And apparently xdiff picks a different
option here than Python's difflib.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/.gitattributes       |   1 +
 t/t3206-range-diff.sh  | 145 ++++++++++
 t/t3206/history.export | 604 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 750 insertions(+)
 create mode 100755 t/t3206-range-diff.sh
 create mode 100644 t/t3206/history.export

diff --git a/t/.gitattributes b/t/.gitattributes
index 3bd959ae5..b17bf71b8 100644
--- a/t/.gitattributes
+++ b/t/.gitattributes
@@ -1,6 +1,7 @@
 t[0-9][0-9][0-9][0-9]/* -whitespace
 /diff-lib/* eol=lf
 /t0110/url-* binary
+/t3206/* eol=lf
 /t3900/*.txt eol=lf
 /t3901/*.txt eol=lf
 /t4034/*/* eol=lf
diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
new file mode 100755
index 000000000..2237c7f4a
--- /dev/null
+++ b/t/t3206-range-diff.sh
@@ -0,0 +1,145 @@
+#!/bin/sh
+
+test_description='range-diff tests'
+
+. ./test-lib.sh
+
+# Note that because of the range-diff's heuristics, test_commit does more
+# harm than good.  We need some real history.
+
+test_expect_success 'setup' '
+	git fast-import < "$TEST_DIRECTORY"/t3206/history.export
+'
+
+test_expect_success 'simple A..B A..C (unmodified)' '
+	git range-diff --no-color master..topic master..unmodified \
+		>actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  35b9b25 s/5/A/
+	2:  fccce22 = 2:  de345ab s/4/A/
+	3:  147e64e = 3:  9af6654 s/11/B/
+	4:  a63e992 = 4:  2901f77 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'simple B...C (unmodified)' '
+	git range-diff --no-color topic...unmodified >actual &&
+	# same "expected" as above
+	test_cmp expected actual
+'
+
+test_expect_success 'simple A B C (unmodified)' '
+	git range-diff --no-color master topic unmodified >actual &&
+	# same "expected" as above
+	test_cmp expected actual
+'
+
+test_expect_success 'trivial reordering' '
+	git range-diff --no-color master topic reordered >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  aca177a s/5/A/
+	3:  147e64e = 2:  14ad629 s/11/B/
+	4:  a63e992 = 3:  ee58208 s/12/B/
+	2:  fccce22 = 4:  307b27a s/4/A/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'removed a commit' '
+	git range-diff --no-color master topic removed >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  7657159 s/5/A/
+	2:  fccce22 < -:  ------- s/4/A/
+	3:  147e64e = 2:  43d84d3 s/11/B/
+	4:  a63e992 = 3:  a740396 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'added a commit' '
+	git range-diff --no-color master topic added >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  2716022 s/5/A/
+	2:  fccce22 = 2:  b62accd s/4/A/
+	-:  ------- > 3:  df46cfa s/6/A/
+	3:  147e64e = 4:  3e64548 s/11/B/
+	4:  a63e992 = 5:  12b4063 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'new base, A B C' '
+	git range-diff --no-color master topic rebased >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  cc9c443 s/5/A/
+	2:  fccce22 = 2:  c5d9641 s/4/A/
+	3:  147e64e = 3:  28cc2b6 s/11/B/
+	4:  a63e992 = 4:  5628ab7 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'new base, B...C' '
+	# this syntax includes the commits from master!
+	git range-diff --no-color topic...rebased >actual &&
+	cat >expected <<-EOF &&
+	-:  ------- > 1:  a31b12e unrelated
+	1:  4de457d = 2:  cc9c443 s/5/A/
+	2:  fccce22 = 3:  c5d9641 s/4/A/
+	3:  147e64e = 4:  28cc2b6 s/11/B/
+	4:  a63e992 = 5:  5628ab7 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'changed commit' '
+	git range-diff --no-color topic...changed >actual &&
+	cat >expected <<-EOF &&
+	1:  4de457d = 1:  a4b3333 s/5/A/
+	2:  fccce22 = 2:  f51d370 s/4/A/
+	3:  147e64e ! 3:  0559556 s/11/B/
+	    @@ -10,7 +10,7 @@
+	      9
+	      10
+	     -11
+	    -+B
+	    ++BB
+	      12
+	      13
+	      14
+	4:  a63e992 ! 4:  d966c5c s/12/B/
+	    @@ -8,7 +8,7 @@
+	     @@
+	      9
+	      10
+	    - B
+	    + BB
+	     -12
+	     +B
+	      13
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'changed message' '
+	git range-diff --no-color topic...changed-message >actual &&
+	sed s/Z/\ /g >expected <<-EOF &&
+	1:  4de457d = 1:  f686024 s/5/A/
+	2:  fccce22 ! 2:  4ab067d s/4/A/
+	    @@ -2,6 +2,8 @@
+	    Z
+	    Z    s/4/A/
+	    Z
+	    +    Also a silly comment here!
+	    +
+	    Zdiff --git a/file b/file
+	    Z--- a/file
+	    Z+++ b/file
+	3:  147e64e = 3:  b9cb956 s/11/B/
+	4:  a63e992 = 4:  8add5f1 s/12/B/
+	EOF
+	test_cmp expected actual
+'
+
+test_done
diff --git a/t/t3206/history.export b/t/t3206/history.export
new file mode 100644
index 000000000..b8ffff094
--- /dev/null
+++ b/t/t3206/history.export
@@ -0,0 +1,604 @@
+blob
+mark :1
+data 51
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+reset refs/heads/removed
+commit refs/heads/removed
+mark :2
+author Thomas Rast <trast@inf.ethz.ch> 1374424921 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374484724 +0200
+data 8
+initial
+M 100644 :1 file
+
+blob
+mark :3
+data 51
+1
+2
+3
+4
+A
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :4
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+blob
+mark :5
+data 51
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :6
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+data 7
+s/4/A/
+from :4
+M 100644 :5 file
+
+blob
+mark :7
+data 50
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+B
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :8
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+data 8
+s/11/B/
+from :6
+M 100644 :7 file
+
+blob
+mark :9
+data 49
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+B
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/topic
+mark :10
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+data 8
+s/12/B/
+from :8
+M 100644 :9 file
+
+blob
+mark :11
+data 10
+unrelated
+
+commit refs/heads/master
+mark :12
+author Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200
+data 10
+unrelated
+from :2
+M 100644 :11 otherfile
+
+commit refs/heads/rebased
+mark :13
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485137 +0200
+data 7
+s/5/A/
+from :12
+M 100644 :3 file
+
+commit refs/heads/rebased
+mark :14
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
+data 7
+s/4/A/
+from :13
+M 100644 :5 file
+
+commit refs/heads/rebased
+mark :15
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
+data 8
+s/11/B/
+from :14
+M 100644 :7 file
+
+commit refs/heads/rebased
+mark :16
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
+data 8
+s/12/B/
+from :15
+M 100644 :9 file
+
+commit refs/heads/added
+mark :17
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/added
+mark :18
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 7
+s/4/A/
+from :17
+M 100644 :5 file
+
+blob
+mark :19
+data 51
+1
+2
+3
+A
+A
+A
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/added
+mark :20
+author Thomas Rast <trast@inf.ethz.ch> 1374485186 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 7
+s/6/A/
+from :18
+M 100644 :19 file
+
+blob
+mark :21
+data 50
+1
+2
+3
+A
+A
+A
+7
+8
+9
+10
+B
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/added
+mark :22
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 8
+s/11/B/
+from :20
+M 100644 :21 file
+
+blob
+mark :23
+data 49
+1
+2
+3
+A
+A
+A
+7
+8
+9
+10
+B
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/added
+mark :24
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
+data 8
+s/12/B/
+from :22
+M 100644 :23 file
+
+commit refs/heads/reordered
+mark :25
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+blob
+mark :26
+data 50
+1
+2
+3
+4
+A
+6
+7
+8
+9
+10
+B
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/reordered
+mark :27
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 8
+s/11/B/
+from :25
+M 100644 :26 file
+
+blob
+mark :28
+data 49
+1
+2
+3
+4
+A
+6
+7
+8
+9
+10
+B
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/reordered
+mark :29
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 8
+s/12/B/
+from :27
+M 100644 :28 file
+
+commit refs/heads/reordered
+mark :30
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
+data 7
+s/4/A/
+from :29
+M 100644 :9 file
+
+commit refs/heads/changed
+mark :31
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/changed
+mark :32
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 7
+s/4/A/
+from :31
+M 100644 :5 file
+
+blob
+mark :33
+data 51
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+BB
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/changed
+mark :34
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 8
+s/11/B/
+from :32
+M 100644 :33 file
+
+blob
+mark :35
+data 50
+1
+2
+3
+A
+A
+6
+7
+8
+9
+10
+BB
+B
+13
+14
+15
+16
+17
+18
+19
+20
+
+commit refs/heads/changed
+mark :36
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
+data 8
+s/12/B/
+from :34
+M 100644 :35 file
+
+commit refs/heads/changed-message
+mark :37
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/changed-message
+mark :38
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200
+data 35
+s/4/A/
+
+Also a silly comment here!
+from :37
+M 100644 :5 file
+
+commit refs/heads/changed-message
+mark :39
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200
+data 8
+s/11/B/
+from :38
+M 100644 :7 file
+
+commit refs/heads/changed-message
+mark :40
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200
+data 8
+s/12/B/
+from :39
+M 100644 :9 file
+
+commit refs/heads/unmodified
+mark :41
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/unmodified
+mark :42
+author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200
+data 7
+s/4/A/
+from :41
+M 100644 :5 file
+
+commit refs/heads/unmodified
+mark :43
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200
+data 8
+s/11/B/
+from :42
+M 100644 :7 file
+
+commit refs/heads/unmodified
+mark :44
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200
+data 8
+s/12/B/
+from :43
+M 100644 :9 file
+
+commit refs/heads/removed
+mark :45
+author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
+data 7
+s/5/A/
+from :2
+M 100644 :3 file
+
+commit refs/heads/removed
+mark :46
+author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
+data 8
+s/11/B/
+from :45
+M 100644 :26 file
+
+commit refs/heads/removed
+mark :47
+author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
+data 8
+s/12/B/
+from :46
+M 100644 :28 file
+
+reset refs/heads/removed
+from :47
+
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 12/21] range-diff: use color for the commit pairs
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                             ` (10 preceding siblings ...)
  2018-08-13 11:33           ` [PATCH v6 11/21] range-diff: add tests Thomas Rast via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 13/21] color: add the meta color GIT_COLOR_REVERSE Johannes Schindelin via GitGitGadget
                             ` (9 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

Arguably the most important part of `git range-diff`'s output is the
list of commits in the two branches, together with their relationships.

For that reason, tbdiff introduced color-coding that is pretty
intuitive, especially for unchanged patches (all dim yellow, like the
first line in `git show`'s output) vs modified patches (old commit is
red, new commit is green). Let's imitate that color scheme.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 51 ++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 38 insertions(+), 13 deletions(-)

diff --git a/range-diff.c b/range-diff.c
index 6d75563f4..b1663da7c 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -258,34 +258,53 @@ static void get_correspondences(struct string_list *a, struct string_list *b,
 	free(b2a);
 }
 
-static void output_pair_header(struct strbuf *buf,
+static void output_pair_header(struct diff_options *diffopt,
+			       struct strbuf *buf,
 			       struct strbuf *dashes,
 			       struct patch_util *a_util,
 			       struct patch_util *b_util)
 {
 	struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
 	struct commit *commit;
+	char status;
+	const char *color_reset = diff_get_color_opt(diffopt, DIFF_RESET);
+	const char *color_old = diff_get_color_opt(diffopt, DIFF_FILE_OLD);
+	const char *color_new = diff_get_color_opt(diffopt, DIFF_FILE_NEW);
+	const char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT);
+	const char *color;
 
 	if (!dashes->len)
 		strbuf_addchars(dashes, '-',
 				strlen(find_unique_abbrev(oid,
 							  DEFAULT_ABBREV)));
 
+	if (!b_util) {
+		color = color_old;
+		status = '<';
+	} else if (!a_util) {
+		color = color_new;
+		status = '>';
+	} else if (strcmp(a_util->patch, b_util->patch)) {
+		color = color_commit;
+		status = '!';
+	} else {
+		color = color_commit;
+		status = '=';
+	}
+
 	strbuf_reset(buf);
+	strbuf_addstr(buf, status == '!' ? color_old : color);
 	if (!a_util)
 		strbuf_addf(buf, "-:  %s ", dashes->buf);
 	else
 		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
 			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
 
-	if (!a_util)
-		strbuf_addch(buf, '>');
-	else if (!b_util)
-		strbuf_addch(buf, '<');
-	else if (strcmp(a_util->patch, b_util->patch))
-		strbuf_addch(buf, '!');
-	else
-		strbuf_addch(buf, '=');
+	if (status == '!')
+		strbuf_addf(buf, "%s%s", color_reset, color);
+	strbuf_addch(buf, status);
+	if (status == '!')
+		strbuf_addf(buf, "%s%s", color_reset, color_new);
 
 	if (!b_util)
 		strbuf_addf(buf, " -:  %s", dashes->buf);
@@ -295,10 +314,13 @@ static void output_pair_header(struct strbuf *buf,
 
 	commit = lookup_commit_reference(the_repository, oid);
 	if (commit) {
+		if (status == '!')
+			strbuf_addf(buf, "%s%s", color_reset, color);
+
 		strbuf_addch(buf, ' ');
 		pp_commit_easy(CMIT_FMT_ONELINE, commit, buf);
 	}
-	strbuf_addch(buf, '\n');
+	strbuf_addf(buf, "%s\n", color_reset);
 
 	fwrite(buf->buf, buf->len, 1, stdout);
 }
@@ -356,21 +378,24 @@ static void output(struct string_list *a, struct string_list *b,
 
 		/* Show unmatched LHS commit whose predecessors were shown. */
 		if (i < a->nr && a_util->matching < 0) {
-			output_pair_header(&buf, &dashes, a_util, NULL);
+			output_pair_header(diffopt,
+					   &buf, &dashes, a_util, NULL);
 			i++;
 			continue;
 		}
 
 		/* Show unmatched RHS commits. */
 		while (j < b->nr && b_util->matching < 0) {
-			output_pair_header(&buf, &dashes, NULL, b_util);
+			output_pair_header(diffopt,
+					   &buf, &dashes, NULL, b_util);
 			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
 
 		/* Show matching LHS/RHS pair. */
 		if (j < b->nr) {
 			a_util = a->items[b_util->matching].util;
-			output_pair_header(&buf, &dashes, a_util, b_util);
+			output_pair_header(diffopt,
+					   &buf, &dashes, a_util, b_util);
 			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
 				patch_diff(a->items[b_util->matching].string,
 					   b->items[j].string, diffopt);
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 13/21] color: add the meta color GIT_COLOR_REVERSE
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                             ` (11 preceding siblings ...)
  2018-08-13 11:33           ` [PATCH v6 12/21] range-diff: use color for the commit pairs Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 14/21] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin via GitGitGadget
                             ` (8 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

This "color" simply reverts background and foreground. It will be used
in the upcoming "dual color" mode of `git range-diff`, where we will
reverse colors for the -/+ markers and the fragment headers of the
"outer" diff.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 color.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/color.h b/color.h
index 5b744e1bc..33e786342 100644
--- a/color.h
+++ b/color.h
@@ -44,6 +44,7 @@ struct strbuf;
 #define GIT_COLOR_BG_CYAN	"\033[46m"
 #define GIT_COLOR_FAINT		"\033[2m"
 #define GIT_COLOR_FAINT_ITALIC	"\033[2;3m"
+#define GIT_COLOR_REVERSE	"\033[7m"
 
 /* A special value meaning "no color selected" */
 #define GIT_COLOR_NIL "NIL"
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 14/21] diff: add an internal option to dual-color diffs of diffs
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                             ` (12 preceding siblings ...)
  2018-08-13 11:33           ` [PATCH v6 13/21] color: add the meta color GIT_COLOR_REVERSE Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 15/21] range-diff: offer to dual-color the diffs Johannes Schindelin via GitGitGadget
                             ` (7 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When diffing diffs, it can be quite daunting to figure out what the heck
is going on, as there are nested +/- signs.

Let's make this easier by adding a flag in diff_options that allows
color-coding the outer diff sign with inverted colors, so that the
preimage and postimage is colored like the diff it is.

Of course, this really only makes sense when the preimage and postimage
*are* diffs. So let's not expose this flag via a command-line option for
now.

This is a feature that was invented by git-tbdiff, and it will be used
by `git range-diff` in the next commit, by offering it via a new option:
`--dual-color`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 diff.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++-----------
 diff.h |  1 +
 2 files changed, 69 insertions(+), 15 deletions(-)

diff --git a/diff.c b/diff.c
index 9c4bd9fa1..e6c857abf 100644
--- a/diff.c
+++ b/diff.c
@@ -609,14 +609,18 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,
 	ecbdata->blank_at_eof_in_postimage = (at - l2) + 1;
 }
 
-static void emit_line_0(struct diff_options *o, const char *set, const char *reset,
+static void emit_line_0(struct diff_options *o,
+			const char *set, unsigned reverse, const char *reset,
 			int first, const char *line, int len)
 {
 	int has_trailing_newline, has_trailing_carriage_return;
 	int nofirst;
 	FILE *file = o->file;
 
-	fputs(diff_line_prefix(o), file);
+	if (first)
+		fputs(diff_line_prefix(o), file);
+	else if (!len)
+		return;
 
 	if (len == 0) {
 		has_trailing_newline = (first == '\n');
@@ -634,8 +638,10 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res
 	}
 
 	if (len || !nofirst) {
+		if (reverse && want_color(o->use_color))
+			fputs(GIT_COLOR_REVERSE, file);
 		fputs(set, file);
-		if (!nofirst)
+		if (first && !nofirst)
 			fputc(first, file);
 		fwrite(line, len, 1, file);
 		fputs(reset, file);
@@ -649,7 +655,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res
 static void emit_line(struct diff_options *o, const char *set, const char *reset,
 		      const char *line, int len)
 {
-	emit_line_0(o, set, reset, line[0], line+1, len-1);
+	emit_line_0(o, set, 0, reset, line[0], line+1, len-1);
 }
 
 enum diff_symbol {
@@ -1168,7 +1174,8 @@ static void dim_moved_lines(struct diff_options *o)
 
 static void emit_line_ws_markup(struct diff_options *o,
 				const char *set, const char *reset,
-				const char *line, int len, char sign,
+				const char *line, int len,
+				const char *set_sign, char sign,
 				unsigned ws_rule, int blank_at_eof)
 {
 	const char *ws = NULL;
@@ -1179,14 +1186,20 @@ static void emit_line_ws_markup(struct diff_options *o,
 			ws = NULL;
 	}
 
-	if (!ws)
-		emit_line_0(o, set, reset, sign, line, len);
-	else if (blank_at_eof)
+	if (!ws && !set_sign)
+		emit_line_0(o, set, 0, reset, sign, line, len);
+	else if (!ws) {
+		/* Emit just the prefix, then the rest. */
+		emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
+			    sign, "", 0);
+		emit_line_0(o, set, 0, reset, 0, line, len);
+	} else if (blank_at_eof)
 		/* Blank line at EOF - paint '+' as well */
-		emit_line_0(o, ws, reset, sign, line, len);
+		emit_line_0(o, ws, 0, reset, sign, line, len);
 	else {
 		/* Emit just the prefix, then the rest. */
-		emit_line_0(o, set, reset, sign, "", 0);
+		emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
+			    sign, "", 0);
 		ws_check_emit(line, len, ws_rule,
 			      o->file, set, reset, ws);
 	}
@@ -1196,7 +1209,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 					 struct emitted_diff_symbol *eds)
 {
 	static const char *nneof = " No newline at end of file\n";
-	const char *context, *reset, *set, *meta, *fraginfo;
+	const char *context, *reset, *set, *set_sign, *meta, *fraginfo;
 	struct strbuf sb = STRBUF_INIT;
 
 	enum diff_symbol s = eds->s;
@@ -1209,7 +1222,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 		context = diff_get_color_opt(o, DIFF_CONTEXT);
 		reset = diff_get_color_opt(o, DIFF_RESET);
 		putc('\n', o->file);
-		emit_line_0(o, context, reset, '\\',
+		emit_line_0(o, context, 0, reset, '\\',
 			    nneof, strlen(nneof));
 		break;
 	case DIFF_SYMBOL_SUBMODULE_HEADER:
@@ -1236,7 +1249,18 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 	case DIFF_SYMBOL_CONTEXT:
 		set = diff_get_color_opt(o, DIFF_CONTEXT);
 		reset = diff_get_color_opt(o, DIFF_RESET);
-		emit_line_ws_markup(o, set, reset, line, len, ' ',
+		set_sign = NULL;
+		if (o->flags.dual_color_diffed_diffs) {
+			char c = !len ? 0 : line[0];
+
+			if (c == '+')
+				set = diff_get_color_opt(o, DIFF_FILE_NEW);
+			else if (c == '@')
+				set = diff_get_color_opt(o, DIFF_FRAGINFO);
+			else if (c == '-')
+				set = diff_get_color_opt(o, DIFF_FILE_OLD);
+		}
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',
 				    flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);
 		break;
 	case DIFF_SYMBOL_PLUS:
@@ -1263,7 +1287,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 			set = diff_get_color_opt(o, DIFF_FILE_NEW);
 		}
 		reset = diff_get_color_opt(o, DIFF_RESET);
-		emit_line_ws_markup(o, set, reset, line, len, '+',
+		if (!o->flags.dual_color_diffed_diffs)
+			set_sign = NULL;
+		else {
+			char c = !len ? 0 : line[0];
+
+			set_sign = set;
+			if (c == '-')
+				set = diff_get_color_opt(o, DIFF_FILE_OLD);
+			else if (c == '@')
+				set = diff_get_color_opt(o, DIFF_FRAGINFO);
+			else if (c != '+')
+				set = diff_get_color_opt(o, DIFF_CONTEXT);
+		}
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK,
 				    flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);
 		break;
@@ -1291,7 +1328,20 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 			set = diff_get_color_opt(o, DIFF_FILE_OLD);
 		}
 		reset = diff_get_color_opt(o, DIFF_RESET);
-		emit_line_ws_markup(o, set, reset, line, len, '-',
+		if (!o->flags.dual_color_diffed_diffs)
+			set_sign = NULL;
+		else {
+			char c = !len ? 0 : line[0];
+
+			set_sign = set;
+			if (c == '+')
+				set = diff_get_color_opt(o, DIFF_FILE_NEW);
+			else if (c == '@')
+				set = diff_get_color_opt(o, DIFF_FRAGINFO);
+			else if (c != '-')
+				set = diff_get_color_opt(o, DIFF_CONTEXT);
+		}
+		emit_line_ws_markup(o, set, reset, line, len, set_sign, '-',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);
 		break;
 	case DIFF_SYMBOL_WORDS_PORCELAIN:
@@ -1482,6 +1532,7 @@ static void emit_hunk_header(struct emit_callback *ecbdata,
 	const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
 	const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);
 	const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
+	const char *reverse = ecbdata->color_diff ? GIT_COLOR_REVERSE : "";
 	static const char atat[2] = { '@', '@' };
 	const char *cp, *ep;
 	struct strbuf msgbuf = STRBUF_INIT;
@@ -1502,6 +1553,8 @@ static void emit_hunk_header(struct emit_callback *ecbdata,
 	ep += 2; /* skip over @@ */
 
 	/* The hunk header in fraginfo color */
+	if (ecbdata->opt->flags.dual_color_diffed_diffs)
+		strbuf_addstr(&msgbuf, reverse);
 	strbuf_addstr(&msgbuf, frag);
 	strbuf_add(&msgbuf, line, ep - line);
 	strbuf_addstr(&msgbuf, reset);
diff --git a/diff.h b/diff.h
index d88ceb357..cca4f9d6c 100644
--- a/diff.h
+++ b/diff.h
@@ -95,6 +95,7 @@ struct diff_flags {
 	unsigned default_follow_renames:1;
 	unsigned stat_with_summary:1;
 	unsigned suppress_diff_headers:1;
+	unsigned dual_color_diffed_diffs:1;
 };
 
 static inline void diff_flags_or(struct diff_flags *a,
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 15/21] range-diff: offer to dual-color the diffs
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                             ` (13 preceding siblings ...)
  2018-08-13 11:33           ` [PATCH v6 14/21] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 16/21] range-diff --dual-color: skip white-space warnings Johannes Schindelin via GitGitGadget
                             ` (6 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When showing what changed between old and new commits, we show a diff of
the patches. This diff is a diff between diffs, therefore there are
nested +/- signs, and it can be relatively hard to understand what is
going on.

With the --dual-color option, the preimage and the postimage are colored
like the diffs they are, and the *outer* +/- sign is inverted for
clarity.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/range-diff.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 76659d0b3..5a9ad82fb 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -20,9 +20,12 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 {
 	int creation_factor = 60;
 	struct diff_options diffopt = { NULL };
+	int dual_color = 0;
 	struct option options[] = {
 		OPT_INTEGER(0, "creation-factor", &creation_factor,
 			    N_("Percentage by which creation is weighted")),
+		OPT_BOOL(0, "dual-color", &dual_color,
+			    N_("color both diff and diff-between-diffs")),
 		OPT_END()
 	};
 	int i, j, res = 0;
@@ -60,6 +63,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 			     options + ARRAY_SIZE(options) - 1, /* OPT_END */
 			     builtin_range_diff_usage, 0);
 
+	if (dual_color) {
+		diffopt.use_color = 1;
+		diffopt.flags.dual_color_diffed_diffs = 1;
+	}
+
 	if (argc == 2) {
 		if (!strstr(argv[0], ".."))
 			die(_("no .. in range: '%s'"), argv[0]);
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 16/21] range-diff --dual-color: skip white-space warnings
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                             ` (14 preceding siblings ...)
  2018-08-13 11:33           ` [PATCH v6 15/21] range-diff: offer to dual-color the diffs Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2018-08-13 17:48             ` Junio C Hamano
  2018-08-13 11:33           ` [PATCH v6 17/21] range-diff: populate the man page Johannes Schindelin via GitGitGadget
                             ` (5 subsequent siblings)
  21 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When displaying a diff of diffs, it is possible that there is an outer
`+` before a context line. That happens when the context changed between
old and new commit. When that context line starts with a tab (after the
space that marks it as context line), our diff machinery spits out a
white-space error (space before tab), but in this case, that is
incorrect.

Rather than adding a specific whitespace flag that specifically ignores
the first space in the output (and might miss other problems with the
white-space warnings), let's just skip handling white-space errors in
dual color mode to begin with.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 diff.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/diff.c b/diff.c
index e6c857abf..ea8ecae04 100644
--- a/diff.c
+++ b/diff.c
@@ -1299,6 +1299,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 				set = diff_get_color_opt(o, DIFF_FRAGINFO);
 			else if (c != '+')
 				set = diff_get_color_opt(o, DIFF_CONTEXT);
+			flags &= ~DIFF_SYMBOL_CONTENT_WS_MASK;
 		}
 		emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK,
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 17/21] range-diff: populate the man page
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                             ` (15 preceding siblings ...)
  2018-08-13 11:33           ` [PATCH v6 16/21] range-diff --dual-color: skip white-space warnings Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2018-09-09 11:14             ` Ævar Arnfjörð Bjarmason
  2018-08-13 11:33           ` [PATCH v6 18/21] completion: support `git range-diff` Johannes Schindelin via GitGitGadget
                             ` (4 subsequent siblings)
  21 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

The bulk of this patch consists of a heavily butchered version of
tbdiff's README written by Thomas Rast and Thomas Gummerer, lifted from
https://github.com/trast/tbdiff.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/git-range-diff.txt | 229 +++++++++++++++++++++++++++++++
 1 file changed, 229 insertions(+)

diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
index 49f717db8..bebb47d42 100644
--- a/Documentation/git-range-diff.txt
+++ b/Documentation/git-range-diff.txt
@@ -5,6 +5,235 @@ NAME
 ----
 git-range-diff - Compare two commit ranges (e.g. two versions of a branch)
 
+SYNOPSIS
+--------
+[verse]
+'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]
+	[--dual-color] [--creation-factor=<factor>]
+	( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
+
+DESCRIPTION
+-----------
+
+This command shows the differences between two versions of a patch
+series, or more generally, two commit ranges (ignoring merge commits).
+
+To that end, it first finds pairs of commits from both commit ranges
+that correspond with each other. Two commits are said to correspond when
+the diff between their patches (i.e. the author information, the commit
+message and the commit diff) is reasonably small compared to the
+patches' size. See ``Algorithm`` below for details.
+
+Finally, the list of matching commits is shown in the order of the
+second commit range, with unmatched commits being inserted just after
+all of their ancestors have been shown.
+
+
+OPTIONS
+-------
+--dual-color::
+	When the commit diffs differ, recreate the original diffs'
+	coloring, and add outer -/+ diff markers with the *background*
+	being red/green to make it easier to see e.g. when there was a
+	change in what exact lines were added.
+
+--creation-factor=<percent>::
+	Set the creation/deletion cost fudge factor to `<percent>`.
+	Defaults to 60. Try a larger value if `git range-diff` erroneously
+	considers a large change a total rewrite (deletion of one commit
+	and addition of another), and a smaller one in the reverse case.
+	See the ``Algorithm`` section below for an explanation why this is
+	needed.
+
+<range1> <range2>::
+	Compare the commits specified by the two ranges, where
+	`<range1>` is considered an older version of `<range2>`.
+
+<rev1>...<rev2>::
+	Equivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.
+
+<base> <rev1> <rev2>::
+	Equivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.
+	Note that `<base>` does not need to be the exact branch point
+	of the branches. Example: after rebasing a branch `my-topic`,
+	`git range-diff my-topic@{u} my-topic@{1} my-topic` would
+	show the differences introduced by the rebase.
+
+`git range-diff` also accepts the regular diff options (see
+linkgit:git-diff[1]), most notably the `--color=[<when>]` and
+`--no-color` options. These options are used when generating the "diff
+between patches", i.e. to compare the author, commit message and diff of
+corresponding old/new commits. There is currently no means to tweak the
+diff options passed to `git log` when generating those patches.
+
+
+CONFIGURATION
+-------------
+This command uses the `diff.color.*` and `pager.range-diff` settings
+(the latter is on by default).
+See linkgit:git-config[1].
+
+
+EXAMPLES
+--------
+
+When a rebase required merge conflicts to be resolved, compare the changes
+introduced by the rebase directly afterwards using:
+
+------------
+$ git range-diff @{u} @{1} @
+------------
+
+
+A typical output of `git range-diff` would look like this:
+
+------------
+-:  ------- > 1:  0ddba11 Prepare for the inevitable!
+1:  c0debee = 2:  cab005e Add a helpful message at the start
+2:  f00dbal ! 3:  decafe1 Describe a bug
+    @@ -1,3 +1,3 @@
+     Author: A U Thor <author@example.com>
+
+    -TODO: Describe a bug
+    +Describe a bug
+    @@ -324,5 +324,6
+      This is expected.
+
+    -+What is unexpected is that it will also crash.
+    ++Unexpectedly, it also crashes. This is a bug, and the jury is
+    ++still out there how to fix it best. See ticket #314 for details.
+
+      Contact
+3:  bedead < -:  ------- TO-UNDO
+------------
+
+In this example, there are 3 old and 3 new commits, where the developer
+removed the 3rd, added a new one before the first two, and modified the
+commit message of the 2nd commit as well its diff.
+
+When the output goes to a terminal, it is color-coded by default, just
+like regular `git diff`'s output. In addition, the first line (adding a
+commit) is green, the last line (deleting a commit) is red, the second
+line (with a perfect match) is yellow like the commit header of `git
+show`'s output, and the third line colors the old commit red, the new
+one green and the rest like `git show`'s commit header.
+
+The color-coded diff is actually a bit hard to read, though, as it
+colors the entire lines red or green. The line that added "What is
+unexpected" in the old commit, for example, is completely red, even if
+the intent of the old commit was to add something.
+
+To help with that, use the `--dual-color` mode. In this mode, the diff
+of diffs will retain the original diff colors, and prefix the lines with
+-/+ markers that have their *background* red or green, to make it more
+obvious that they describe how the diff itself changed.
+
+
+Algorithm
+---------
+
+The general idea is this: we generate a cost matrix between the commits
+in both commit ranges, then solve the least-cost assignment.
+
+The cost matrix is populated thusly: for each pair of commits, both
+diffs are generated and the "diff of diffs" is generated, with 3 context
+lines, then the number of lines in that diff is used as cost.
+
+To avoid false positives (e.g. when a patch has been removed, and an
+unrelated patch has been added between two iterations of the same patch
+series), the cost matrix is extended to allow for that, by adding
+fixed-cost entries for wholesale deletes/adds.
+
+Example: Let commits `1--2` be the first iteration of a patch series and
+`A--C` the second iteration. Let's assume that `A` is a cherry-pick of
+`2,` and `C` is a cherry-pick of `1` but with a small modification (say,
+a fixed typo). Visualize the commits as a bipartite graph:
+
+------------
+    1            A
+
+    2            B
+
+		 C
+------------
+
+We are looking for a "best" explanation of the new series in terms of
+the old one. We can represent an "explanation" as an edge in the graph:
+
+
+------------
+    1            A
+	       /
+    2 --------'  B
+
+		 C
+------------
+
+This explanation comes for "free" because there was no change. Similarly
+`C` could be explained using `1`, but that comes at some cost c>0
+because of the modification:
+
+------------
+    1 ----.      A
+	  |    /
+    2 ----+---'  B
+	  |
+	  `----- C
+	  c>0
+------------
+
+In mathematical terms, what we are looking for is some sort of a minimum
+cost bipartite matching; `1` is matched to `C` at some cost, etc. The
+underlying graph is in fact a complete bipartite graph; the cost we
+associate with every edge is the size of the diff between the two
+commits' patches. To explain also new commits, we introduce dummy nodes
+on both sides:
+
+------------
+    1 ----.      A
+	  |    /
+    2 ----+---'  B
+	  |
+    o     `----- C
+	  c>0
+    o            o
+
+    o            o
+------------
+
+The cost of an edge `o--C` is the size of `C`'s diff, modified by a
+fudge factor that should be smaller than 100%. The cost of an edge
+`o--o` is free. The fudge factor is necessary because even if `1` and
+`C` have nothing in common, they may still share a few empty lines and
+such, possibly making the assignment `1--C`, `o--o` slightly cheaper
+than `1--o`, `o--C` even if `1` and `C` have nothing in common. With the
+fudge factor we require a much larger common part to consider patches as
+corresponding.
+
+The overall time needed to compute this algorithm is the time needed to
+compute n+m commit diffs and then n*m diffs of patches, plus the time
+needed to compute the least-cost assigment between n and m diffs. Git
+uses an implementation of the Jonker-Volgenant algorithm to solve the
+assignment problem, which has cubic runtime complexity. The matching
+found in this case will look like this:
+
+------------
+    1 ----.      A
+	  |    /
+    2 ----+---'  B
+       .--+-----'
+    o -'  `----- C
+	  c>0
+    o ---------- o
+
+    o ---------- o
+------------
+
+
+SEE ALSO
+--------
+linkgit:git-log[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 18/21] completion: support `git range-diff`
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                             ` (16 preceding siblings ...)
  2018-08-13 11:33           ` [PATCH v6 17/21] range-diff: populate the man page Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 19/21] range-diff: left-pad patch numbers Johannes Schindelin via GitGitGadget
                             ` (3 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

Tab completion of `git range-diff` is very convenient, especially
given that the revision arguments to specify the commit ranges to
compare are typically more complex than, say, what is normally passed
to `git log`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 contrib/completion/git-completion.bash | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 94c95516e..3d4ec3432 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1976,6 +1976,20 @@ _git_push ()
 	__git_complete_remote_or_refspec
 }
 
+_git_range_diff ()
+{
+	case "$cur" in
+	--*)
+		__gitcomp "
+			--creation-factor= --dual-color
+			$__git_diff_common_options
+		"
+		return
+		;;
+	esac
+	__git_complete_revlist
+}
+
 _git_rebase ()
 {
 	__git_find_repo_path
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 19/21] range-diff: left-pad patch numbers
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                             ` (17 preceding siblings ...)
  2018-08-13 11:33           ` [PATCH v6 18/21] completion: support `git range-diff` Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 20/21] range-diff: make --dual-color the default mode Johannes Schindelin via GitGitGadget
                             ` (2 subsequent siblings)
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

As pointed out by Elijah Newren, tbdiff has this neat little alignment
trick where it outputs the commit pairs with patch numbers that are
padded to the maximal patch number's width:

	  1: cafedead =   1: acefade first patch
	[...]
	314: beefeada < 314: facecab up to PI!

Let's do the same in range-diff, too.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 range-diff.c | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/range-diff.c b/range-diff.c
index b1663da7c..b6b9abac2 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -259,6 +259,7 @@ static void get_correspondences(struct string_list *a, struct string_list *b,
 }
 
 static void output_pair_header(struct diff_options *diffopt,
+			       int patch_no_width,
 			       struct strbuf *buf,
 			       struct strbuf *dashes,
 			       struct patch_util *a_util,
@@ -295,9 +296,9 @@ static void output_pair_header(struct diff_options *diffopt,
 	strbuf_reset(buf);
 	strbuf_addstr(buf, status == '!' ? color_old : color);
 	if (!a_util)
-		strbuf_addf(buf, "-:  %s ", dashes->buf);
+		strbuf_addf(buf, "%*s:  %s ", patch_no_width, "-", dashes->buf);
 	else
-		strbuf_addf(buf, "%d:  %s ", a_util->i + 1,
+		strbuf_addf(buf, "%*d:  %s ", patch_no_width, a_util->i + 1,
 			    find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
 
 	if (status == '!')
@@ -307,9 +308,9 @@ static void output_pair_header(struct diff_options *diffopt,
 		strbuf_addf(buf, "%s%s", color_reset, color_new);
 
 	if (!b_util)
-		strbuf_addf(buf, " -:  %s", dashes->buf);
+		strbuf_addf(buf, " %*s:  %s", patch_no_width, "-", dashes->buf);
 	else
-		strbuf_addf(buf, " %d:  %s", b_util->i + 1,
+		strbuf_addf(buf, " %*d:  %s", patch_no_width, b_util->i + 1,
 			    find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));
 
 	commit = lookup_commit_reference(the_repository, oid);
@@ -357,6 +358,7 @@ static void output(struct string_list *a, struct string_list *b,
 		   struct diff_options *diffopt)
 {
 	struct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;
+	int patch_no_width = decimal_width(1 + (a->nr > b->nr ? a->nr : b->nr));
 	int i = 0, j = 0;
 
 	/*
@@ -378,7 +380,7 @@ static void output(struct string_list *a, struct string_list *b,
 
 		/* Show unmatched LHS commit whose predecessors were shown. */
 		if (i < a->nr && a_util->matching < 0) {
-			output_pair_header(diffopt,
+			output_pair_header(diffopt, patch_no_width,
 					   &buf, &dashes, a_util, NULL);
 			i++;
 			continue;
@@ -386,7 +388,7 @@ static void output(struct string_list *a, struct string_list *b,
 
 		/* Show unmatched RHS commits. */
 		while (j < b->nr && b_util->matching < 0) {
-			output_pair_header(diffopt,
+			output_pair_header(diffopt, patch_no_width,
 					   &buf, &dashes, NULL, b_util);
 			b_util = ++j < b->nr ? b->items[j].util : NULL;
 		}
@@ -394,7 +396,7 @@ static void output(struct string_list *a, struct string_list *b,
 		/* Show matching LHS/RHS pair. */
 		if (j < b->nr) {
 			a_util = a->items[b_util->matching].util;
-			output_pair_header(diffopt,
+			output_pair_header(diffopt, patch_no_width,
 					   &buf, &dashes, a_util, b_util);
 			if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
 				patch_diff(a->items[b_util->matching].string,
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 20/21] range-diff: make --dual-color the default mode
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                             ` (18 preceding siblings ...)
  2018-08-13 11:33           ` [PATCH v6 19/21] range-diff: left-pad patch numbers Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2018-08-13 11:33           ` [PATCH v6 21/21] range-diff: use dim/bold cues to improve dual color mode Johannes Schindelin via GitGitGadget
  2018-08-13 11:38           ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

After using this command extensively for the last two months, this
developer came to the conclusion that even if the dual color mode still
leaves a lot of room for confusion about what was actually changed, the
non-dual color mode is substantially worse in that regard.

Therefore, we really want to make the dual color mode the default.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/git-range-diff.txt       | 32 +++++++++++++++-----------
 builtin/range-diff.c                   | 10 ++++----
 contrib/completion/git-completion.bash |  2 +-
 3 files changed, 25 insertions(+), 19 deletions(-)

diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
index bebb47d42..82c71c682 100644
--- a/Documentation/git-range-diff.txt
+++ b/Documentation/git-range-diff.txt
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]
-	[--dual-color] [--creation-factor=<factor>]
+	[--no-dual-color] [--creation-factor=<factor>]
 	( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
 
 DESCRIPTION
@@ -31,11 +31,14 @@ all of their ancestors have been shown.
 
 OPTIONS
 -------
---dual-color::
-	When the commit diffs differ, recreate the original diffs'
-	coloring, and add outer -/+ diff markers with the *background*
-	being red/green to make it easier to see e.g. when there was a
-	change in what exact lines were added.
+--no-dual-color::
+	When the commit diffs differ, `git range-diff` recreates the
+	original diffs' coloring, and adds outer -/+ diff markers with
+	the *background* being red/green to make it easier to see e.g.
+	when there was a change in what exact lines were added. This is
+	known to `range-diff` as "dual coloring". Use `--no-dual-color`
+	to revert to color all lines according to the outer diff markers
+	(and completely ignore the inner diff when it comes to color).
 
 --creation-factor=<percent>::
 	Set the creation/deletion cost fudge factor to `<percent>`.
@@ -118,15 +121,16 @@ line (with a perfect match) is yellow like the commit header of `git
 show`'s output, and the third line colors the old commit red, the new
 one green and the rest like `git show`'s commit header.
 
-The color-coded diff is actually a bit hard to read, though, as it
-colors the entire lines red or green. The line that added "What is
-unexpected" in the old commit, for example, is completely red, even if
-the intent of the old commit was to add something.
+A naive color-coded diff of diffs is actually a bit hard to read,
+though, as it colors the entire lines red or green. The line that added
+"What is unexpected" in the old commit, for example, is completely red,
+even if the intent of the old commit was to add something.
 
-To help with that, use the `--dual-color` mode. In this mode, the diff
-of diffs will retain the original diff colors, and prefix the lines with
--/+ markers that have their *background* red or green, to make it more
-obvious that they describe how the diff itself changed.
+To help with that, `range` uses the `--dual-color` mode by default. In
+this mode, the diff of diffs will retain the original diff colors, and
+prefix the lines with -/+ markers that have their *background* red or
+green, to make it more obvious that they describe how the diff itself
+changed.
 
 
 Algorithm
diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 5a9ad82fb..f52d45d9d 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -20,11 +20,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 {
 	int creation_factor = 60;
 	struct diff_options diffopt = { NULL };
-	int dual_color = 0;
+	int simple_color = -1;
 	struct option options[] = {
 		OPT_INTEGER(0, "creation-factor", &creation_factor,
 			    N_("Percentage by which creation is weighted")),
-		OPT_BOOL(0, "dual-color", &dual_color,
+		OPT_BOOL(0, "no-dual-color", &simple_color,
 			    N_("color both diff and diff-between-diffs")),
 		OPT_END()
 	};
@@ -63,8 +63,10 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
 			     options + ARRAY_SIZE(options) - 1, /* OPT_END */
 			     builtin_range_diff_usage, 0);
 
-	if (dual_color) {
-		diffopt.use_color = 1;
+	if (simple_color < 1) {
+		if (!simple_color)
+			/* force color when --dual-color was used */
+			diffopt.use_color = 1;
 		diffopt.flags.dual_color_diffed_diffs = 1;
 	}
 
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 3d4ec3432..d63d2dffd 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1981,7 +1981,7 @@ _git_range_diff ()
 	case "$cur" in
 	--*)
 		__gitcomp "
-			--creation-factor= --dual-color
+			--creation-factor= --no-dual-color
 			$__git_diff_common_options
 		"
 		return
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 387+ messages in thread

* [PATCH v6 21/21] range-diff: use dim/bold cues to improve dual color mode
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                             ` (19 preceding siblings ...)
  2018-08-13 11:33           ` [PATCH v6 20/21] range-diff: make --dual-color the default mode Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:33           ` Johannes Schindelin via GitGitGadget
  2018-08-13 11:38           ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin
  21 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2018-08-13 11:33 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

It *is* a confusing thing to look at a diff of diffs. All too easy is it
to mix up whether the -/+ markers refer to the "inner" or the "outer"
diff, i.e. whether a `+` indicates that a line was added by either the
old or the new diff (or both), or whether the new diff does something
different than the old diff.

To make things easier to process for normal developers, we introduced
the dual color mode which colors the lines according to the commit diff,
i.e. lines that are added by a commit (whether old, new, or both) are
colored in green. In non-dual color mode, the lines would be colored
according to the outer diff: if the old commit added a line, it would be
colored red (because that line addition is only present in the first
commit range that was specified on the command-line, i.e. the "old"
commit, but not in the second commit range, i.e. the "new" commit).

However, this dual color mode is still not making things clear enough,
as we are looking at two levels of diffs, and we still only pick a color
according to *one* of them (the outer diff marker is colored
differently, of course, but in particular with deep indentation, it is
easy to lose track of that outer diff marker's background color).

Therefore, let's add another dimension to the mix. Still use
green/red/normal according to the commit diffs, but now also dim the
lines that were only in the old commit, and use bold face for the lines
that are only in the new commit.

That way, it is much easier not to lose track of, say, when we are
looking at a line that was added in the previous iteration of a patch
series but the new iteration adds a slightly different version: the
obsolete change will be dimmed, the current version of the patch will be
bold.

At least this developer has a much easier time reading the range-diffs
that way.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/config.txt         |  6 ++++--
 Documentation/git-range-diff.txt | 17 +++++++++++++----
 color.h                          |  6 ++++++
 diff.c                           | 28 ++++++++++++++++++++++------
 diff.h                           |  8 +++++++-
 5 files changed, 52 insertions(+), 13 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 63365dcf3..90241ed77 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1193,8 +1193,10 @@ color.diff.<slot>::
 	(highlighting whitespace errors), `oldMoved` (deleted lines),
 	`newMoved` (added lines), `oldMovedDimmed`, `oldMovedAlternative`,
 	`oldMovedAlternativeDimmed`, `newMovedDimmed`, `newMovedAlternative`
-	and `newMovedAlternativeDimmed` (See the '<mode>'
-	setting of '--color-moved' in linkgit:git-diff[1] for details).
+	`newMovedAlternativeDimmed` (See the '<mode>'
+	setting of '--color-moved' in linkgit:git-diff[1] for details),
+	`contextDimmed`, `oldDimmed`, `newDimmed`, `contextBold`,
+	`oldBold`, and `newBold` (see linkgit:git-range-diff[1] for details).
 
 color.decorate.<slot>::
 	Use customized color for 'git log --decorate' output.  `<slot>` is one
diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
index 82c71c682..f693930fd 100644
--- a/Documentation/git-range-diff.txt
+++ b/Documentation/git-range-diff.txt
@@ -35,10 +35,19 @@ OPTIONS
 	When the commit diffs differ, `git range-diff` recreates the
 	original diffs' coloring, and adds outer -/+ diff markers with
 	the *background* being red/green to make it easier to see e.g.
-	when there was a change in what exact lines were added. This is
-	known to `range-diff` as "dual coloring". Use `--no-dual-color`
-	to revert to color all lines according to the outer diff markers
-	(and completely ignore the inner diff when it comes to color).
+	when there was a change in what exact lines were added.
++
+Additionally, the commit diff lines that are only present in the first commit
+range are shown "dimmed" (this can be overridden using the `color.diff.<slot>`
+config setting where `<slot>` is one of `contextDimmed`, `oldDimmed` and
+`newDimmed`), and the commit diff lines that are only present in the second
+commit range are shown in bold (which can be overridden using the config
+settings `color.diff.<slot>` with `<slot>` being one of `contextBold`,
+`oldBold` or `newBold`).
++
+This is known to `range-diff` as "dual coloring". Use `--no-dual-color`
+to revert to color all lines according to the outer diff markers
+(and completely ignore the inner diff when it comes to color).
 
 --creation-factor=<percent>::
 	Set the creation/deletion cost fudge factor to `<percent>`.
diff --git a/color.h b/color.h
index 33e786342..98894d6a1 100644
--- a/color.h
+++ b/color.h
@@ -36,6 +36,12 @@ struct strbuf;
 #define GIT_COLOR_BOLD_BLUE	"\033[1;34m"
 #define GIT_COLOR_BOLD_MAGENTA	"\033[1;35m"
 #define GIT_COLOR_BOLD_CYAN	"\033[1;36m"
+#define GIT_COLOR_FAINT_RED	"\033[2;31m"
+#define GIT_COLOR_FAINT_GREEN	"\033[2;32m"
+#define GIT_COLOR_FAINT_YELLOW	"\033[2;33m"
+#define GIT_COLOR_FAINT_BLUE	"\033[2;34m"
+#define GIT_COLOR_FAINT_MAGENTA	"\033[2;35m"
+#define GIT_COLOR_FAINT_CYAN	"\033[2;36m"
 #define GIT_COLOR_BG_RED	"\033[41m"
 #define GIT_COLOR_BG_GREEN	"\033[42m"
 #define GIT_COLOR_BG_YELLOW	"\033[43m"
diff --git a/diff.c b/diff.c
index ea8ecae04..ae1314952 100644
--- a/diff.c
+++ b/diff.c
@@ -70,6 +70,12 @@ static char diff_colors[][COLOR_MAXLEN] = {
 	GIT_COLOR_BOLD_YELLOW,	/* NEW_MOVED ALTERNATIVE */
 	GIT_COLOR_FAINT,	/* NEW_MOVED_DIM */
 	GIT_COLOR_FAINT_ITALIC,	/* NEW_MOVED_ALTERNATIVE_DIM */
+	GIT_COLOR_FAINT,	/* CONTEXT_DIM */
+	GIT_COLOR_FAINT_RED,	/* OLD_DIM */
+	GIT_COLOR_FAINT_GREEN,	/* NEW_DIM */
+	GIT_COLOR_BOLD,		/* CONTEXT_BOLD */
+	GIT_COLOR_BOLD_RED,	/* OLD_BOLD */
+	GIT_COLOR_BOLD_GREEN,	/* NEW_BOLD */
 };
 
 static const char *color_diff_slots[] = {
@@ -89,6 +95,12 @@ static const char *color_diff_slots[] = {
 	[DIFF_FILE_NEW_MOVED_ALT]     = "newMovedAlternative",
 	[DIFF_FILE_NEW_MOVED_DIM]     = "newMovedDimmed",
 	[DIFF_FILE_NEW_MOVED_ALT_DIM] = "newMovedAlternativeDimmed",
+	[DIFF_CONTEXT_DIM]	      = "contextDimmed",
+	[DIFF_FILE_OLD_DIM]	      = "oldDimmed",
+	[DIFF_FILE_NEW_DIM]	      = "newDimmed",
+	[DIFF_CONTEXT_BOLD]	      = "contextBold",
+	[DIFF_FILE_OLD_BOLD]	      = "oldBold",
+	[DIFF_FILE_NEW_BOLD]	      = "newBold",
 };
 
 static NORETURN void die_want_option(const char *option_name)
@@ -1294,11 +1306,13 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 
 			set_sign = set;
 			if (c == '-')
-				set = diff_get_color_opt(o, DIFF_FILE_OLD);
+				set = diff_get_color_opt(o, DIFF_FILE_OLD_BOLD);
 			else if (c == '@')
 				set = diff_get_color_opt(o, DIFF_FRAGINFO);
-			else if (c != '+')
-				set = diff_get_color_opt(o, DIFF_CONTEXT);
+			else if (c == '+')
+				set = diff_get_color_opt(o, DIFF_FILE_NEW_BOLD);
+			else
+				set = diff_get_color_opt(o, DIFF_CONTEXT_BOLD);
 			flags &= ~DIFF_SYMBOL_CONTENT_WS_MASK;
 		}
 		emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
@@ -1336,11 +1350,13 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 
 			set_sign = set;
 			if (c == '+')
-				set = diff_get_color_opt(o, DIFF_FILE_NEW);
+				set = diff_get_color_opt(o, DIFF_FILE_NEW_DIM);
 			else if (c == '@')
 				set = diff_get_color_opt(o, DIFF_FRAGINFO);
-			else if (c != '-')
-				set = diff_get_color_opt(o, DIFF_CONTEXT);
+			else if (c == '-')
+				set = diff_get_color_opt(o, DIFF_FILE_OLD_DIM);
+			else
+				set = diff_get_color_opt(o, DIFF_CONTEXT_DIM);
 		}
 		emit_line_ws_markup(o, set, reset, line, len, set_sign, '-',
 				    flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);
diff --git a/diff.h b/diff.h
index cca4f9d6c..e1e54256c 100644
--- a/diff.h
+++ b/diff.h
@@ -248,7 +248,13 @@ enum color_diff {
 	DIFF_FILE_NEW_MOVED = 13,
 	DIFF_FILE_NEW_MOVED_ALT = 14,
 	DIFF_FILE_NEW_MOVED_DIM = 15,
-	DIFF_FILE_NEW_MOVED_ALT_DIM = 16
+	DIFF_FILE_NEW_MOVED_ALT_DIM = 16,
+	DIFF_CONTEXT_DIM = 17,
+	DIFF_FILE_OLD_DIM = 18,
+	DIFF_FILE_NEW_DIM = 19,
+	DIFF_CONTEXT_BOLD = 20,
+	DIFF_FILE_OLD_BOLD = 21,
+	DIFF_FILE_NEW_BOLD = 22,
 };
 const char *diff_get_color(int diff_use_color, enum color_diff ix);
 #define diff_get_color_opt(o, ix) \
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 387+ messages in thread

* Re: [PATCH v6 00/21] Add range-diff, a tbdiff lookalike
  2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
                             ` (20 preceding siblings ...)
  2018-08-13 11:33           ` [PATCH v6 21/21] range-diff: use dim/bold cues to improve dual color mode Johannes Schindelin via GitGitGadget
@ 2018-08-13 11:38           ` Johannes Schindelin
  2018-08-13 20:47             ` Thomas Gummerer
  21 siblings, 1 reply; 387+ messages in thread
From: Johannes Schindelin @ 2018-08-13 11:38 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget; +Cc: git, Junio C Hamano

Hi,

On Mon, 13 Aug 2018, Johannes Schindelin via GitGitGadget wrote:

> 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.
> 
> 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.
> 
> While at it, I forward-ported AEvar's patch to force --decorate=no because 
> git -p tbdiff would fail otherwise.
> 
> 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 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:
> 
>         base=^{/Start.the.merging-rebase}
>         tag=v2.17.0.windows.1
>         pre=$tag$base^2
>         git range-diff $pre$base..$pre $tag$base..$tag
> 
> 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.

Changes since v5:

- Fixed the bug (introduced in v5) where a dashdash would not be handled
  appropriately.

> Changes since v4:
> 
>  * Fixed a typo in the commit message of "range-diff: add tests" that was
>    introduced in v4.
>  * White-space fixes.
>  * Fixed the length of the first header underline in the man page.
>  * Changed the preprocessor guard in linear-assignment.h to reflect the new
>    name (instead of the old name, which was hungarian.h).
>  * Likewise, changed the preprocessor guards in range-diff.h to hide the
>    history of the thrice-renamed command.
>  * Fixed indentation in the completion.
>  * Instead of trying to paper over white-space error handling that does not
>    apply to "diffs of diffs", dual color mode now simply disables all
>    white-space warnings.
>  * When showing the "single arg must be symmetric range" error message, git
>    range-diff now also shows the usage.
>  * Adjusted the commit message of "range-diff: adjust the output of the
>    commit pairs" to avoid the surprise of the reviewer when onelines are
>    printed all of a sudden, too.
>  * "range-diff: adjust the output of the commit pairs" is now using a
>    simpler way to print onelines.
>  * We are now sandwiching the diff_opt_parse() loop between two 
>    parse_options(), to make sure that we caught all options, and that the -- 
>    separator is handled.
>  * Adjusted the lookup_commit_reference() call to the newest master (it now
>    takes a the_repository parameter).
> 
> Changes since v3:
> 
>  * 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).
>  * The documentation was adjusted a bit more in the patch that makes 
>    --dual-color the default.
>  * Clarified the calculation of the cost matrix, as per Stefan Beller's
>    request.
>  * The man page now spells out that merge commits are ignored in the commit
>    ranges (not merges per se).
>  * The code in linear-assignment.c was adjusted to use the SWAP() macro.
>  * 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.
>  * A bogus indentation change was backed out from the patch introducing the
>    first rudimentary implementation.
>  * Instead of merely warning about missing .. in the 2-parameter invocation,
>    we now exit with the error message.
>  * 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).
>  * A lengthy if...else if...else if...else was simplified (from a logical
>    point of view) by reordering it.
>  * The unnecessarily static variable dashes was turned into a local variable
>    of the caller.
>  * The commit message talking about the new man page still referred to git
>    branch --diff, which has been fixed.
>  * A forgotten t7910 reference was changed to t3206.
>  * An unbalanced double-tick was fixed in the man page.
>  * Fixed grammar both of the commit message and the description of the 
>    --no-dual-color option.
>  * 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).
>  * 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.
>  * 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".
> 
> Changes since v2:
> 
>  * Right-aligned the patch numbers in the commit pairs.
>  * Used ALLOC_ARRAY() in hungarian.c instead of xmalloc(sizeof()*size).
>  * Changed compute_assignment()s return type from int to void, as it always
>    succeeds.
>  * Changed the Hungarian Algorithm to use an integer cost matrix.
>  * Changed the --creation-weight option to --creation-factor where is an
>    integer.
>  * Retitled 1/19 and 2/19 to better conform with the current conventions, as
>    pointed out (and suggested) by Junio.
>  * Shut up Coverity, and at the same time avoided passing the unnecessary i 
>    and j parameters to output_pair_header().
>  * Removed support for the --no-patches option: we inherit diff_options'
>    support for -s already (and much more).
>  * 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.
>  * Instead of overriding the fragment header color, the dual color mode will
>    now reverse the "outer" fragment headers, too.
>  * 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.
>  * Pretty much re-wrote the completion, to support the new --diff mode of
>    git-branch. See below: it was reverted for range-diff.
>  * Renamed t7910 to t3206, to be closer to the git-branch tests.
>  * Ensured that git_diff_ui_config() gets called, and therefore color.diff.*
>    respected.
>  * Avoided leaking four_spaces.
>  * Fixed a declaration in a for (;;) statement (which Junio had as a fixup!
>    that I almost missed).
>  * Renamed branch --diff, which had been renamed from branch-diff (which was
>    picked to avoid re-using tbdiff) to range-diff.
>  * Renamed hungarian.c and its header to linear-assignment.c
>  * Made --dual-color the default, and changed it to still auto-detect
>    whether color should be used rather than forcing it
> 
> Johannes Schindelin (20):
>   linear-assignment: a function to solve least-cost assignment problems
>   Introduce `range-diff` to compare iterations of a topic branch
>   range-diff: first rudimentary implementation
>   range-diff: improve the order of the shown commits
>   range-diff: also show the diff between patches
>   range-diff: right-trim commit messages
>   range-diff: indent the diffs just like tbdiff
>   range-diff: suppress the diff headers
>   range-diff: adjust the output of the commit pairs
>   range-diff: do not show "function names" in hunk headers
>   range-diff: use color for the commit pairs
>   color: add the meta color GIT_COLOR_REVERSE
>   diff: add an internal option to dual-color diffs of diffs
>   range-diff: offer to dual-color the diffs
>   range-diff --dual-color: skip white-space warnings
>   range-diff: populate the man page
>   completion: support `git range-diff`
>   range-diff: left-pad patch numbers
>   range-diff: make --dual-color the default mode
>   range-diff: use dim/bold cues to improve dual color mode
> 
> Thomas Rast (1):
>   range-diff: add tests
> 
>  .gitignore                             |   1 +
>  Documentation/config.txt               |   6 +-
>  Documentation/git-range-diff.txt       | 252 +++++++++++
>  Makefile                               |   3 +
>  builtin.h                              |   1 +
>  builtin/range-diff.c                   | 116 +++++
>  color.h                                |   7 +
>  command-list.txt                       |   1 +
>  contrib/completion/git-completion.bash |  14 +
>  diff.c                                 | 105 ++++-
>  diff.h                                 |  10 +-
>  git.c                                  |   1 +
>  linear-assignment.c                    | 201 ++++++++
>  linear-assignment.h                    |  22 +
>  range-diff.c                           | 435 ++++++++++++++++++
>  range-diff.h                           |   9 +
>  t/.gitattributes                       |   1 +
>  t/t3206-range-diff.sh                  | 145 ++++++
>  t/t3206/history.export                 | 604 +++++++++++++++++++++++++
>  19 files changed, 1915 insertions(+), 19 deletions(-)
>  create mode 100644 Documentation/git-range-diff.txt
>  create mode 100644 builtin/range-diff.c
>  create mode 100644 linear-assignment.c
>  create mode 100644 linear-assignment.h
>  create mode 100644 range-diff.c
>  create mode 100644 range-diff.h
>  create mode 100755 t/t3206-range-diff.sh
>  create mode 100644 t/t3206/history.export
> 
> 
> base-commit: 1d89318c48d233d52f1db230cf622935ac3c69fa
> Published-As: https://github.com/gitgitgadget/git/releases/tags/pr-1%2Fdscho%2Fbranch-diff-v6
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1/dscho/branch-diff-v6
> Pull-Request: https://github.com/gitgitgadget/git/pull/1
> 
> Range-diff vs v5:
> 
>   1:  f168da3a3 =  1:  f168da3a3 linear-assignment: a function to solve least-cost assignment problems
>   2:  33758f361 =  2:  33758f361 Introduce `range-diff` to compare iterations of a topic branch
>   3:  08b8c3fc4 =  3:  08b8c3fc4 range-diff: first rudimentary implementation
>   4:  7b9091968 =  4:  7b9091968 range-diff: improve the order of the shown commits
>   5:  9e1e66007 !  5:  8515d2f75 range-diff: also show the diff between patches
>      @@ -80,6 +80,8 @@
>       +		else
>       +			i += c;
>       +	}
>      ++	while (i < argc)
>      ++		argv[j++] = argv[i++];
>       +	argc = j;
>       +	diff_setup_done(&diffopt);
>       +
>   6:  167ca02a3 =  6:  a10ca0163 range-diff: right-trim commit messages
>   7:  ca8de8c75 =  7:  f81cbef2c range-diff: indent the diffs just like tbdiff
>   8:  eb94d1982 =  8:  458090ffd range-diff: suppress the diff headers
>   9:  6330afad9 =  9:  d3be03a44 range-diff: adjust the output of the commit pairs
>  10:  c296675eb = 10:  94b44dfe6 range-diff: do not show "function names" in hunk headers
>  11:  85e0ab82f = 11:  1477c58e4 range-diff: add tests
>  12:  f48b62644 = 12:  32492c159 range-diff: use color for the commit pairs
>  13:  1ad74f939 = 13:  969a196f4 color: add the meta color GIT_COLOR_REVERSE
>  14:  39a0ecd28 = 14:  f1c86f606 diff: add an internal option to dual-color diffs of diffs
>  15:  c32a24f6a = 15:  3c7b9f339 range-diff: offer to dual-color the diffs
>  16:  05947781f = 16:  c56c51c8b range-diff --dual-color: skip white-space warnings
>  17:  3147c4440 = 17:  8c5543a06 range-diff: populate the man page
>  18:  b08e6d937 = 18:  16e3cf27b completion: support `git range-diff`
>  19:  19406283e = 19:  d9b09abcf range-diff: left-pad patch numbers
>  20:  6b3552386 = 20:  f6fd3955e range-diff: make --dual-color the default mode
>  21:  ccf8c1bb2 = 21:  699cd712e range-diff: use dim/bold cues to improve dual color mode
> 
> -- 
> gitgitgadget
> 
> 

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v6 16/21] range-diff --dual-color: skip white-space warnings
  2018-08-13 11:33           ` [PATCH v6 16/21] range-diff --dual-color: skip white-space warnings Johannes Schindelin via GitGitGadget
@ 2018-08-13 17:48             ` Junio C Hamano
  0 siblings, 0 replies; 387+ messages in thread
From: Junio C Hamano @ 2018-08-13 17:48 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget; +Cc: git, Johannes Schindelin

"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:

> From: Johannes Schindelin <johannes.schindelin@gmx.de>
>
> When displaying a diff of diffs, it is possible that there is an outer
> `+` before a context line. That happens when the context changed between
> old and new commit. When that context line starts with a tab (after the
> space that marks it as context line), our diff machinery spits out a
> white-space error (space before tab), but in this case, that is
> incorrect.

    Also it is possible that there is an outer `+`, ` `, or `-`
    before an added line (i.e. the second column is `+`), and that
    line introduces a leading whitespace error (e.g. the whole line
    begins with two plusses, SP and HT, meaning that the new
    iteration of the patch introduces a space-before-tab whitespace
    error), but feeding that to our normal diff machinery would of
    course not catch it as introducing a new whitespace error.

I think it is a good design decision to give up showing whitespace
errors correctly in this round, because the problem is not limited
to the first space on an outer context line.  The next paragraph
would want to lose the mention of the "Rather than".  We do want to
list the problems we know about for future reference, but that is
what we already did in the above paragraph, so all we need to say
after this point in the next paragraph would be "let's punt and
leave whitespace error highlighting for future enhancements" or
something like that.

With the dim/bold cues step applied, the resulting dual-color mode
shows the differences between the previous few rounds and this one
pretty nicely.  After seeing the huccup between v5 and v6, I am a
bit hesitant to merge this immediately to 'next' but hopefully we
can have it there in a few days (I definitely will merge the topic
to my previate edition that I use for my everyday work during this
integration cycle).

Thanks.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v5 05/21] range-diff: also show the diff between patches
  2018-08-13  9:46             ` Johannes Schindelin
@ 2018-08-13 18:01               ` Thomas Gummerer
  0 siblings, 0 replies; 387+ messages in thread
From: Thomas Gummerer @ 2018-08-13 18:01 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Johannes Schindelin via GitGitGadget, git, Junio C Hamano

On 08/13, Johannes Schindelin wrote:
> Hi Thomas,
> 
> On Sun, 12 Aug 2018, Thomas Gummerer wrote:
> 
> > On 08/10, Johannes Schindelin via GitGitGadget wrote:
> > > From: Johannes Schindelin <johannes.schindelin@gmx.de>
> >
> > [...]
> > 
> > I don't think this handles "--" quite as would be expected.  Trying to
> > use "git range-diff -- js/range-diff-v4...HEAD" I get:
> > 
> >     $ ./git range-diff -- js/range-diff-v4...HEAD
> >     error: need two commit ranges
> >     usage: git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>
> >        or: git range-diff [<options>] <old-tip>...<new-tip>
> >        or: git range-diff [<options>] <base> <old-tip> <new-tip>
> > 
> >         --creation-factor <n>
> >                               Percentage by which creation is weighted
> >         --no-dual-color       color both diff and diff-between-diffs
> > 
> > while what I would have expected is to actually get a range diff.
> > This happens because after we break out of the loop we don't add the
> > actual ranges to argv, but just skip them instead.
> 
> Ouch, good point.
> 
> > I think something like the following should be squashed in to this
> > patch.
> > 
> > --->8---
> > diff --git a/builtin/range-diff.c b/builtin/range-diff.c
> > index ef3ba22e29..132574c57a 100644
> > --- a/builtin/range-diff.c
> > +++ b/builtin/range-diff.c
> > @@ -53,6 +53,11 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
> >                 else
> >                         i += c;
> >         }
> > +       if (i < argc && !strcmp("--", argv[i])) {
> > +               i++; j++;
> > +               while (i < argc)
> > +                       argv[j++] = argv[i++];
> > +       }
> >         argc = j;
> >         diff_setup_done(&diffopt);
> 
> I do not think that is correct. The original idea was for the first
> `parse_options()` call to keep the dashdash, for the second one to keep
> the dashdash, too, and for the final one to swallow it.
> 
> Also, if `i < argc` at this point, we already know that `argv[i]` refers
> to the dashdash, otherwise the previous loop would not have exited early.
> 
> I went with this simple version instead:
> 
> 	while (i < argc)
> 		argv[j++] = argv[i++];

Right, that's much better, thanks!

> Thanks!
> Dscho


^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v6 11/21] range-diff: add tests
  2018-08-13 11:33           ` [PATCH v6 11/21] range-diff: add tests Thomas Rast via GitGitGadget
@ 2018-08-13 18:35             ` Thomas Gummerer
  2018-08-14 14:53               ` Johannes Schindelin
  0 siblings, 1 reply; 387+ messages in thread
From: Thomas Gummerer @ 2018-08-13 18:35 UTC (permalink / raw)
  To: Thomas Rast via GitGitGadget; +Cc: git, Junio C Hamano, Thomas Rast

On 08/13, Thomas Rast via GitGitGadget wrote:
> From: Thomas Rast <tr@thomasrast.ch>
> 
> These are essentially lifted from https://github.com/trast/tbdiff, with
> light touch-ups to account for the command now being named `git
> range-diff`.
> 
> Apart from renaming `tbdiff` to `range-diff`, only one test case needed
> to be adjusted: 11 - 'changed message'.
> 
> The underlying reason it had to be adjusted is that diff generation is
> sometimes ambiguous. In this case, a comment line and an empty line are
> added, but it is ambiguous whether they were added after the existing
> empty line, or whether an empty line and the comment line are added
> *before* the existing empty line. And apparently xdiff picks a different
> option here than Python's difflib.
>

Just noticed while reading the whole series again (hopefully for the
last time :)), do we need Thomas Rast's Sign-off here, as he is
credited as the author here? 

> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  t/.gitattributes       |   1 +
>  t/t3206-range-diff.sh  | 145 ++++++++++
>  t/t3206/history.export | 604 +++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 750 insertions(+)
>  create mode 100755 t/t3206-range-diff.sh
>  create mode 100644 t/t3206/history.export
> 
> diff --git a/t/.gitattributes b/t/.gitattributes
> index 3bd959ae5..b17bf71b8 100644
> --- a/t/.gitattributes
> +++ b/t/.gitattributes
> @@ -1,6 +1,7 @@
>  t[0-9][0-9][0-9][0-9]/* -whitespace
>  /diff-lib/* eol=lf
>  /t0110/url-* binary
> +/t3206/* eol=lf
>  /t3900/*.txt eol=lf
>  /t3901/*.txt eol=lf
>  /t4034/*/* eol=lf
> diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
> new file mode 100755
> index 000000000..2237c7f4a
> --- /dev/null
> +++ b/t/t3206-range-diff.sh
> @@ -0,0 +1,145 @@
> +#!/bin/sh
> +
> +test_description='range-diff tests'
> +
> +. ./test-lib.sh
> +
> +# Note that because of the range-diff's heuristics, test_commit does more
> +# harm than good.  We need some real history.
> +
> +test_expect_success 'setup' '
> +	git fast-import < "$TEST_DIRECTORY"/t3206/history.export
> +'
> +
> +test_expect_success 'simple A..B A..C (unmodified)' '
> +	git range-diff --no-color master..topic master..unmodified \
> +		>actual &&
> +	cat >expected <<-EOF &&
> +	1:  4de457d = 1:  35b9b25 s/5/A/
> +	2:  fccce22 = 2:  de345ab s/4/A/
> +	3:  147e64e = 3:  9af6654 s/11/B/
> +	4:  a63e992 = 4:  2901f77 s/12/B/
> +	EOF
> +	test_cmp expected actual
> +'
> +
> +test_expect_success 'simple B...C (unmodified)' '
> +	git range-diff --no-color topic...unmodified >actual &&
> +	# same "expected" as above
> +	test_cmp expected actual
> +'
> +
> +test_expect_success 'simple A B C (unmodified)' '
> +	git range-diff --no-color master topic unmodified >actual &&
> +	# same "expected" as above
> +	test_cmp expected actual
> +'
> +
> +test_expect_success 'trivial reordering' '
> +	git range-diff --no-color master topic reordered >actual &&
> +	cat >expected <<-EOF &&
> +	1:  4de457d = 1:  aca177a s/5/A/
> +	3:  147e64e = 2:  14ad629 s/11/B/
> +	4:  a63e992 = 3:  ee58208 s/12/B/
> +	2:  fccce22 = 4:  307b27a s/4/A/
> +	EOF
> +	test_cmp expected actual
> +'
> +
> +test_expect_success 'removed a commit' '
> +	git range-diff --no-color master topic removed >actual &&
> +	cat >expected <<-EOF &&
> +	1:  4de457d = 1:  7657159 s/5/A/
> +	2:  fccce22 < -:  ------- s/4/A/
> +	3:  147e64e = 2:  43d84d3 s/11/B/
> +	4:  a63e992 = 3:  a740396 s/12/B/
> +	EOF
> +	test_cmp expected actual
> +'
> +
> +test_expect_success 'added a commit' '
> +	git range-diff --no-color master topic added >actual &&
> +	cat >expected <<-EOF &&
> +	1:  4de457d = 1:  2716022 s/5/A/
> +	2:  fccce22 = 2:  b62accd s/4/A/
> +	-:  ------- > 3:  df46cfa s/6/A/
> +	3:  147e64e = 4:  3e64548 s/11/B/
> +	4:  a63e992 = 5:  12b4063 s/12/B/
> +	EOF
> +	test_cmp expected actual
> +'
> +
> +test_expect_success 'new base, A B C' '
> +	git range-diff --no-color master topic rebased >actual &&
> +	cat >expected <<-EOF &&
> +	1:  4de457d = 1:  cc9c443 s/5/A/
> +	2:  fccce22 = 2:  c5d9641 s/4/A/
> +	3:  147e64e = 3:  28cc2b6 s/11/B/
> +	4:  a63e992 = 4:  5628ab7 s/12/B/
> +	EOF
> +	test_cmp expected actual
> +'
> +
> +test_expect_success 'new base, B...C' '
> +	# this syntax includes the commits from master!
> +	git range-diff --no-color topic...rebased >actual &&
> +	cat >expected <<-EOF &&
> +	-:  ------- > 1:  a31b12e unrelated
> +	1:  4de457d = 2:  cc9c443 s/5/A/
> +	2:  fccce22 = 3:  c5d9641 s/4/A/
> +	3:  147e64e = 4:  28cc2b6 s/11/B/
> +	4:  a63e992 = 5:  5628ab7 s/12/B/
> +	EOF
> +	test_cmp expected actual
> +'
> +
> +test_expect_success 'changed commit' '
> +	git range-diff --no-color topic...changed >actual &&
> +	cat >expected <<-EOF &&
> +	1:  4de457d = 1:  a4b3333 s/5/A/
> +	2:  fccce22 = 2:  f51d370 s/4/A/
> +	3:  147e64e ! 3:  0559556 s/11/B/
> +	    @@ -10,7 +10,7 @@
> +	      9
> +	      10
> +	     -11
> +	    -+B
> +	    ++BB
> +	      12
> +	      13
> +	      14
> +	4:  a63e992 ! 4:  d966c5c s/12/B/
> +	    @@ -8,7 +8,7 @@
> +	     @@
> +	      9
> +	      10
> +	    - B
> +	    + BB
> +	     -12
> +	     +B
> +	      13
> +	EOF
> +	test_cmp expected actual
> +'
> +
> +test_expect_success 'changed message' '
> +	git range-diff --no-color topic...changed-message >actual &&
> +	sed s/Z/\ /g >expected <<-EOF &&
> +	1:  4de457d = 1:  f686024 s/5/A/
> +	2:  fccce22 ! 2:  4ab067d s/4/A/
> +	    @@ -2,6 +2,8 @@
> +	    Z
> +	    Z    s/4/A/
> +	    Z
> +	    +    Also a silly comment here!
> +	    +
> +	    Zdiff --git a/file b/file
> +	    Z--- a/file
> +	    Z+++ b/file
> +	3:  147e64e = 3:  b9cb956 s/11/B/
> +	4:  a63e992 = 4:  8add5f1 s/12/B/
> +	EOF
> +	test_cmp expected actual
> +'
> +
> +test_done
> diff --git a/t/t3206/history.export b/t/t3206/history.export
> new file mode 100644
> index 000000000..b8ffff094
> --- /dev/null
> +++ b/t/t3206/history.export
> @@ -0,0 +1,604 @@
> +blob
> +mark :1
> +data 51
> +1
> +2
> +3
> +4
> +5
> +6
> +7
> +8
> +9
> +10
> +11
> +12
> +13
> +14
> +15
> +16
> +17
> +18
> +19
> +20
> +
> +reset refs/heads/removed
> +commit refs/heads/removed
> +mark :2
> +author Thomas Rast <trast@inf.ethz.ch> 1374424921 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374484724 +0200
> +data 8
> +initial
> +M 100644 :1 file
> +
> +blob
> +mark :3
> +data 51
> +1
> +2
> +3
> +4
> +A
> +6
> +7
> +8
> +9
> +10
> +11
> +12
> +13
> +14
> +15
> +16
> +17
> +18
> +19
> +20
> +
> +commit refs/heads/topic
> +mark :4
> +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
> +data 7
> +s/5/A/
> +from :2
> +M 100644 :3 file
> +
> +blob
> +mark :5
> +data 51
> +1
> +2
> +3
> +A
> +A
> +6
> +7
> +8
> +9
> +10
> +11
> +12
> +13
> +14
> +15
> +16
> +17
> +18
> +19
> +20
> +
> +commit refs/heads/topic
> +mark :6
> +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
> +data 7
> +s/4/A/
> +from :4
> +M 100644 :5 file
> +
> +blob
> +mark :7
> +data 50
> +1
> +2
> +3
> +A
> +A
> +6
> +7
> +8
> +9
> +10
> +B
> +12
> +13
> +14
> +15
> +16
> +17
> +18
> +19
> +20
> +
> +commit refs/heads/topic
> +mark :8
> +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
> +data 8
> +s/11/B/
> +from :6
> +M 100644 :7 file
> +
> +blob
> +mark :9
> +data 49
> +1
> +2
> +3
> +A
> +A
> +6
> +7
> +8
> +9
> +10
> +B
> +B
> +13
> +14
> +15
> +16
> +17
> +18
> +19
> +20
> +
> +commit refs/heads/topic
> +mark :10
> +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
> +data 8
> +s/12/B/
> +from :8
> +M 100644 :9 file
> +
> +blob
> +mark :11
> +data 10
> +unrelated
> +
> +commit refs/heads/master
> +mark :12
> +author Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200
> +data 10
> +unrelated
> +from :2
> +M 100644 :11 otherfile
> +
> +commit refs/heads/rebased
> +mark :13
> +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485137 +0200
> +data 7
> +s/5/A/
> +from :12
> +M 100644 :3 file
> +
> +commit refs/heads/rebased
> +mark :14
> +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
> +data 7
> +s/4/A/
> +from :13
> +M 100644 :5 file
> +
> +commit refs/heads/rebased
> +mark :15
> +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
> +data 8
> +s/11/B/
> +from :14
> +M 100644 :7 file
> +
> +commit refs/heads/rebased
> +mark :16
> +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
> +data 8
> +s/12/B/
> +from :15
> +M 100644 :9 file
> +
> +commit refs/heads/added
> +mark :17
> +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
> +data 7
> +s/5/A/
> +from :2
> +M 100644 :3 file
> +
> +commit refs/heads/added
> +mark :18
> +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
> +data 7
> +s/4/A/
> +from :17
> +M 100644 :5 file
> +
> +blob
> +mark :19
> +data 51
> +1
> +2
> +3
> +A
> +A
> +A
> +7
> +8
> +9
> +10
> +11
> +12
> +13
> +14
> +15
> +16
> +17
> +18
> +19
> +20
> +
> +commit refs/heads/added
> +mark :20
> +author Thomas Rast <trast@inf.ethz.ch> 1374485186 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
> +data 7
> +s/6/A/
> +from :18
> +M 100644 :19 file
> +
> +blob
> +mark :21
> +data 50
> +1
> +2
> +3
> +A
> +A
> +A
> +7
> +8
> +9
> +10
> +B
> +12
> +13
> +14
> +15
> +16
> +17
> +18
> +19
> +20
> +
> +commit refs/heads/added
> +mark :22
> +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
> +data 8
> +s/11/B/
> +from :20
> +M 100644 :21 file
> +
> +blob
> +mark :23
> +data 49
> +1
> +2
> +3
> +A
> +A
> +A
> +7
> +8
> +9
> +10
> +B
> +B
> +13
> +14
> +15
> +16
> +17
> +18
> +19
> +20
> +
> +commit refs/heads/added
> +mark :24
> +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
> +data 8
> +s/12/B/
> +from :22
> +M 100644 :23 file
> +
> +commit refs/heads/reordered
> +mark :25
> +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
> +data 7
> +s/5/A/
> +from :2
> +M 100644 :3 file
> +
> +blob
> +mark :26
> +data 50
> +1
> +2
> +3
> +4
> +A
> +6
> +7
> +8
> +9
> +10
> +B
> +12
> +13
> +14
> +15
> +16
> +17
> +18
> +19
> +20
> +
> +commit refs/heads/reordered
> +mark :27
> +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
> +data 8
> +s/11/B/
> +from :25
> +M 100644 :26 file
> +
> +blob
> +mark :28
> +data 49
> +1
> +2
> +3
> +4
> +A
> +6
> +7
> +8
> +9
> +10
> +B
> +B
> +13
> +14
> +15
> +16
> +17
> +18
> +19
> +20
> +
> +commit refs/heads/reordered
> +mark :29
> +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
> +data 8
> +s/12/B/
> +from :27
> +M 100644 :28 file
> +
> +commit refs/heads/reordered
> +mark :30
> +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
> +data 7
> +s/4/A/
> +from :29
> +M 100644 :9 file
> +
> +commit refs/heads/changed
> +mark :31
> +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
> +data 7
> +s/5/A/
> +from :2
> +M 100644 :3 file
> +
> +commit refs/heads/changed
> +mark :32
> +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
> +data 7
> +s/4/A/
> +from :31
> +M 100644 :5 file
> +
> +blob
> +mark :33
> +data 51
> +1
> +2
> +3
> +A
> +A
> +6
> +7
> +8
> +9
> +10
> +BB
> +12
> +13
> +14
> +15
> +16
> +17
> +18
> +19
> +20
> +
> +commit refs/heads/changed
> +mark :34
> +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
> +data 8
> +s/11/B/
> +from :32
> +M 100644 :33 file
> +
> +blob
> +mark :35
> +data 50
> +1
> +2
> +3
> +A
> +A
> +6
> +7
> +8
> +9
> +10
> +BB
> +B
> +13
> +14
> +15
> +16
> +17
> +18
> +19
> +20
> +
> +commit refs/heads/changed
> +mark :36
> +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
> +data 8
> +s/12/B/
> +from :34
> +M 100644 :35 file
> +
> +commit refs/heads/changed-message
> +mark :37
> +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200
> +data 7
> +s/5/A/
> +from :2
> +M 100644 :3 file
> +
> +commit refs/heads/changed-message
> +mark :38
> +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200
> +data 35
> +s/4/A/
> +
> +Also a silly comment here!
> +from :37
> +M 100644 :5 file
> +
> +commit refs/heads/changed-message
> +mark :39
> +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200
> +data 8
> +s/11/B/
> +from :38
> +M 100644 :7 file
> +
> +commit refs/heads/changed-message
> +mark :40
> +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200
> +data 8
> +s/12/B/
> +from :39
> +M 100644 :9 file
> +
> +commit refs/heads/unmodified
> +mark :41
> +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200
> +data 7
> +s/5/A/
> +from :2
> +M 100644 :3 file
> +
> +commit refs/heads/unmodified
> +mark :42
> +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200
> +data 7
> +s/4/A/
> +from :41
> +M 100644 :5 file
> +
> +commit refs/heads/unmodified
> +mark :43
> +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200
> +data 8
> +s/11/B/
> +from :42
> +M 100644 :7 file
> +
> +commit refs/heads/unmodified
> +mark :44
> +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200
> +data 8
> +s/12/B/
> +from :43
> +M 100644 :9 file
> +
> +commit refs/heads/removed
> +mark :45
> +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
> +data 7
> +s/5/A/
> +from :2
> +M 100644 :3 file
> +
> +commit refs/heads/removed
> +mark :46
> +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
> +data 8
> +s/11/B/
> +from :45
> +M 100644 :26 file
> +
> +commit refs/heads/removed
> +mark :47
> +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
> +committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
> +data 8
> +s/12/B/
> +from :46
> +M 100644 :28 file
> +
> +reset refs/heads/removed
> +from :47
> +
> -- 
> gitgitgadget
> 

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v6 00/21] Add range-diff, a tbdiff lookalike
  2018-08-13 11:38           ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin
@ 2018-08-13 20:47             ` Thomas Gummerer
  0 siblings, 0 replies; 387+ messages in thread
From: Thomas Gummerer @ 2018-08-13 20:47 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Johannes Schindelin via GitGitGadget, git, Junio C Hamano

On 08/13, Johannes Schindelin wrote:
> Hi,
> 
> On Mon, 13 Aug 2018, Johannes Schindelin via GitGitGadget wrote:
> 
> > 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.
> > 
> > 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.
> > 
> > While at it, I forward-ported AEvar's patch to force --decorate=no because 
> > git -p tbdiff would fail otherwise.
> > 
> > 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 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:
> > 
> >         base=^{/Start.the.merging-rebase}
> >         tag=v2.17.0.windows.1
> >         pre=$tag$base^2
> >         git range-diff $pre$base..$pre $tag$base..$tag
> > 
> > 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.
> 
> Changes since v5:
> 
> - Fixed the bug (introduced in v5) where a dashdash would not be handled
>   appropriately.

Thanks!  I've read through all the patches (and the range-diff :))
again and played around a bit with the newest version, and I think
this is ready for 'next'.

While playing around with it I did find one error message that reads
slightly odd, but it's still understandable, so I'm not sure it's
worth worrying about now (we can always improve it on top):

     $ ./git range-diff -- js/range-diff-v4...HEADt
    fatal: ambiguous argument 'HEADt..js/range-diff-v4': unknown revision or path not in the working tree.
    Use '--' to separate paths from revisions, like this:
    'git <command> [<revision>...] -- [<file>...]'
    error: could not parse log for 'HEADt..js/range-diff-v4'


> [...]

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v6 11/21] range-diff: add tests
  2018-08-13 18:35             ` Thomas Gummerer
@ 2018-08-14 14:53               ` Johannes Schindelin
  2018-08-14 15:03                 ` Jeff King
  2018-08-14 15:18                 ` Junio C Hamano
  0 siblings, 2 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-08-14 14:53 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: Thomas Rast via GitGitGadget, git, Junio C Hamano, Thomas Rast

Hi Thomas,

On Mon, 13 Aug 2018, Thomas Gummerer wrote:

> On 08/13, Thomas Rast via GitGitGadget wrote:
> > From: Thomas Rast <tr@thomasrast.ch>
> > 
> > These are essentially lifted from https://github.com/trast/tbdiff, with
> > light touch-ups to account for the command now being named `git
> > range-diff`.
> > 
> > Apart from renaming `tbdiff` to `range-diff`, only one test case needed
> > to be adjusted: 11 - 'changed message'.
> > 
> > The underlying reason it had to be adjusted is that diff generation is
> > sometimes ambiguous. In this case, a comment line and an empty line are
> > added, but it is ambiguous whether they were added after the existing
> > empty line, or whether an empty line and the comment line are added
> > *before* the existing empty line. And apparently xdiff picks a different
> > option here than Python's difflib.
> >
> 
> Just noticed while reading the whole series again (hopefully for the
> last time :)), do we need Thomas Rast's Sign-off here, as he is
> credited as the author here? 

Hmm. I hoped that my commit message was enough to indicate that while he
is the author, I assembled this. Maybe I should move him to the footer, as
an Original-Authored-By:?

Junio?

Ciao,
Dscho
> 
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> >  t/.gitattributes       |   1 +
> >  t/t3206-range-diff.sh  | 145 ++++++++++
> >  t/t3206/history.export | 604 +++++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 750 insertions(+)
> >  create mode 100755 t/t3206-range-diff.sh
> >  create mode 100644 t/t3206/history.export
> > 
> > diff --git a/t/.gitattributes b/t/.gitattributes
> > index 3bd959ae5..b17bf71b8 100644
> > --- a/t/.gitattributes
> > +++ b/t/.gitattributes
> > @@ -1,6 +1,7 @@
> >  t[0-9][0-9][0-9][0-9]/* -whitespace
> >  /diff-lib/* eol=lf
> >  /t0110/url-* binary
> > +/t3206/* eol=lf
> >  /t3900/*.txt eol=lf
> >  /t3901/*.txt eol=lf
> >  /t4034/*/* eol=lf
> > diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
> > new file mode 100755
> > index 000000000..2237c7f4a
> > --- /dev/null
> > +++ b/t/t3206-range-diff.sh
> > @@ -0,0 +1,145 @@
> > +#!/bin/sh
> > +
> > +test_description='range-diff tests'
> > +
> > +. ./test-lib.sh
> > +
> > +# Note that because of the range-diff's heuristics, test_commit does more
> > +# harm than good.  We need some real history.
> > +
> > +test_expect_success 'setup' '
> > +	git fast-import < "$TEST_DIRECTORY"/t3206/history.export
> > +'
> > +
> > +test_expect_success 'simple A..B A..C (unmodified)' '
> > +	git range-diff --no-color master..topic master..unmodified \
> > +		>actual &&
> > +	cat >expected <<-EOF &&
> > +	1:  4de457d = 1:  35b9b25 s/5/A/
> > +	2:  fccce22 = 2:  de345ab s/4/A/
> > +	3:  147e64e = 3:  9af6654 s/11/B/
> > +	4:  a63e992 = 4:  2901f77 s/12/B/
> > +	EOF
> > +	test_cmp expected actual
> > +'
> > +
> > +test_expect_success 'simple B...C (unmodified)' '
> > +	git range-diff --no-color topic...unmodified >actual &&
> > +	# same "expected" as above
> > +	test_cmp expected actual
> > +'
> > +
> > +test_expect_success 'simple A B C (unmodified)' '
> > +	git range-diff --no-color master topic unmodified >actual &&
> > +	# same "expected" as above
> > +	test_cmp expected actual
> > +'
> > +
> > +test_expect_success 'trivial reordering' '
> > +	git range-diff --no-color master topic reordered >actual &&
> > +	cat >expected <<-EOF &&
> > +	1:  4de457d = 1:  aca177a s/5/A/
> > +	3:  147e64e = 2:  14ad629 s/11/B/
> > +	4:  a63e992 = 3:  ee58208 s/12/B/
> > +	2:  fccce22 = 4:  307b27a s/4/A/
> > +	EOF
> > +	test_cmp expected actual
> > +'
> > +
> > +test_expect_success 'removed a commit' '
> > +	git range-diff --no-color master topic removed >actual &&
> > +	cat >expected <<-EOF &&
> > +	1:  4de457d = 1:  7657159 s/5/A/
> > +	2:  fccce22 < -:  ------- s/4/A/
> > +	3:  147e64e = 2:  43d84d3 s/11/B/
> > +	4:  a63e992 = 3:  a740396 s/12/B/
> > +	EOF
> > +	test_cmp expected actual
> > +'
> > +
> > +test_expect_success 'added a commit' '
> > +	git range-diff --no-color master topic added >actual &&
> > +	cat >expected <<-EOF &&
> > +	1:  4de457d = 1:  2716022 s/5/A/
> > +	2:  fccce22 = 2:  b62accd s/4/A/
> > +	-:  ------- > 3:  df46cfa s/6/A/
> > +	3:  147e64e = 4:  3e64548 s/11/B/
> > +	4:  a63e992 = 5:  12b4063 s/12/B/
> > +	EOF
> > +	test_cmp expected actual
> > +'
> > +
> > +test_expect_success 'new base, A B C' '
> > +	git range-diff --no-color master topic rebased >actual &&
> > +	cat >expected <<-EOF &&
> > +	1:  4de457d = 1:  cc9c443 s/5/A/
> > +	2:  fccce22 = 2:  c5d9641 s/4/A/
> > +	3:  147e64e = 3:  28cc2b6 s/11/B/
> > +	4:  a63e992 = 4:  5628ab7 s/12/B/
> > +	EOF
> > +	test_cmp expected actual
> > +'
> > +
> > +test_expect_success 'new base, B...C' '
> > +	# this syntax includes the commits from master!
> > +	git range-diff --no-color topic...rebased >actual &&
> > +	cat >expected <<-EOF &&
> > +	-:  ------- > 1:  a31b12e unrelated
> > +	1:  4de457d = 2:  cc9c443 s/5/A/
> > +	2:  fccce22 = 3:  c5d9641 s/4/A/
> > +	3:  147e64e = 4:  28cc2b6 s/11/B/
> > +	4:  a63e992 = 5:  5628ab7 s/12/B/
> > +	EOF
> > +	test_cmp expected actual
> > +'
> > +
> > +test_expect_success 'changed commit' '
> > +	git range-diff --no-color topic...changed >actual &&
> > +	cat >expected <<-EOF &&
> > +	1:  4de457d = 1:  a4b3333 s/5/A/
> > +	2:  fccce22 = 2:  f51d370 s/4/A/
> > +	3:  147e64e ! 3:  0559556 s/11/B/
> > +	    @@ -10,7 +10,7 @@
> > +	      9
> > +	      10
> > +	     -11
> > +	    -+B
> > +	    ++BB
> > +	      12
> > +	      13
> > +	      14
> > +	4:  a63e992 ! 4:  d966c5c s/12/B/
> > +	    @@ -8,7 +8,7 @@
> > +	     @@
> > +	      9
> > +	      10
> > +	    - B
> > +	    + BB
> > +	     -12
> > +	     +B
> > +	      13
> > +	EOF
> > +	test_cmp expected actual
> > +'
> > +
> > +test_expect_success 'changed message' '
> > +	git range-diff --no-color topic...changed-message >actual &&
> > +	sed s/Z/\ /g >expected <<-EOF &&
> > +	1:  4de457d = 1:  f686024 s/5/A/
> > +	2:  fccce22 ! 2:  4ab067d s/4/A/
> > +	    @@ -2,6 +2,8 @@
> > +	    Z
> > +	    Z    s/4/A/
> > +	    Z
> > +	    +    Also a silly comment here!
> > +	    +
> > +	    Zdiff --git a/file b/file
> > +	    Z--- a/file
> > +	    Z+++ b/file
> > +	3:  147e64e = 3:  b9cb956 s/11/B/
> > +	4:  a63e992 = 4:  8add5f1 s/12/B/
> > +	EOF
> > +	test_cmp expected actual
> > +'
> > +
> > +test_done
> > diff --git a/t/t3206/history.export b/t/t3206/history.export
> > new file mode 100644
> > index 000000000..b8ffff094
> > --- /dev/null
> > +++ b/t/t3206/history.export
> > @@ -0,0 +1,604 @@
> > +blob
> > +mark :1
> > +data 51
> > +1
> > +2
> > +3
> > +4
> > +5
> > +6
> > +7
> > +8
> > +9
> > +10
> > +11
> > +12
> > +13
> > +14
> > +15
> > +16
> > +17
> > +18
> > +19
> > +20
> > +
> > +reset refs/heads/removed
> > +commit refs/heads/removed
> > +mark :2
> > +author Thomas Rast <trast@inf.ethz.ch> 1374424921 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374484724 +0200
> > +data 8
> > +initial
> > +M 100644 :1 file
> > +
> > +blob
> > +mark :3
> > +data 51
> > +1
> > +2
> > +3
> > +4
> > +A
> > +6
> > +7
> > +8
> > +9
> > +10
> > +11
> > +12
> > +13
> > +14
> > +15
> > +16
> > +17
> > +18
> > +19
> > +20
> > +
> > +commit refs/heads/topic
> > +mark :4
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
> > +data 7
> > +s/5/A/
> > +from :2
> > +M 100644 :3 file
> > +
> > +blob
> > +mark :5
> > +data 51
> > +1
> > +2
> > +3
> > +A
> > +A
> > +6
> > +7
> > +8
> > +9
> > +10
> > +11
> > +12
> > +13
> > +14
> > +15
> > +16
> > +17
> > +18
> > +19
> > +20
> > +
> > +commit refs/heads/topic
> > +mark :6
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
> > +data 7
> > +s/4/A/
> > +from :4
> > +M 100644 :5 file
> > +
> > +blob
> > +mark :7
> > +data 50
> > +1
> > +2
> > +3
> > +A
> > +A
> > +6
> > +7
> > +8
> > +9
> > +10
> > +B
> > +12
> > +13
> > +14
> > +15
> > +16
> > +17
> > +18
> > +19
> > +20
> > +
> > +commit refs/heads/topic
> > +mark :8
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
> > +data 8
> > +s/11/B/
> > +from :6
> > +M 100644 :7 file
> > +
> > +blob
> > +mark :9
> > +data 49
> > +1
> > +2
> > +3
> > +A
> > +A
> > +6
> > +7
> > +8
> > +9
> > +10
> > +B
> > +B
> > +13
> > +14
> > +15
> > +16
> > +17
> > +18
> > +19
> > +20
> > +
> > +commit refs/heads/topic
> > +mark :10
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
> > +data 8
> > +s/12/B/
> > +from :8
> > +M 100644 :9 file
> > +
> > +blob
> > +mark :11
> > +data 10
> > +unrelated
> > +
> > +commit refs/heads/master
> > +mark :12
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200
> > +data 10
> > +unrelated
> > +from :2
> > +M 100644 :11 otherfile
> > +
> > +commit refs/heads/rebased
> > +mark :13
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485137 +0200
> > +data 7
> > +s/5/A/
> > +from :12
> > +M 100644 :3 file
> > +
> > +commit refs/heads/rebased
> > +mark :14
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
> > +data 7
> > +s/4/A/
> > +from :13
> > +M 100644 :5 file
> > +
> > +commit refs/heads/rebased
> > +mark :15
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
> > +data 8
> > +s/11/B/
> > +from :14
> > +M 100644 :7 file
> > +
> > +commit refs/heads/rebased
> > +mark :16
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
> > +data 8
> > +s/12/B/
> > +from :15
> > +M 100644 :9 file
> > +
> > +commit refs/heads/added
> > +mark :17
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
> > +data 7
> > +s/5/A/
> > +from :2
> > +M 100644 :3 file
> > +
> > +commit refs/heads/added
> > +mark :18
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
> > +data 7
> > +s/4/A/
> > +from :17
> > +M 100644 :5 file
> > +
> > +blob
> > +mark :19
> > +data 51
> > +1
> > +2
> > +3
> > +A
> > +A
> > +A
> > +7
> > +8
> > +9
> > +10
> > +11
> > +12
> > +13
> > +14
> > +15
> > +16
> > +17
> > +18
> > +19
> > +20
> > +
> > +commit refs/heads/added
> > +mark :20
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485186 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
> > +data 7
> > +s/6/A/
> > +from :18
> > +M 100644 :19 file
> > +
> > +blob
> > +mark :21
> > +data 50
> > +1
> > +2
> > +3
> > +A
> > +A
> > +A
> > +7
> > +8
> > +9
> > +10
> > +B
> > +12
> > +13
> > +14
> > +15
> > +16
> > +17
> > +18
> > +19
> > +20
> > +
> > +commit refs/heads/added
> > +mark :22
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
> > +data 8
> > +s/11/B/
> > +from :20
> > +M 100644 :21 file
> > +
> > +blob
> > +mark :23
> > +data 49
> > +1
> > +2
> > +3
> > +A
> > +A
> > +A
> > +7
> > +8
> > +9
> > +10
> > +B
> > +B
> > +13
> > +14
> > +15
> > +16
> > +17
> > +18
> > +19
> > +20
> > +
> > +commit refs/heads/added
> > +mark :24
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
> > +data 8
> > +s/12/B/
> > +from :22
> > +M 100644 :23 file
> > +
> > +commit refs/heads/reordered
> > +mark :25
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
> > +data 7
> > +s/5/A/
> > +from :2
> > +M 100644 :3 file
> > +
> > +blob
> > +mark :26
> > +data 50
> > +1
> > +2
> > +3
> > +4
> > +A
> > +6
> > +7
> > +8
> > +9
> > +10
> > +B
> > +12
> > +13
> > +14
> > +15
> > +16
> > +17
> > +18
> > +19
> > +20
> > +
> > +commit refs/heads/reordered
> > +mark :27
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
> > +data 8
> > +s/11/B/
> > +from :25
> > +M 100644 :26 file
> > +
> > +blob
> > +mark :28
> > +data 49
> > +1
> > +2
> > +3
> > +4
> > +A
> > +6
> > +7
> > +8
> > +9
> > +10
> > +B
> > +B
> > +13
> > +14
> > +15
> > +16
> > +17
> > +18
> > +19
> > +20
> > +
> > +commit refs/heads/reordered
> > +mark :29
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
> > +data 8
> > +s/12/B/
> > +from :27
> > +M 100644 :28 file
> > +
> > +commit refs/heads/reordered
> > +mark :30
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
> > +data 7
> > +s/4/A/
> > +from :29
> > +M 100644 :9 file
> > +
> > +commit refs/heads/changed
> > +mark :31
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
> > +data 7
> > +s/5/A/
> > +from :2
> > +M 100644 :3 file
> > +
> > +commit refs/heads/changed
> > +mark :32
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
> > +data 7
> > +s/4/A/
> > +from :31
> > +M 100644 :5 file
> > +
> > +blob
> > +mark :33
> > +data 51
> > +1
> > +2
> > +3
> > +A
> > +A
> > +6
> > +7
> > +8
> > +9
> > +10
> > +BB
> > +12
> > +13
> > +14
> > +15
> > +16
> > +17
> > +18
> > +19
> > +20
> > +
> > +commit refs/heads/changed
> > +mark :34
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
> > +data 8
> > +s/11/B/
> > +from :32
> > +M 100644 :33 file
> > +
> > +blob
> > +mark :35
> > +data 50
> > +1
> > +2
> > +3
> > +A
> > +A
> > +6
> > +7
> > +8
> > +9
> > +10
> > +BB
> > +B
> > +13
> > +14
> > +15
> > +16
> > +17
> > +18
> > +19
> > +20
> > +
> > +commit refs/heads/changed
> > +mark :36
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
> > +data 8
> > +s/12/B/
> > +from :34
> > +M 100644 :35 file
> > +
> > +commit refs/heads/changed-message
> > +mark :37
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200
> > +data 7
> > +s/5/A/
> > +from :2
> > +M 100644 :3 file
> > +
> > +commit refs/heads/changed-message
> > +mark :38
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200
> > +data 35
> > +s/4/A/
> > +
> > +Also a silly comment here!
> > +from :37
> > +M 100644 :5 file
> > +
> > +commit refs/heads/changed-message
> > +mark :39
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200
> > +data 8
> > +s/11/B/
> > +from :38
> > +M 100644 :7 file
> > +
> > +commit refs/heads/changed-message
> > +mark :40
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200
> > +data 8
> > +s/12/B/
> > +from :39
> > +M 100644 :9 file
> > +
> > +commit refs/heads/unmodified
> > +mark :41
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200
> > +data 7
> > +s/5/A/
> > +from :2
> > +M 100644 :3 file
> > +
> > +commit refs/heads/unmodified
> > +mark :42
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200
> > +data 7
> > +s/4/A/
> > +from :41
> > +M 100644 :5 file
> > +
> > +commit refs/heads/unmodified
> > +mark :43
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200
> > +data 8
> > +s/11/B/
> > +from :42
> > +M 100644 :7 file
> > +
> > +commit refs/heads/unmodified
> > +mark :44
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200
> > +data 8
> > +s/12/B/
> > +from :43
> > +M 100644 :9 file
> > +
> > +commit refs/heads/removed
> > +mark :45
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
> > +data 7
> > +s/5/A/
> > +from :2
> > +M 100644 :3 file
> > +
> > +commit refs/heads/removed
> > +mark :46
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
> > +data 8
> > +s/11/B/
> > +from :45
> > +M 100644 :26 file
> > +
> > +commit refs/heads/removed
> > +mark :47
> > +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
> > +committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
> > +data 8
> > +s/12/B/
> > +from :46
> > +M 100644 :28 file
> > +
> > +reset refs/heads/removed
> > +from :47
> > +
> > -- 
> > gitgitgadget
> > 
> 
> 

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v6 11/21] range-diff: add tests
  2018-08-14 14:53               ` Johannes Schindelin
@ 2018-08-14 15:03                 ` Jeff King
  2018-08-14 15:06                   ` Jeff King
  2018-08-14 15:18                 ` Junio C Hamano
  1 sibling, 1 reply; 387+ messages in thread
From: Jeff King @ 2018-08-14 15:03 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Thomas Gummerer, Thomas Rast via GitGitGadget, git,
	Junio C Hamano, Thomas Rast

On Tue, Aug 14, 2018 at 04:53:51PM +0200, Johannes Schindelin wrote:

> > > These are essentially lifted from https://github.com/trast/tbdiff, with
> > > light touch-ups to account for the command now being named `git
> > > range-diff`.
> [...]
> > Just noticed while reading the whole series again (hopefully for the
> > last time :)), do we need Thomas Rast's Sign-off here, as he is
> > credited as the author here? 
> 
> Hmm. I hoped that my commit message was enough to indicate that while he
> is the author, I assembled this. Maybe I should move him to the footer, as
> an Original-Authored-By:?

I think the "Author" field is actually distinct from the copyright
provenance. In this case it ought to be perfectly fine to add your
signed-off-by under the DCO's point b:

  The contribution is based upon previous work that, to the best
  of my knowledge, is covered under an appropriate open source
  license and I have the right under that license to submit that
  work with modifications [...]

This is based on the tests in tbdiff, which is explicitly GPL'd by
Thomas. So your signoff certifies that, which is fine.

As for the author field, IMHO it serves two purposes:

  - to give credit where it is due

  - so that people digging in history know who to contact for
    questions/problems

In this case it probably makes sense for it to be you, as you'd take
responsibility for the code in _this_ project. And as you note, you can
give credit in the commit message (the only unfortunate thing is that
most automated statistics would not credit Thomas, but in theory they
could by mentioning him in the trailer).

-Peff

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v6 11/21] range-diff: add tests
  2018-08-14 15:03                 ` Jeff King
@ 2018-08-14 15:06                   ` Jeff King
  0 siblings, 0 replies; 387+ messages in thread
From: Jeff King @ 2018-08-14 15:06 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Thomas Gummerer, Thomas Rast via GitGitGadget, git,
	Junio C Hamano, Thomas Rast

On Tue, Aug 14, 2018 at 11:03:10AM -0400, Jeff King wrote:

> > Hmm. I hoped that my commit message was enough to indicate that while he
> > is the author, I assembled this. Maybe I should move him to the footer, as
> > an Original-Authored-By:?
> 
> I think the "Author" field is actually distinct from the copyright
> provenance. In this case it ought to be perfectly fine to add your
> signed-off-by under the DCO's point b:
> 
>   The contribution is based upon previous work that, to the best
>   of my knowledge, is covered under an appropriate open source
>   license and I have the right under that license to submit that
>   work with modifications [...]
> 
> This is based on the tests in tbdiff, which is explicitly GPL'd by
> Thomas. So your signoff certifies that, which is fine.
> 
> As for the author field, IMHO it serves two purposes:
> 
>   - to give credit where it is due
> 
>   - so that people digging in history know who to contact for
>     questions/problems
> 
> In this case it probably makes sense for it to be you, as you'd take
> responsibility for the code in _this_ project. And as you note, you can
> give credit in the commit message (the only unfortunate thing is that
> most automated statistics would not credit Thomas, but in theory they
> could by mentioning him in the trailer).

One thing I should have made clear: this is all my opinion, and anything
Thomas expresses trumps that. But since he hasn't been active lately,
this is all what I would do in the absence of input from him. Obviously
a sign-off from him is better than none. :)

-Peff

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v6 11/21] range-diff: add tests
  2018-08-14 14:53               ` Johannes Schindelin
  2018-08-14 15:03                 ` Jeff King
@ 2018-08-14 15:18                 ` Junio C Hamano
  1 sibling, 0 replies; 387+ messages in thread
From: Junio C Hamano @ 2018-08-14 15:18 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Thomas Gummerer, Thomas Rast via GitGitGadget, git, Thomas Rast

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> Hmm. I hoped that my commit message was enough to indicate that while he
> is the author, I assembled this. Maybe I should move him to the footer, as
> an Original-Authored-By:?
>
> Junio?

I think the log message gives a clear enough statement to credit the
original author.  Sign-off is not about credit, but is about making
sure we know the provenance of the contribution we would use in our
codebase, so it would be nice to have, but lifting code from another
project (i.e. TRast's tbdiff) that is appropriately licensed (GPLv2)
verbatim is something you can do with your own sign-off, without the
original author's sign-off, so I think what we have is good.

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v6 17/21] range-diff: populate the man page
  2018-08-13 11:33           ` [PATCH v6 17/21] range-diff: populate the man page Johannes Schindelin via GitGitGadget
@ 2018-09-09 11:14             ` Ævar Arnfjörð Bjarmason
  2018-09-09 16:54               ` SZEDER Gábor
  0 siblings, 1 reply; 387+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-09-09 11:14 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: git, Junio C Hamano, Johannes Schindelin, SZEDER Gábor


On Mon, Aug 13 2018, Johannes Schindelin via GitGitGadget wrote:

I realize this topic has long since landed, just seemed like a good
thing to reply to to ask this question:

> [...]
> +	( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
> [...]
> +<range1> <range2>::
> +	Compare the commits specified by the two ranges, where
> +	`<range1>` is considered an older version of `<range2>`.
> +
> +<rev1>...<rev2>::
> +	Equivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.
> +
> +<base> <rev1> <rev2>::
> +	Equivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.
> +	Note that `<base>` does not need to be the exact branch point
> +	of the branches. Example: after rebasing a branch `my-topic`,
> +	`git range-diff my-topic@{u} my-topic@{1} my-topic` would
> +	show the differences introduced by the rebase.

I find myself using range-diff often by watching forced pushes to public
repos to see what others are doing, e.g. just now:

     + 38b5f0fe72...718fbdedbc split-index-racy       -> szeder/split-index-racy  (forced update)

And then I turn that into:

    # @{u} because I happen to be on 'master' and it's shorter to type
    # than origin/master...
    git range-diff @{u} 38b5f0fe72...718fbdedbc

Only to get an error because it doesn't support that, but just:

    git range-diff @{u} 38b5f0fe72 718fbdedbc

I think it would be convenient given that "fetch" produces this output
to support this sort of invocation as synonymous with the three-arg
form. Then you can directly copy/paste that from terminals that have a
convenient feature to highlight a continuous \S+ reason to copy/paste
it.

I can patch it in, but maybe there's UI reasons not to do this that I'm
missing, e.g. confusion with the existing <rev1>...<rev2> syntax. What
do you think?

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v6 17/21] range-diff: populate the man page
  2018-09-09 11:14             ` Ævar Arnfjörð Bjarmason
@ 2018-09-09 16:54               ` SZEDER Gábor
  2018-09-09 17:19                 ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 387+ messages in thread
From: SZEDER Gábor @ 2018-09-09 16:54 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Johannes Schindelin via GitGitGadget, git, Junio C Hamano,
	Johannes Schindelin

On Sun, Sep 09, 2018 at 01:14:25PM +0200, Ævar Arnfjörð Bjarmason wrote:
> 
> On Mon, Aug 13 2018, Johannes Schindelin via GitGitGadget wrote:
> 
> I realize this topic has long since landed, just seemed like a good
> thing to reply to to ask this question:
> 
> > [...]
> > +	( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
> > [...]
> > +<range1> <range2>::
> > +	Compare the commits specified by the two ranges, where
> > +	`<range1>` is considered an older version of `<range2>`.
> > +
> > +<rev1>...<rev2>::
> > +	Equivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.
> > +
> > +<base> <rev1> <rev2>::
> > +	Equivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.
> > +	Note that `<base>` does not need to be the exact branch point
> > +	of the branches. Example: after rebasing a branch `my-topic`,
> > +	`git range-diff my-topic@{u} my-topic@{1} my-topic` would
> > +	show the differences introduced by the rebase.
> 
> I find myself using range-diff often by watching forced pushes to public
> repos to see what others are doing, e.g. just now:
> 
>      + 38b5f0fe72...718fbdedbc split-index-racy       -> szeder/split-index-racy  (forced update)

Heh, spying on my wip bugfixes :)

> And then I turn that into:
> 
>     # @{u} because I happen to be on 'master' and it's shorter to type
>     # than origin/master...
>     git range-diff @{u} 38b5f0fe72...718fbdedbc

I don't understand what you want with that @{u} or 'origin/master' in
the first place.  It's unnecessary, the three-dot notation on its own
works just fine.


> Only to get an error because it doesn't support that, but just:
> 
>     git range-diff @{u} 38b5f0fe72 718fbdedbc
> 
> I think it would be convenient given that "fetch" produces this output
> to support this sort of invocation as synonymous with the three-arg
> form. Then you can directly copy/paste that from terminals that have a
> convenient feature to highlight a continuous \S+ reason to copy/paste
> it.
> 
> I can patch it in, but maybe there's UI reasons not to do this that I'm
> missing, e.g. confusion with the existing <rev1>...<rev2> syntax. What
> do you think?

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v6 17/21] range-diff: populate the man page
  2018-09-09 16:54               ` SZEDER Gábor
@ 2018-09-09 17:19                 ` Ævar Arnfjörð Bjarmason
  2018-09-10 13:37                   ` Jeff King
  2018-09-10 17:17                   ` Junio C Hamano
  0 siblings, 2 replies; 387+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2018-09-09 17:19 UTC (permalink / raw)
  To: SZEDER Gábor
  Cc: Johannes Schindelin via GitGitGadget, git, Junio C Hamano,
	Johannes Schindelin


On Sun, Sep 09 2018, SZEDER Gábor wrote:

> On Sun, Sep 09, 2018 at 01:14:25PM +0200, Ævar Arnfjörð Bjarmason wrote:
>>
>> On Mon, Aug 13 2018, Johannes Schindelin via GitGitGadget wrote:
>>
>> I realize this topic has long since landed, just seemed like a good
>> thing to reply to to ask this question:
>>
>> > [...]
>> > +	( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
>> > [...]
>> > +<range1> <range2>::
>> > +	Compare the commits specified by the two ranges, where
>> > +	`<range1>` is considered an older version of `<range2>`.
>> > +
>> > +<rev1>...<rev2>::
>> > +	Equivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.
>> > +
>> > +<base> <rev1> <rev2>::
>> > +	Equivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.
>> > +	Note that `<base>` does not need to be the exact branch point
>> > +	of the branches. Example: after rebasing a branch `my-topic`,
>> > +	`git range-diff my-topic@{u} my-topic@{1} my-topic` would
>> > +	show the differences introduced by the rebase.
>>
>> I find myself using range-diff often by watching forced pushes to public
>> repos to see what others are doing, e.g. just now:
>>
>>      + 38b5f0fe72...718fbdedbc split-index-racy       -> szeder/split-index-racy  (forced update)
>
> Heh, spying on my wip bugfixes :)
>
>> And then I turn that into:
>>
>>     # @{u} because I happen to be on 'master' and it's shorter to type
>>     # than origin/master...
>>     git range-diff @{u} 38b5f0fe72...718fbdedbc
>
> I don't understand what you want with that @{u} or 'origin/master' in
> the first place.  It's unnecessary, the three-dot notation on its own
> works just fine.

Maybe I've been using the wrong mode all along, I passed over by habits
from tbdiff, which were surely copy/pasted from somewhere.

Looking at the git-range-diff manpage though it recommends <base> <rev1>
<rev2> over <rev1>...<rev2> when the topic has been rebased, which is
usually the case for e.g. a topic that's submitted to git.git (usually
be the time feedback has been gathered & a re-submission has been made
Junio has pushed another "master").

So isn't "<base> <rev1> <rev2>" the right thing to use over
"<rev1>...<rev2>" for git.git use? I think so, but I'm not sure.

In any case, there are going to be those use-case where you should be
using "<base> <rev1> <rev2>", and a rebase will be propagated by a
force-push, so I thought it made sense that range-diff could directly
consume the output of "fetch" in that case...

>> Only to get an error because it doesn't support that, but just:
>>
>>     git range-diff @{u} 38b5f0fe72 718fbdedbc
>>
>> I think it would be convenient given that "fetch" produces this output
>> to support this sort of invocation as synonymous with the three-arg
>> form. Then you can directly copy/paste that from terminals that have a
>> convenient feature to highlight a continuous \S+ reason to copy/paste
>> it.
>>
>> I can patch it in, but maybe there's UI reasons not to do this that I'm
>> missing, e.g. confusion with the existing <rev1>...<rev2> syntax. What
>> do you think?

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v6 17/21] range-diff: populate the man page
  2018-09-09 17:19                 ` Ævar Arnfjörð Bjarmason
@ 2018-09-10 13:37                   ` Jeff King
  2018-10-02 15:06                     ` Johannes Schindelin
  2018-09-10 17:17                   ` Junio C Hamano
  1 sibling, 1 reply; 387+ messages in thread
From: Jeff King @ 2018-09-10 13:37 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: SZEDER Gábor, Johannes Schindelin via GitGitGadget, git,
	Junio C Hamano, Johannes Schindelin

On Sun, Sep 09, 2018 at 07:19:51PM +0200, Ævar Arnfjörð Bjarmason wrote:

> >> And then I turn that into:
> >>
> >>     # @{u} because I happen to be on 'master' and it's shorter to type
> >>     # than origin/master...
> >>     git range-diff @{u} 38b5f0fe72...718fbdedbc
> >
> > I don't understand what you want with that @{u} or 'origin/master' in
> > the first place.  It's unnecessary, the three-dot notation on its own
> > works just fine.
> 
> Maybe I've been using the wrong mode all along, I passed over by habits
> from tbdiff, which were surely copy/pasted from somewhere.
> 
> Looking at the git-range-diff manpage though it recommends <base> <rev1>
> <rev2> over <rev1>...<rev2> when the topic has been rebased, which is
> usually the case for e.g. a topic that's submitted to git.git (usually
> be the time feedback has been gathered & a re-submission has been made
> Junio has pushed another "master").
> 
> So isn't "<base> <rev1> <rev2>" the right thing to use over
> "<rev1>...<rev2>" for git.git use? I think so, but I'm not sure.

The problem with <rev1>...<rev2> is that it finds the actual merge base,
not the beginning of the topic. So if you have a 5-patch topic, but the
first two patches weren't changed in the rebase, it won't show them at
all!  I made this mistake in [1], for example.

For a force-push, though, you may not care about seeing the topic as a
whole, and that mid-topic merge-base could be just fine. So pasting just
the "A...B" works.

I don't think your "@{u} A...B" makes any sense. You're giving _two_
bases, which is weird. But even if you wanted to ignore the "..." base
as a convenience to users of fetch, @{u} does not necessarily have
anything to do with the @{upstream} of the topic at "A". You really want
branch@{u}, which is on a separate part of the fetch output line (and
your branch@{u} and the remote's are not necessarily the same, either;
in this case you probably do not even have that branch checked out).

-Peff

[1] https://public-inbox.org/git/20180821195102.GB859@sigill.intra.peff.net/

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v6 17/21] range-diff: populate the man page
  2018-09-09 17:19                 ` Ævar Arnfjörð Bjarmason
  2018-09-10 13:37                   ` Jeff King
@ 2018-09-10 17:17                   ` Junio C Hamano
  1 sibling, 0 replies; 387+ messages in thread
From: Junio C Hamano @ 2018-09-10 17:17 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: SZEDER Gábor, Johannes Schindelin via GitGitGadget, git,
	Johannes Schindelin

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> Looking at the git-range-diff manpage though it recommends <base> <rev1>
> <rev2> over <rev1>...<rev2> when the topic has been rebased, which is
> usually the case for e.g. a topic that's submitted to git.git (usually
> be the time feedback has been gathered & a re-submission has been made
> Junio has pushed another "master").
>
> So isn't "<base> <rev1> <rev2>" the right thing to use over
> "<rev1>...<rev2>" for git.git use? I think so, but I'm not sure.

If <rev2> is forked from different base than where <rev1> was
forked, then <base> <rev1> <rev2> would give you more sensible
range.  And such an update is inevitable when <rev2> must rely on
new things that recently appeared on <base> since <rev1> forked from
the mainline.  But otherwise <rev1>...<rev2> should work just fine.

> In any case, there are going to be those use-case where you should be
> using "<base> <rev1> <rev2>", and a rebase will be propagated by a
> force-push, so I thought it made sense that range-diff could directly
> consume the output of "fetch" in that case...

I am not absolutely sure if there is *more* useful interpretation
that "<base> <rev1>...<rev2>" may want to mean than to serve as a
synonym for "<base> <rev1> <rev2>" for those who are too lazy to
type.  But if there isn't, I'd say it is a reasonable synonym to
want.


^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v6 17/21] range-diff: populate the man page
  2018-09-10 13:37                   ` Jeff King
@ 2018-10-02 15:06                     ` Johannes Schindelin
  0 siblings, 0 replies; 387+ messages in thread
From: Johannes Schindelin @ 2018-10-02 15:06 UTC (permalink / raw)
  To: Jeff King
  Cc: Ævar Arnfjörð Bjarmason, SZEDER Gábor,
	Johannes Schindelin via GitGitGadget, git, Junio C Hamano

[-- Attachment #1: Type: text/plain, Size: 3618 bytes --]

Hi Peff,

On Mon, 10 Sep 2018, Jeff King wrote:

> On Sun, Sep 09, 2018 at 07:19:51PM +0200, Ævar Arnfjörð Bjarmason wrote:
> 
> > >> And then I turn that into:
> > >>
> > >>     # @{u} because I happen to be on 'master' and it's shorter to type
> > >>     # than origin/master...
> > >>     git range-diff @{u} 38b5f0fe72...718fbdedbc
> > >
> > > I don't understand what you want with that @{u} or 'origin/master' in
> > > the first place.  It's unnecessary, the three-dot notation on its own
> > > works just fine.
> > 
> > Maybe I've been using the wrong mode all along, I passed over by habits
> > from tbdiff, which were surely copy/pasted from somewhere.
> > 
> > Looking at the git-range-diff manpage though it recommends <base> <rev1>
> > <rev2> over <rev1>...<rev2> when the topic has been rebased, which is
> > usually the case for e.g. a topic that's submitted to git.git (usually
> > be the time feedback has been gathered & a re-submission has been made
> > Junio has pushed another "master").
> > 
> > So isn't "<base> <rev1> <rev2>" the right thing to use over
> > "<rev1>...<rev2>" for git.git use? I think so, but I'm not sure.
> 
> The problem with <rev1>...<rev2> is that it finds the actual merge base,
> not the beginning of the topic.

That is actually not true, not for `range-diff`. If it sees `A...B`, it
will automatically generate `B..A A..B` from it.

That matters if the branches `A` and `B` have multiple merge bases.

> So if you have a 5-patch topic, but the first two patches weren't
> changed in the rebase, it won't show them at all!  I made this mistake
> in [1], for example.

Yep, that is very easy to do.

Another thing to note is that often `A...B` is not doing the right thing
with branches that go into `pu` because some of us contributors rebase
to `master` (or `next`) between iterations. For such a use case, I
myself prefer the `@{u}` version that Ævar wants to use. (Although I
leave off the three dots, in which case everything works quite
magically.)

> For a force-push, though, you may not care about seeing the topic as a
> whole, and that mid-topic merge-base could be just fine. So pasting just
> the "A...B" works.
> 
> I don't think your "@{u} A...B" makes any sense. You're giving _two_
> bases, which is weird. But even if you wanted to ignore the "..." base
> as a convenience to users of fetch, @{u} does not necessarily have
> anything to do with the @{upstream} of the topic at "A". You really want
> branch@{u}, which is on a separate part of the fetch output line (and
> your branch@{u} and the remote's are not necessarily the same, either;
> in this case you probably do not even have that branch checked out).

While `@{u}` in general does not relate to `A` nor `B`, it is quite
possible that it always does in Ævar's scenario. I would not want to
limit them in how they want to use Git from this point of view.

However, I would have a little bit of a problem with special-casing the
two-arg version when there are no dots in the first arg, and three dots
in the second one.

The problem here: the two-arg version already has a meaning: two commit
ranges. And it *is* conceivable that somebody wants to compare, say, the
full history of `git-gui.git` with a certain symmetric range in `pu`.
Granted, that is very obscure a use case, but it would be hard to
explain why the two-arg case refers to two commit ranges in some cases,
and in other cases not.

Ciao,
Dscho

> 
> -Peff
> 
> [1] https://public-inbox.org/git/20180821195102.GB859@sigill.intra.peff.net/
> 

^ permalink raw reply	[flat|nested] 387+ messages in thread

* Re: [PATCH v6 03/21] range-diff: first rudimentary implementation
  2018-08-13 11:33           ` [PATCH v6 03/21] range-diff: first rudimentary implementation Johannes Schindelin via GitGitGadget
@ 2019-03-05  6:29             ` Junio C Hamano
  0 siblings, 0 replies; 387+ messages in thread
From: Junio C Hamano @ 2019-03-05  6:29 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget; +Cc: git, Johannes Schindelin

"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:

> +		else if (!line.buf[0] || starts_with(line.buf, "index "))
> +			/*
> +			 * A completely blank (not ' \n', which is context)
> +			 * line is not valid in a diff.  We skip it

I noticed this while wondering how somebody could teach range-diff
to honor --notes=amlog while preparing the patches to be compared
[*1*], but this assumption goes against what POSIX.1 says these
days.

    It is implementation-defined whether an empty unaffected line is
    written as an empty line or a line containing a single <space> character.

cf. http://pubs.opengroup.org/onlinepubs/9699919799/utilities/diff.html#tag_20_34_10_07

We need to insert ", as we disable user's diff.suppressBlankEmpty
settings" before ".  We skip it" (and if we get affected by the
setting, we need to fix it; it is not ultra-urgent, though).

[Footnote]

*1* ... which I do not have a good answer to, yet.  As discussed
earlier, the diffopt passed into the show_range_diff() machinery is
primarily meant for the final output (i.e. how the matching patches
from the two iterations are compared) and not about how the patches
to be compared are generated.  Worse, --notes=amlog (and possibly
other useful options) are parsed by "git log" side of the machinery,
not "git diff" side that populates diffopt.

^ permalink raw reply	[flat|nested] 387+ messages in thread

end of thread, other threads:[~2019-03-05  6:29 UTC | newest]

Thread overview: 387+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-05-03 15:30 [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Johannes Schindelin
2018-05-03 15:30 ` [PATCH 01/18] Add a function to solve least-cost assignment problems Johannes Schindelin
2018-05-13 18:19   ` Duy Nguyen
2018-05-21  9:52     ` Johannes Schindelin
2018-05-03 15:30 ` [PATCH 02/18] Add a new builtin: branch-diff Johannes Schindelin
2018-05-03 16:10   ` Ramsay Jones
2018-05-03 20:25     ` Johannes Schindelin
2018-05-03 23:20       ` Ramsay Jones
2018-05-04  6:40         ` Johannes Schindelin
2018-05-04 15:37           ` Ramsay Jones
2018-05-05 19:41             ` Johannes Schindelin
2018-05-09 16:24               ` Ramsay Jones
2018-06-01  8:23                 ` Johannes Schindelin
2018-05-04 16:34           ` Elijah Newren
2018-05-05 20:24             ` Johannes Schindelin
2018-05-03 16:41   ` Duy Nguyen
2018-05-03 20:30     ` Johannes Schindelin
2018-05-03 20:32       ` Johannes Schindelin
2018-05-04  5:15         ` Duy Nguyen
2018-05-04  7:23           ` Johannes Schindelin
2018-05-04 14:44             ` Duy Nguyen
2018-05-04 15:17               ` Duy Nguyen
2018-05-04 15:23               ` Johannes Schindelin
2018-05-04 15:29                 ` Duy Nguyen
2018-05-03 16:43   ` Stefan Beller
2018-05-03 20:42     ` Johannes Schindelin
2018-05-03 21:12       ` Stefan Beller
2018-05-03 21:49         ` Johannes Schindelin
2018-05-04  3:23           ` Junio C Hamano
2018-05-04  2:35   ` Eric Sunshine
2018-05-04  6:52     ` Johannes Schindelin
2018-05-04  7:27       ` Eric Sunshine
2018-05-03 15:30 ` [PATCH 03/18] branch-diff: first rudimentary implementation Johannes Schindelin
2018-05-03 16:30   ` Ramsay Jones
2018-05-03 20:44     ` Johannes Schindelin
2018-05-03 17:06   ` Stefan Beller
2018-05-03 21:01     ` Johannes Schindelin
2018-05-03 21:19       ` Stefan Beller
2018-05-03 22:00         ` Johannes Schindelin
2018-05-04  2:35   ` Eric Sunshine
2018-05-04  7:03     ` Johannes Schindelin
2018-05-04  4:56   ` Junio C Hamano
2018-05-04  7:18     ` Johannes Schindelin
2018-05-03 15:30 ` [PATCH 04/18] branch-diff: improve the order of the shown commits Johannes Schindelin
2018-05-03 15:30 ` [PATCH 05/18] branch-diff: also show the diff between patches Johannes Schindelin
2018-05-04  2:51   ` Eric Sunshine
2018-05-04  3:15     ` Eric Sunshine
2018-05-04  7:15     ` Johannes Schindelin
2018-05-03 15:30 ` [PATCH 06/18] branch-diff: right-trim commit messages Johannes Schindelin
2018-05-03 15:30 ` [PATCH 07/18] branch-diff: indent the diffs just like tbdiff Johannes Schindelin
2018-05-03 15:30 ` [PATCH 08/18] branch-diff: suppress the diff headers Johannes Schindelin
2018-05-03 15:30 ` [PATCH 09/18] branch-diff: adjust the output of the commit pairs Johannes Schindelin
2018-05-03 15:30 ` [PATCH 10/18] branch-diff: do not show "function names" in hunk headers Johannes Schindelin
2018-05-03 15:30 ` [PATCH 11/18] branch-diff: add tests Johannes Schindelin
2018-05-03 16:56   ` Ævar Arnfjörð Bjarmason
2018-05-03 21:03     ` Johannes Schindelin
2018-05-03 17:11   ` Stefan Beller
2018-05-03 21:05     ` Johannes Schindelin
2018-05-03 23:27   ` Philip Oakley
2018-05-04  6:42     ` Johannes Schindelin
2018-05-03 15:30 ` [PATCH 12/18] branch-diff: use color for the commit pairs Johannes Schindelin
2018-05-03 15:30 ` [PATCH 13/18] color: provide inverted colors, too Johannes Schindelin
2018-05-03 15:30 ` [PATCH 14/18] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin
2018-05-03 15:31 ` [PATCH 15/18] branch-diff: offer to dual-color the diffs Johannes Schindelin
2018-05-03 15:31 ` [PATCH 16/18] branch-diff --dual-color: work around bogus white-space warning Johannes Schindelin
2018-05-03 15:31 ` [PATCH 17/18] branch-diff: add a man page Johannes Schindelin
2018-05-04  3:27   ` Eric Sunshine
2018-05-04  7:17     ` Johannes Schindelin
2018-05-03 15:31 ` [PATCH 18/18] completion: support branch-diff Johannes Schindelin
2018-05-03 18:05 ` [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Ævar Arnfjörð Bjarmason
2018-05-03 21:07   ` Johannes Schindelin
2018-05-03 21:50   ` Jacob Keller
2018-05-04  5:24 ` Junio C Hamano
2018-05-04  7:24   ` Johannes Schindelin
2018-05-04 15:34 ` [PATCH v2 " Johannes Schindelin
2018-05-04 15:34   ` [PATCH v2 01/18] Add a function to solve least-cost assignment problems Johannes Schindelin
2018-05-05 18:24     ` Jeff King
2018-05-05 21:55       ` Johannes Schindelin
2018-05-30 13:55     ` SZEDER Gábor
2018-05-30 16:14       ` Stefan Beller
2018-05-30 23:28         ` brian m. carlson
2018-05-31 12:19           ` Johannes Schindelin
2018-05-04 15:34   ` [PATCH v2 02/18] Add a new builtin: branch-diff Johannes Schindelin
2018-05-05 18:26     ` Jeff King
2018-05-05 21:57       ` Johannes Schindelin
2018-05-06  0:25         ` Todd Zullinger
2018-05-06  0:38           ` Todd Zullinger
2018-05-06 12:04             ` Johannes Schindelin
2018-05-06  1:05         ` Igor Djordjevic
2018-05-06  4:53           ` Jacob Keller
2018-05-06  8:32             ` Duy Nguyen
2018-05-06 12:08               ` Johannes Schindelin
2018-05-06 12:10           ` Johannes Schindelin
2018-05-06 13:37             ` Igor Djordjevic
2018-05-07  1:34               ` Johannes Schindelin
2018-05-07 22:05                 ` Igor Djordjevic
2018-05-07 22:24                   ` Stefan Beller
2018-05-07 23:39                     ` Igor Djordjevic
2018-05-08  3:44                     ` Jeff King
2018-05-08  3:48                       ` Jeff King
2018-05-22 11:38                       ` Ævar Arnfjörð Bjarmason
2018-05-25 22:06                         ` Stefan Beller
     [not found]                           ` <CAA8fPEkNjy+ETz4Mx+C2kUfLjLzR9uuOmO3GfN48ZH1SwyfE1A@mail.gmail.com>
2018-05-26  6:15                             ` Fwd: " Øyvind Rønningstad
2018-06-01  8:15                             ` Johannes Schindelin
2018-05-06  2:33         ` Junio C Hamano
2018-05-06 12:21           ` Johannes Schindelin
2018-05-06 20:51             ` Eric Sunshine
2018-05-07  2:04               ` Johannes Schindelin
2018-05-07  7:48                 ` Jeff King
2018-05-07 21:33                   ` Igor Djordjevic
2018-05-21 10:33                     ` Johannes Schindelin
2018-05-21 17:56                       ` Stefan Beller
2018-05-21 20:24                         ` Jeff King
2018-05-21 21:40                           ` Brandon Williams
2018-05-21 21:48                             ` Stefan Beller
2018-05-21 21:52                             ` Jeff King
2018-05-22  2:08                               ` Junio C Hamano
2018-05-08  0:30                   ` Junio C Hamano
2018-05-07  1:45             ` Junio C Hamano
2018-05-07  5:39               ` Johannes Schindelin
2018-05-07 15:12                 ` Junio C Hamano
2018-05-21 10:41                   ` Johannes Schindelin
2018-05-07  7:50         ` Jeff King
2018-05-07 15:28           ` Duy Nguyen
2018-05-07 19:58             ` Stefan Beller
2018-05-04 15:34   ` [PATCH v2 03/18] branch-diff: first rudimentary implementation Johannes Schindelin
2018-05-04 15:34   ` [PATCH v2 04/18] branch-diff: improve the order of the shown commits Johannes Schindelin
2018-05-04 15:34   ` [PATCH v2 05/18] branch-diff: also show the diff between patches Johannes Schindelin
2018-05-06  1:14     ` Igor Djordjevic
2018-05-06 12:18       ` Johannes Schindelin
2018-05-04 15:34   ` [PATCH v2 06/18] branch-diff: right-trim commit messages Johannes Schindelin
2018-05-04 15:34   ` [PATCH v2 07/18] branch-diff: indent the diffs just like tbdiff Johannes Schindelin
2018-05-06 14:15     ` Martin Ågren
2018-05-07  1:54       ` Johannes Schindelin
2018-05-04 15:34   ` [PATCH v2 08/18] branch-diff: suppress the diff headers Johannes Schindelin
2018-05-04 15:34   ` [PATCH v2 09/18] branch-diff: adjust the output of the commit pairs Johannes Schindelin
2018-05-04 16:25     ` Elijah Newren
2018-05-04 15:34   ` [PATCH v2 10/18] branch-diff: do not show "function names" in hunk headers Johannes Schindelin
2018-05-04 15:34   ` [PATCH v2 11/18] branch-diff: add tests Johannes Schindelin
2018-05-04 15:34   ` [PATCH v2 12/18] branch-diff: use color for the commit pairs Johannes Schindelin
2018-05-05 23:48     ` Todd Zullinger
2018-05-07  1:52       ` Johannes Schindelin
2018-05-08  2:10         ` Todd Zullinger
2018-06-01  8:17           ` Johannes Schindelin
2018-05-04 15:34   ` [PATCH v2 13/18] color: provide inverted colors, too Johannes Schindelin
2018-05-05 18:29     ` Jeff King
2018-05-05 22:03       ` Johannes Schindelin
2018-05-06  6:35         ` Jeff King
2018-05-06  6:41           ` Jeff King
2018-05-07  1:20             ` Johannes Schindelin
2018-05-07  7:37               ` Jeff King
2018-05-07  1:35             ` Junio C Hamano
2018-05-07  5:38               ` Johannes Schindelin
2018-05-07  7:40               ` Jeff King
2018-05-04 15:34   ` [PATCH v2 14/18] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin
2018-05-04 15:35   ` [PATCH v2 15/18] branch-diff: offer to dual-color the diffs Johannes Schindelin
2018-05-04 15:35   ` [PATCH v2 16/18] branch-diff --dual-color: work around bogus white-space warning Johannes Schindelin
2018-05-04 15:35   ` [PATCH v2 17/18] branch-diff: add a man page Johannes Schindelin
2018-05-04 15:35   ` [PATCH v2 18/18] completion: support branch-diff Johannes Schindelin
2018-05-06  8:24     ` Duy Nguyen
2018-05-07  1:23       ` Johannes Schindelin
2018-05-04 16:21   ` [PATCH v2 00/18] Add `branch-diff`, a `tbdiff` lookalike Elijah Newren
2018-05-04 16:30     ` Elijah Newren
2018-05-05 20:03     ` Johannes Schindelin
2018-05-07 17:07       ` Elijah Newren
2018-05-07 17:50         ` SZEDER Gábor
2018-05-07 18:38           ` Elijah Newren
2018-05-06  5:22   ` Junio C Hamano
2018-05-06 12:23     ` Johannes Schindelin
2018-05-06 22:56   ` brian m. carlson
2018-05-07  2:05     ` Johannes Schindelin
2018-07-03 11:26   ` [PATCH v3 00/20] Add `range-diff`, " Johannes Schindelin via GitGitGadget
2018-04-30 21:54     ` [PATCH v3 01/20] linear-assignment: a function to solve least-cost assignment problems Johannes Schindelin via GitGitGadget
2018-07-06 22:43       ` Junio C Hamano
2018-07-07 11:34         ` Johannes Schindelin
2018-07-07 16:34           ` Junio C Hamano
2018-07-07 19:27             ` Johannes Schindelin
2018-07-07 22:23               ` Johannes Schindelin
2018-07-09 22:08                 ` refs/notes/amlog problems, was " Johannes Schindelin
2018-07-11 16:12                   ` Junio C Hamano
2018-07-12 15:23                     ` Johannes Schindelin
2018-07-12 16:59                       ` Junio C Hamano
2018-07-19 17:06                         ` Junio C Hamano
2018-07-20 18:51                           ` Johannes Schindelin
2018-07-20 19:34                             ` Junio C Hamano
2018-07-20 21:20                               ` Stefan Beller
2018-07-20 21:24                                 ` Junio C Hamano
     [not found]                                   ` <CAPc5daW-KoyUX3i7M5YbdQC2mFKAmVBS42-XT84hpm30VFcZ1g@mail.gmail.com>
2018-07-20 21:30                                     ` Stefan Beller
2018-07-21 22:02                                       ` Johannes Schindelin
2018-07-21 21:56                               ` Johannes Schindelin
2018-07-23  1:25                                 ` Jeff King
2018-07-24  1:50                                   ` Junio C Hamano
2018-07-24  9:45                                     ` Jeff King
2018-07-09 22:23                 ` Junio C Hamano
2018-07-10 10:47                   ` refs/notes/amlog woes, was " Johannes Schindelin
2018-07-11 10:07       ` SZEDER Gábor
2018-07-12 15:11         ` Johannes Schindelin
2018-05-01 19:42     ` [PATCH v3 02/20] Introduce `range-diff` to compare iterations of a topic branch Johannes Schindelin via GitGitGadget
2018-05-02  0:34     ` [PATCH v3 03/20] range-diff: first rudimentary implementation Johannes Schindelin via GitGitGadget
2018-07-16  6:55       ` Eric Sunshine
2018-07-17  9:53         ` Johannes Schindelin
2018-05-02 10:22     ` [PATCH v3 04/20] range-diff: improve the order of the shown commits Johannes Schindelin via GitGitGadget
2018-05-02 14:49     ` [PATCH v3 06/20] range-diff: right-trim commit messages Johannes Schindelin via GitGitGadget
2018-05-02 14:52     ` [PATCH v3 07/20] range-diff: indent the diffs just like tbdiff Johannes Schindelin via GitGitGadget
2018-05-02 14:53     ` [PATCH v3 08/20] range-diff: suppress the diff headers Johannes Schindelin via GitGitGadget
2018-05-02 15:19     ` [PATCH v3 11/20] range-diff: add tests Thomas Rast via GitGitGadget
2018-07-16  7:28       ` Eric Sunshine
2018-07-17 16:28         ` Johannes Schindelin
2018-05-02 21:35     ` [PATCH v3 09/20] range-diff: adjust the output of the commit pairs Johannes Schindelin via GitGitGadget
2018-07-16  7:21       ` Eric Sunshine
2018-07-17 16:24         ` Johannes Schindelin
2018-07-17 17:47           ` Stefan Beller
2018-07-20 18:57             ` Johannes Schindelin
2018-07-20 19:16               ` Stefan Beller
2018-07-21 22:07                 ` Johannes Schindelin
2018-05-02 23:32     ` [PATCH v3 12/20] range-diff: use color for " Johannes Schindelin via GitGitGadget
2018-05-03  0:14     ` [PATCH v3 13/20] color: add the meta color GIT_COLOR_REVERSE Johannes Schindelin via GitGitGadget
2018-05-03  0:17     ` [PATCH v3 14/20] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin via GitGitGadget
2018-07-09 19:29       ` Stefan Beller
2018-07-10 17:45         ` [PATCH 0/2] " Stefan Beller
2018-07-10 17:45           ` [PATCH 1/2] diff.c: convert emit_line_ws_markup to take string for sign Stefan Beller
2018-07-10 17:45           ` [PATCH 2/2] WIP diff.c: clarify emit_line_0 Stefan Beller
2018-07-10 19:58             ` [PATCH 1/2] diff.c: convert emit_line_ws_markup to take string for sign Stefan Beller
2018-07-10 19:59             ` [PATCH] diff.c: clarify emit_line_0 Stefan Beller
2018-07-10 21:54               ` [PATCH] ws: do not reset and set color twice Stefan Beller
2018-07-21 21:13           ` [PATCH 0/2] Re: [PATCH v3 14/20] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin
2018-05-03  1:01     ` [PATCH v3 15/20] range-diff: offer to dual-color the diffs Johannes Schindelin via GitGitGadget
2018-05-03  1:11     ` [PATCH v3 16/20] range-diff --dual-color: work around bogus white-space warning Johannes Schindelin via GitGitGadget
2018-07-09 19:34       ` Stefan Beller
2018-07-09 21:02         ` Junio C Hamano
2018-07-10 10:08           ` Johannes Schindelin
2018-07-10 15:50             ` Junio C Hamano
2018-07-10 16:32             ` Stefan Beller
2018-07-21 21:44               ` Johannes Schindelin
2018-05-03 13:50     ` [PATCH v3 17/20] range-diff: add a man page Johannes Schindelin via GitGitGadget
2018-07-09 18:20       ` Stefan Beller
2018-07-09 20:00         ` Johannes Schindelin
2018-07-09 20:25           ` Stefan Beller
2018-07-09 20:38             ` Johannes Schindelin
2018-07-16  8:01       ` Eric Sunshine
2018-07-17 16:39         ` Johannes Schindelin
2018-05-03 14:44     ` [PATCH v3 18/20] completion: support `git range-diff` Johannes Schindelin via GitGitGadget
2018-07-06 22:46       ` Junio C Hamano
2018-07-07 11:38         ` Johannes Schindelin
2018-05-05 19:52     ` [PATCH v3 19/20] range-diff: left-pad patch numbers Johannes Schindelin via GitGitGadget
2018-05-06 15:26     ` [PATCH v3 05/20] range-diff: also show the diff between patches Johannes Schindelin via GitGitGadget
2018-05-06 15:35     ` [PATCH v3 10/20] range-diff: do not show "function names" in hunk headers Johannes Schindelin via GitGitGadget
2018-06-30 20:41     ` [PATCH v3 20/20] range-diff: make --dual-color the default mode Johannes Schindelin via GitGitGadget
2018-07-16  8:06       ` Eric Sunshine
2018-07-17 16:40         ` Johannes Schindelin
2018-07-21 22:04     ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Johannes Schindelin via GitGitGadget
2018-07-21 22:04       ` [PATCH v4 01/21] linear-assignment: a function to solve least-cost assignment problems Johannes Schindelin via GitGitGadget
2018-07-28  8:46         ` Thomas Gummerer
2018-07-30 15:59           ` Johannes Schindelin
2018-07-21 22:04       ` [PATCH v4 02/21] Introduce `range-diff` to compare iterations of a topic branch Johannes Schindelin via GitGitGadget
2018-07-21 22:04       ` [PATCH v4 03/21] range-diff: first rudimentary implementation Johannes Schindelin via GitGitGadget
2018-07-29 18:36         ` Thomas Gummerer
2018-07-30 16:21           ` Johannes Schindelin
2018-07-30 21:16             ` Thomas Gummerer
2018-08-10 20:50               ` Johannes Schindelin
2018-07-21 22:04       ` [PATCH v4 04/21] range-diff: improve the order of the shown commits Johannes Schindelin via GitGitGadget
2018-07-21 22:04       ` [PATCH v4 05/21] range-diff: also show the diff between patches Johannes Schindelin via GitGitGadget
2018-07-29 19:03         ` Thomas Gummerer
2018-07-29 19:22           ` Eric Sunshine
2018-07-29 21:45             ` Thomas Gummerer
2018-07-30 16:28               ` Johannes Schindelin
2018-07-30 21:26                 ` Thomas Gummerer
2018-07-30 21:51                   ` Eric Sunshine
2018-08-10 21:12                     ` Johannes Schindelin
2018-08-10 21:31                       ` Eric Sunshine
2018-08-10 22:02                         ` Johannes Schindelin
2018-08-10 20:36                   ` Johannes Schindelin
2018-07-21 22:04       ` [PATCH v4 06/21] range-diff: right-trim commit messages Johannes Schindelin via GitGitGadget
2018-07-21 22:04       ` [PATCH v4 07/21] range-diff: indent the diffs just like tbdiff Johannes Schindelin via GitGitGadget
2018-07-21 22:04       ` [PATCH v4 08/21] range-diff: suppress the diff headers Johannes Schindelin via GitGitGadget
2018-07-21 22:04       ` [PATCH v4 09/21] range-diff: adjust the output of the commit pairs Johannes Schindelin via GitGitGadget
2018-07-29 19:38         ` Thomas Gummerer
2018-08-10 21:01           ` Johannes Schindelin
2018-07-29 21:28         ` Thomas Gummerer
2018-07-21 22:04       ` [PATCH v4 10/21] range-diff: do not show "function names" in hunk headers Johannes Schindelin via GitGitGadget
2018-07-29 20:52         ` Thomas Gummerer
2018-08-10 21:03           ` Johannes Schindelin
2018-07-21 22:05       ` [PATCH v4 11/21] range-diff: add tests Thomas Rast via GitGitGadget
2018-07-22  5:04         ` Eric Sunshine
2018-07-30 16:30           ` Johannes Schindelin
2018-07-30 20:18             ` Junio C Hamano
2018-07-30 23:40               ` Stefan Beller
2018-07-31 15:19                 ` Junio C Hamano
2018-07-23 21:25         ` Stefan Beller
2018-07-21 22:05       ` [PATCH v4 12/21] range-diff: use color for the commit pairs Johannes Schindelin via GitGitGadget
2018-07-21 22:05       ` [PATCH v4 13/21] color: add the meta color GIT_COLOR_REVERSE Johannes Schindelin via GitGitGadget
2018-07-21 22:05       ` [PATCH v4 14/21] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin via GitGitGadget
2018-07-23 22:27         ` Junio C Hamano
2018-07-23 22:48           ` Stefan Beller
2018-07-21 22:05       ` [PATCH v4 15/21] range-diff: offer to dual-color the diffs Johannes Schindelin via GitGitGadget
2018-07-21 22:05       ` [PATCH v4 16/21] range-diff --dual-color: fix bogus white-space warning Johannes Schindelin via GitGitGadget
2018-07-23 22:20         ` Stefan Beller
2018-08-10 21:05           ` Johannes Schindelin
2018-07-23 22:39         ` Junio C Hamano
2018-07-24  1:27           ` Junio C Hamano
2018-07-21 22:05       ` [PATCH v4 17/21] range-diff: populate the man page Johannes Schindelin via GitGitGadget
2018-07-29 21:23         ` Thomas Gummerer
2018-08-10 21:06           ` Johannes Schindelin
2018-07-21 22:05       ` [PATCH v4 18/21] completion: support `git range-diff` Johannes Schindelin via GitGitGadget
2018-07-22  5:49         ` Eric Sunshine
2018-08-10 20:24           ` Johannes Schindelin
2018-07-21 22:05       ` [PATCH v4 19/21] range-diff: left-pad patch numbers Johannes Schindelin via GitGitGadget
2018-07-21 22:05       ` [PATCH v4 20/21] range-diff: make --dual-color the default mode Johannes Schindelin via GitGitGadget
2018-07-29 21:33         ` Thomas Gummerer
2018-08-10 21:07           ` Johannes Schindelin
2018-07-21 22:05       ` [PATCH v4 21/21] range-diff: use dim/bold cues to improve dual color mode Johannes Schindelin via GitGitGadget
2018-07-23 21:03       ` [PATCH v4 00/21] Add `range-diff`, a `tbdiff` lookalike Stefan Beller
2018-07-23 21:49         ` Junio C Hamano
2018-07-25 17:44           ` Stefan Beller
2018-07-26  9:47         ` Johannes Schindelin
2018-08-08 13:05         ` Johannes Schindelin
2018-08-08 17:33           ` Stefan Beller
2018-08-10 21:18             ` Johannes Schindelin
2018-08-10 21:31               ` Junio C Hamano
2018-08-10 22:00                 ` Johannes Schindelin
2018-07-29 21:50       ` Thomas Gummerer
2018-08-10 22:14       ` [PATCH v5 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 01/21] linear-assignment: a function to solve least-cost assignment problems Johannes Schindelin via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 02/21] Introduce `range-diff` to compare iterations of a topic branch Johannes Schindelin via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 03/21] range-diff: first rudimentary implementation Johannes Schindelin via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 04/21] range-diff: improve the order of the shown commits Johannes Schindelin via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 05/21] range-diff: also show the diff between patches Johannes Schindelin via GitGitGadget
2018-08-12 21:47           ` Thomas Gummerer
2018-08-13  9:46             ` Johannes Schindelin
2018-08-13 18:01               ` Thomas Gummerer
2018-08-10 22:14         ` [PATCH v5 06/21] range-diff: right-trim commit messages Johannes Schindelin via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 07/21] range-diff: indent the diffs just like tbdiff Johannes Schindelin via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 08/21] range-diff: suppress the diff headers Johannes Schindelin via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 09/21] range-diff: adjust the output of the commit pairs Johannes Schindelin via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 10/21] range-diff: do not show "function names" in hunk headers Johannes Schindelin via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 11/21] range-diff: add tests Thomas Rast via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 12/21] range-diff: use color for the commit pairs Johannes Schindelin via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 13/21] color: add the meta color GIT_COLOR_REVERSE Johannes Schindelin via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 14/21] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 15/21] range-diff: offer to dual-color the diffs Johannes Schindelin via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 16/21] range-diff --dual-color: skip white-space warnings Johannes Schindelin via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 17/21] range-diff: populate the man page Johannes Schindelin via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 18/21] completion: support `git range-diff` Johannes Schindelin via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 19/21] range-diff: left-pad patch numbers Johannes Schindelin via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 20/21] range-diff: make --dual-color the default mode Johannes Schindelin via GitGitGadget
2018-08-10 22:14         ` [PATCH v5 21/21] range-diff: use dim/bold cues to improve dual color mode Johannes Schindelin via GitGitGadget
2018-08-13 11:32         ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin via GitGitGadget
2018-08-13 11:33           ` [PATCH v6 01/21] linear-assignment: a function to solve least-cost assignment problems Johannes Schindelin via GitGitGadget
2018-08-13 11:33           ` [PATCH v6 02/21] Introduce `range-diff` to compare iterations of a topic branch Johannes Schindelin via GitGitGadget
2018-08-13 11:33           ` [PATCH v6 03/21] range-diff: first rudimentary implementation Johannes Schindelin via GitGitGadget
2019-03-05  6:29             ` Junio C Hamano
2018-08-13 11:33           ` [PATCH v6 04/21] range-diff: improve the order of the shown commits Johannes Schindelin via GitGitGadget
2018-08-13 11:33           ` [PATCH v6 05/21] range-diff: also show the diff between patches Johannes Schindelin via GitGitGadget
2018-08-13 11:33           ` [PATCH v6 06/21] range-diff: right-trim commit messages Johannes Schindelin via GitGitGadget
2018-08-13 11:33           ` [PATCH v6 07/21] range-diff: indent the diffs just like tbdiff Johannes Schindelin via GitGitGadget
2018-08-13 11:33           ` [PATCH v6 08/21] range-diff: suppress the diff headers Johannes Schindelin via GitGitGadget
2018-08-13 11:33           ` [PATCH v6 09/21] range-diff: adjust the output of the commit pairs Johannes Schindelin via GitGitGadget
2018-08-13 11:33           ` [PATCH v6 10/21] range-diff: do not show "function names" in hunk headers Johannes Schindelin via GitGitGadget
2018-08-13 11:33           ` [PATCH v6 11/21] range-diff: add tests Thomas Rast via GitGitGadget
2018-08-13 18:35             ` Thomas Gummerer
2018-08-14 14:53               ` Johannes Schindelin
2018-08-14 15:03                 ` Jeff King
2018-08-14 15:06                   ` Jeff King
2018-08-14 15:18                 ` Junio C Hamano
2018-08-13 11:33           ` [PATCH v6 12/21] range-diff: use color for the commit pairs Johannes Schindelin via GitGitGadget
2018-08-13 11:33           ` [PATCH v6 13/21] color: add the meta color GIT_COLOR_REVERSE Johannes Schindelin via GitGitGadget
2018-08-13 11:33           ` [PATCH v6 14/21] diff: add an internal option to dual-color diffs of diffs Johannes Schindelin via GitGitGadget
2018-08-13 11:33           ` [PATCH v6 15/21] range-diff: offer to dual-color the diffs Johannes Schindelin via GitGitGadget
2018-08-13 11:33           ` [PATCH v6 16/21] range-diff --dual-color: skip white-space warnings Johannes Schindelin via GitGitGadget
2018-08-13 17:48             ` Junio C Hamano
2018-08-13 11:33           ` [PATCH v6 17/21] range-diff: populate the man page Johannes Schindelin via GitGitGadget
2018-09-09 11:14             ` Ævar Arnfjörð Bjarmason
2018-09-09 16:54               ` SZEDER Gábor
2018-09-09 17:19                 ` Ævar Arnfjörð Bjarmason
2018-09-10 13:37                   ` Jeff King
2018-10-02 15:06                     ` Johannes Schindelin
2018-09-10 17:17                   ` Junio C Hamano
2018-08-13 11:33           ` [PATCH v6 18/21] completion: support `git range-diff` Johannes Schindelin via GitGitGadget
2018-08-13 11:33           ` [PATCH v6 19/21] range-diff: left-pad patch numbers Johannes Schindelin via GitGitGadget
2018-08-13 11:33           ` [PATCH v6 20/21] range-diff: make --dual-color the default mode Johannes Schindelin via GitGitGadget
2018-08-13 11:33           ` [PATCH v6 21/21] range-diff: use dim/bold cues to improve dual color mode Johannes Schindelin via GitGitGadget
2018-08-13 11:38           ` [PATCH v6 00/21] Add range-diff, a tbdiff lookalike Johannes Schindelin
2018-08-13 20:47             ` Thomas Gummerer
2018-05-21  4:48 ` [PATCH 00/18] Add `branch-diff`, a `tbdiff` lookalike Junio C Hamano
2018-05-21  9:51   ` Johannes Schindelin
2018-05-22  1:42     ` Junio C Hamano
2018-06-01  8:28       ` Johannes Schindelin

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.