git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [PATCH 0/2] Show Git Mailing List: a builtin difftool
@ 2016-11-22 17:01 Johannes Schindelin
  2016-11-22 17:01 ` [PATCH 1/2] difftool: add the builtin Johannes Schindelin
                   ` (2 more replies)
  0 siblings, 3 replies; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-22 17:01 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

I have been working on the builtin difftool for a little over a week,
for two reasons:

1. Perl is really not native on Windows. Not only is there a performance
   penalty to be paid just for running Perl scripts, we also have to deal
   with the fact that users may have different Perl installations, with
   different options, and some other Perl installation may decide to set
   PERL5LIB globally, wreaking havoc with Git for Windows' Perl (which we
   have to use because almost all other Perl distributions lack the
   Subversion bindings we need for `git svn`).

2. Perl makes for a rather large reason that Git for Windows' installer
   weighs in with >30MB. While one Perl script less does not relieve us
   of that burden, it is one step in the right direction.

This pair of patches serves two purposes: to ask for reviews, and to
show what I plan to release as part of Git for Windows v2.11.0 (which is
due this Thursday, if Git v2.11.0 is released tomorrow, as planned).

The second patch really only explains how I will make sure that the
builtin difftool will only affect users who want to opt in to testing.


Johannes Schindelin (2):
  difftool: add the builtin
  difftool: add a feature flag for the builtin vs scripted version

 .gitignore                 |   2 +
 Makefile                   |   1 +
 builtin.h                  |   1 +
 builtin/builtin-difftool.c | 680 +++++++++++++++++++++++++++++++++++++++++++++
 git-difftool.perl          |   7 +
 git.c                      |  21 ++
 6 files changed, 712 insertions(+)
 create mode 100644 builtin/builtin-difftool.c


base-commit: 1310affe024fba407bff55dbe65cd6d670c8a32d
Published-As: https://github.com/dscho/git/releases/tag/builtin-difftool-v1
Fetch-It-Via: git fetch https://github.com/dscho/git builtin-difftool-v1

-- 
2.10.1.583.g721a9e0


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

* [PATCH 1/2] difftool: add the builtin
  2016-11-22 17:01 [PATCH 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin
@ 2016-11-22 17:01 ` Johannes Schindelin
  2016-11-23  8:08   ` David Aguilar
  2016-11-22 17:01 ` [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version Johannes Schindelin
  2016-11-23 22:03 ` [PATCH v2 0/1] Show Git Mailing List: a builtin difftool Johannes Schindelin
  2 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-22 17:01 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

This adds a builtin difftool that represents a conversion of the current
Perl script version of the difftool.

The motivation is that Perl scripts are not at all native on Windows,
and that `git difftool` therefore is pretty slow on that platform, when
there is no good reason for it to be slow.

In addition, Perl does not really have access to Git's internals. That
means that any script will always have to jump through unnecessary
hoops.

The current version of the builtin difftool does not, however, make full
use of the internals but instead chooses to spawn a couple of Git
processes, still, to make for an easier conversion. There remains a lot
of room for improvement, left for a later date.

Note: the original difftool is still called by `git difftool`. To get the
new, experimental version, call `git builtin-difftool`. The reason: this
new, experimental, builtin difftool will be shipped as part of Git for
Windows v2.11.0, to allow for easier large-scale testing.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitignore                 |   1 +
 Makefile                   |   1 +
 builtin.h                  |   1 +
 builtin/builtin-difftool.c | 680 +++++++++++++++++++++++++++++++++++++++++++++
 git.c                      |   1 +
 5 files changed, 684 insertions(+)
 create mode 100644 builtin/builtin-difftool.c

diff --git a/.gitignore b/.gitignore
index 05cb58a..4f54531 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,6 +51,7 @@
 /git-diff-tree
 /git-difftool
 /git-difftool--helper
+/git-builtin-difftool
 /git-describe
 /git-fast-export
 /git-fast-import
diff --git a/Makefile b/Makefile
index f53fcc9..f764174 100644
--- a/Makefile
+++ b/Makefile
@@ -888,6 +888,7 @@ BUILTIN_OBJS += builtin/diff-files.o
 BUILTIN_OBJS += builtin/diff-index.o
 BUILTIN_OBJS += builtin/diff-tree.o
 BUILTIN_OBJS += builtin/diff.o
+BUILTIN_OBJS += builtin/builtin-difftool.o
 BUILTIN_OBJS += builtin/fast-export.o
 BUILTIN_OBJS += builtin/fetch-pack.o
 BUILTIN_OBJS += builtin/fetch.o
diff --git a/builtin.h b/builtin.h
index b9122bc..409a61e 100644
--- a/builtin.h
+++ b/builtin.h
@@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_index(int argc, const char **argv, const char *prefix);
 extern int cmd_diff(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_builtin_difftool(int argc, const char **argv, const char *prefix);
 extern int cmd_fast_export(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);
diff --git a/builtin/builtin-difftool.c b/builtin/builtin-difftool.c
new file mode 100644
index 0000000..9feefcd
--- /dev/null
+++ b/builtin/builtin-difftool.c
@@ -0,0 +1,680 @@
+/*
+ * "git difftool" builtin command
+ *
+ * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
+ * git-difftool--helper script.
+ *
+ * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.
+ * The GIT_DIFF* variables are exported for use by git-difftool--helper.
+ *
+ * Any arguments that are unknown to this script are forwarded to 'git diff'.
+ *
+ * Copyright (C) 2016 Johannes Schindelin
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+#include "run-command.h"
+#include "argv-array.h"
+#include "strbuf.h"
+#include "lockfile.h"
+
+static char *diff_gui_tool;
+static int trust_exit_code;
+
+static const char * const builtin_difftool_usage[] = {
+	N_("git add [<options>] [--] <pathspec>..."),
+	NULL
+};
+
+static int difftool_config(const char *var, const char *value, void *cb)
+{
+	if (!strcmp(var, "diff.guitool")) {
+		diff_gui_tool = xstrdup(value);
+		return 0;
+	}
+
+	if (!strcmp(var, "difftool.trustexitcode")) {
+		trust_exit_code = git_config_bool(var, value);
+		return 0;
+	}
+
+	return git_default_config(var, value, cb);
+}
+
+static int print_tool_help(void)
+{
+	const char *argv[] = { "mergetool", "--tool-help=diff", NULL };
+	return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
+static int parse_index_info(char *p, int *mode1, int *mode2,
+			    struct object_id *oid1, struct object_id *oid2,
+			    char *status)
+{
+	if (*p != ':')
+		return error("expected ':', got '%c'", *p);
+	*mode1 = (int)strtol(p + 1, &p, 8);
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	*mode2 = (int)strtol(p + 1, &p, 8);
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	if (get_oid_hex(++p, oid1))
+		return error("expected object ID, got '%s'", p + 1);
+	p += GIT_SHA1_HEXSZ;
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	if (get_oid_hex(++p, oid2))
+		return error("expected object ID, got '%s'", p + 1);
+	p += GIT_SHA1_HEXSZ;
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	*status = *++p;
+	if (!status || p[1])
+		return error("unexpected trailer: '%s'", p);
+	return 0;
+}
+
+/*
+ * Remove any trailing slash from $workdir
+ * before starting to avoid double slashes in symlink targets.
+ */
+static void add_path(struct strbuf *buf, size_t base_len, const char *path)
+{
+	strbuf_setlen(buf, base_len);
+	if (buf->len && buf->buf[buf->len - 1] != '/')
+		strbuf_addch(buf, '/');
+	strbuf_addstr(buf, path);
+}
+
+/*
+ * Determine whether we can simply reuse the file in the worktree.
+ */
+static int use_wt_file(const char *workdir, const char *name,
+		       struct object_id *oid)
+{
+	struct strbuf buf = STRBUF_INIT;
+	struct stat st;
+	int use = 0;
+
+	strbuf_addstr(&buf, workdir);
+	add_path(&buf, buf.len, name);
+
+	if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) {
+		struct object_id wt_oid;
+		int fd = open(buf.buf, O_RDONLY);
+
+		if (!index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {
+			if (is_null_oid(oid)) {
+				oidcpy(oid, &wt_oid);
+				use = 1;
+			} else if (!oidcmp(oid, &wt_oid))
+				use = 1;
+		}
+	}
+
+	strbuf_release(&buf);
+
+	return use;
+}
+
+struct working_tree_entry {
+	struct hashmap_entry entry;
+	char path[FLEX_ARRAY];
+};
+
+static int working_tree_entry_cmp(struct working_tree_entry *a,
+				  struct working_tree_entry *b, void *keydata)
+{
+	return strcmp(a->path, b->path);
+}
+
+/*
+ * The `left` and `right` entries hold paths for the symlinks hashmap,
+ * and a SHA-1 surrounded by brief text for submodules.
+ */
+struct pair_entry {
+	struct hashmap_entry entry;
+	char left[PATH_MAX], right[PATH_MAX];
+	const char path[FLEX_ARRAY];
+};
+
+static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata)
+{
+	return strcmp(a->path, b->path);
+}
+
+static void add_left_or_right(struct hashmap *map, const char *path,
+			      const char *content, int is_right)
+{
+	struct pair_entry *e, *existing;
+
+	FLEX_ALLOC_STR(e, path, path);
+	hashmap_entry_init(e, strhash(path));
+	existing = hashmap_get(map, e, NULL);
+	if (existing) {
+		free(e);
+		e = existing;
+	} else {
+		e->left[0] = e->right[0] = '\0';
+		hashmap_add(map, e);
+	}
+	strcpy(is_right ? e->right : e->left, content);
+}
+
+struct path_entry {
+	struct hashmap_entry entry;
+	char path[FLEX_ARRAY];
+};
+
+int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key)
+{
+	return strcmp(a->path, key ? key : b->path);
+}
+
+static void changed_files(struct hashmap *result, const char *index_path,
+			  const char *workdir)
+{
+	struct child_process update_index = CHILD_PROCESS_INIT;
+	struct child_process diff_files = CHILD_PROCESS_INIT;
+	struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT;
+	const char *git_dir = absolute_path(get_git_dir()), *env[] = {
+		NULL, NULL
+	};
+	FILE *fp;
+
+	strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path);
+	env[0] = index_env.buf;
+
+	argv_array_pushl(&update_index.args,
+			 "--git-dir", git_dir, "--work-tree", workdir,
+			 "update-index", "--really-refresh", "-q",
+			 "--unmerged", NULL);
+	update_index.no_stdin = 1;
+	update_index.no_stdout = 1;
+	update_index.no_stderr = 1;
+	update_index.git_cmd = 1;
+	update_index.use_shell = 0;
+	update_index.clean_on_exit = 1;
+	update_index.dir = workdir;
+	update_index.env = env;
+	/* Ignore any errors of update-index */
+	run_command(&update_index);
+
+	argv_array_pushl(&diff_files.args,
+			 "--git-dir", git_dir, "--work-tree", workdir,
+			 "diff-files", "--name-only", "-z", NULL);
+	diff_files.no_stdin = 1;
+	diff_files.git_cmd = 1;
+	diff_files.use_shell = 0;
+	diff_files.clean_on_exit = 1;
+	diff_files.out = -1;
+	diff_files.dir = workdir;
+	diff_files.env = env;
+	if (start_command(&diff_files))
+		die("could not obtain raw diff");
+	fp = xfdopen(diff_files.out, "r");
+	while (!strbuf_getline_nul(&buf, fp)) {
+		struct path_entry *entry;
+		FLEX_ALLOC_STR(entry, path, buf.buf);
+		hashmap_entry_init(entry, strhash(buf.buf));
+		hashmap_add(result, entry);
+	}
+	if (finish_command(&diff_files))
+		die("diff-files did not exit properly");
+	strbuf_release(&index_env);
+	strbuf_release(&buf);
+}
+
+#include "dir.h"
+
+static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)
+{
+	struct strbuf buf = STRBUF_INIT;
+	strbuf_addstr(&buf, tmpdir);
+	remove_dir_recursively(&buf, 0);
+	if (exit_code)
+		warning(_("failed: %d"), exit_code);
+	exit(exit_code);
+}
+
+static int ensure_leading_directories(char *path)
+{
+	switch (safe_create_leading_directories(path)) {
+		case SCLD_OK:
+		case SCLD_EXISTS:
+			return 0;
+		default:
+			return error(_("could not create leading directories "
+				       "of '%s'"), path);
+	}
+}
+
+static int run_dir_diff(const char *extcmd, int symlinks,
+			int argc, const char **argv)
+{
+	char tmpdir[PATH_MAX];
+	struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;
+	struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;
+	struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;
+	struct strbuf wtdir = STRBUF_INIT;
+	size_t ldir_len, rdir_len, wtdir_len;
+	struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1);
+	const char *workdir, *tmp;
+	int ret = 0, i;
+	FILE *fp;
+	struct hashmap working_tree_dups, submodules, symlinks2;
+	struct hashmap_iter iter;
+	struct pair_entry *entry;
+	enum object_type type;
+	unsigned long size;
+	struct index_state wtindex;
+	struct checkout lstate, rstate;
+	int rc, flags = RUN_GIT_CMD, err = 0;
+	struct child_process child = CHILD_PROCESS_INIT;
+	const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL };
+	struct hashmap wt_modified, tmp_modified;
+	int indices_loaded = 0;
+
+	setup_work_tree();
+	workdir = get_git_work_tree();
+
+	/* Setup temp directories */
+	tmp = getenv("TMPDIR");
+	sprintf(tmpdir, "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp");
+	if (!mkdtemp(tmpdir))
+		return error("could not create temporary directory");
+	strbuf_addf(&ldir, "%s/left/", tmpdir);
+	strbuf_addf(&rdir, "%s/right/", tmpdir);
+	strbuf_addstr(&wtdir, workdir);
+	if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))
+		strbuf_addch(&wtdir, '/');
+	mkdir(ldir.buf, 0777);
+	mkdir(rdir.buf, 0777);
+
+	memset(&wtindex, 0, sizeof(wtindex));
+
+	memset(&lstate, 0, sizeof(lstate));
+	lstate.base_dir = ldir.buf;
+	lstate.base_dir_len = ldir.len;
+	lstate.force = 1;
+	memset(&rstate, 0, sizeof(rstate));
+	rstate.base_dir = rdir.buf;
+	rstate.base_dir_len = rdir.len;
+	rstate.force = 1;
+
+	ldir_len = ldir.len;
+	rdir_len = rdir.len;
+	wtdir_len = wtdir.len;
+
+	hashmap_init(&working_tree_dups,
+		     (hashmap_cmp_fn)working_tree_entry_cmp, 0);
+	hashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0);
+	hashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0);
+
+	child.no_stdin = 1;
+	child.git_cmd = 1;
+	child.use_shell = 0;
+	child.clean_on_exit = 1;
+	child.out = -1;
+	argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z",
+			 NULL);
+	for (i = 0; i < argc; i++)
+		argv_array_push(&child.args, argv[i]);
+	if (start_command(&child))
+		die("could not obtain raw diff");
+	fp = xfdopen(child.out, "r");
+
+	/* Build index info for left and right sides of the diff */
+	while (!strbuf_getline_nul(&info, fp)) {
+		int lmode, rmode;
+		struct object_id loid, roid;
+		char status;
+		const char *src_path, *dst_path;
+		size_t src_path_len, dst_path_len;
+
+		if (starts_with(info.buf, "::"))
+			die(N_("combined diff formats('-c' and '--cc') are "
+			       "not supported in\n"
+			       "directory diff mode('-d' and '--dir-diff')."));
+
+		if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,
+				     &status))
+			break;
+		if (strbuf_getline_nul(&lpath, fp))
+			break;
+		src_path = lpath.buf;
+		src_path_len = lpath.len;
+
+		if (status != 'C' && status != 'R') {
+			dst_path = src_path;
+			dst_path_len = src_path_len;
+		} else {
+			if (strbuf_getline_nul(&rpath, fp))
+				break;
+			dst_path = rpath.buf;
+			dst_path_len = rpath.len;
+		}
+
+		if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) {
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "Subproject commit %s",
+				    oid_to_hex(&loid));
+			add_left_or_right(&submodules, src_path, buf.buf, 0);
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "Subproject commit %s",
+				    oid_to_hex(&roid));
+			if (!oidcmp(&loid, &roid))
+				strbuf_addstr(&buf, "-dirty");
+			add_left_or_right(&submodules, dst_path, buf.buf, 1);
+			continue;
+		}
+
+		if (S_ISLNK(lmode)) {
+			char *content = read_sha1_file(loid.hash, &type, &size);
+			add_left_or_right(&symlinks2, src_path, content, 0);
+			free(content);
+		}
+
+		if (S_ISLNK(rmode)) {
+			char *content = read_sha1_file(roid.hash, &type, &size);
+			add_left_or_right(&symlinks2, dst_path, content, 1);
+			free(content);
+		}
+
+		if (lmode && status != 'C') {
+			ce->ce_mode = lmode;
+			oidcpy(&ce->oid, &loid);
+			strcpy(ce->name, src_path);
+			ce->ce_namelen = src_path_len;
+			if (checkout_entry(ce, &lstate, NULL))
+				return error("could not write '%s'", src_path);
+		}
+
+		if (rmode) {
+			struct working_tree_entry *entry;
+
+			/* Avoid duplicate working_tree entries */
+			FLEX_ALLOC_STR(entry, path, dst_path);
+			hashmap_entry_init(entry, strhash(dst_path));
+			if (hashmap_get(&working_tree_dups, entry, NULL)) {
+				free(entry);
+				continue;
+			}
+			hashmap_add(&working_tree_dups, entry);
+
+			if (!use_wt_file(workdir, dst_path, &roid)) {
+				ce->ce_mode = rmode;
+				oidcpy(&ce->oid, &roid);
+				strcpy(ce->name, dst_path);
+				ce->ce_namelen = dst_path_len;
+				if (checkout_entry(ce, &rstate, NULL))
+					return error("could not write '%s'",
+						     dst_path);
+			} else if (!is_null_oid(&roid)) {
+				/*
+				 * Changes in the working tree need special
+				 * treatment since they are not part of the
+				 * index.
+				 */
+				struct cache_entry *ce2 =
+					make_cache_entry(rmode, roid.hash,
+							 dst_path, 0, 0);
+				ce_mode_from_stat(ce2, rmode);
+
+				add_index_entry(&wtindex, ce2,
+						ADD_CACHE_JUST_APPEND);
+
+				add_path(&wtdir, wtdir_len, dst_path);
+				add_path(&rdir, rdir_len, dst_path);
+				if (ensure_leading_directories(rdir.buf))
+					return error("could not create "
+						     "directory for '%s'",
+						     dst_path);
+				if (symlinks) {
+					if (symlink(wtdir.buf, rdir.buf)) {
+						ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf);
+						goto finish;
+					}
+				} else {
+					struct stat st;
+					if (stat(wtdir.buf, &st))
+						st.st_mode = 0644;
+					if (copy_file(rdir.buf, wtdir.buf,
+						      st.st_mode)) {
+						ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf);
+						goto finish;
+					}
+				}
+			}
+		}
+	}
+	if (finish_command(&child)) {
+		ret = error("error occurred running diff --raw");
+		goto finish;
+	}
+
+	/*
+	 * Changes to submodules require special treatment.This loop writes a
+	 * temporary file to both the left and right directories to show the
+	 * change in the recorded SHA1 for the submodule.
+	 */
+	hashmap_iter_init(&submodules, &iter);
+	while ((entry = hashmap_iter_next(&iter))) {
+		if (*entry->left) {
+			add_path(&ldir, ldir_len, entry->path);
+			ensure_leading_directories(ldir.buf);
+			write_file(ldir.buf, "%s", entry->left);
+		}
+		if (*entry->right) {
+			add_path(&rdir, rdir_len, entry->path);
+			ensure_leading_directories(rdir.buf);
+			write_file(rdir.buf, "%s", entry->right);
+		}
+	}
+
+	/*
+	 * Symbolic links require special treatment.The standard "git diff"
+	 * shows only the link itself, not the contents of the link target.
+	 * This loop replicates that behavior.
+	 */
+	hashmap_iter_init(&symlinks2, &iter);
+	while ((entry = hashmap_iter_next(&iter))) {
+		if (*entry->left) {
+			add_path(&ldir, ldir_len, entry->path);
+			ensure_leading_directories(ldir.buf);
+			write_file(ldir.buf, "%s", entry->left);
+		}
+		if (*entry->right) {
+			add_path(&rdir, rdir_len, entry->path);
+			ensure_leading_directories(rdir.buf);
+			write_file(rdir.buf, "%s", entry->right);
+		}
+	}
+
+	strbuf_release(&buf);
+
+	strbuf_setlen(&ldir, ldir_len);
+	helper_argv[1] = ldir.buf;
+	strbuf_setlen(&rdir, rdir_len);
+	helper_argv[2] = rdir.buf;
+
+	if (extcmd) {
+		helper_argv[0] = extcmd;
+		flags = 0;
+	} else
+		setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1);
+	rc = run_command_v_opt(helper_argv, flags);
+
+	/*
+	 * If the diff includes working copy files and those
+	 * files were modified during the diff, then the changes
+	 * should be copied back to the working tree.
+	 * Do not copy back files when symlinks are used and the
+	 * external tool did not replace the original link with a file.
+	 *
+	 * These hashes are loaded lazily since they aren't needed
+	 * in the common case of --symlinks and the difftool updating
+	 * files through the symlink.
+	 */
+	hashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp,
+		     wtindex.cache_nr);
+	hashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp,
+		     wtindex.cache_nr);
+
+	for (i = 0; i < wtindex.cache_nr; i++) {
+		struct hashmap_entry dummy;
+		const char *name = wtindex.cache[i]->name;
+		struct stat st;
+
+		add_path(&rdir, rdir_len, name);
+		if (lstat(rdir.buf, &st))
+			continue;
+
+		if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode))
+			continue;
+
+		if (!indices_loaded) {
+			static struct lock_file lock;
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "%s/wtindex", tmpdir);
+			if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
+			    write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {
+				ret = error("could not write %s", buf.buf);
+				rollback_lock_file(&lock);
+				goto finish;
+			}
+			changed_files(&wt_modified, buf.buf, workdir);
+			strbuf_setlen(&rdir, rdir_len);
+			changed_files(&tmp_modified, buf.buf, rdir.buf);
+			add_path(&rdir, rdir_len, name);
+			indices_loaded = 1;
+		}
+
+		hashmap_entry_init(&dummy, strhash(name));
+		if (hashmap_get(&tmp_modified, &dummy, name)) {
+			add_path(&wtdir, wtdir_len, name);
+			if (hashmap_get(&wt_modified, &dummy, name)) {
+				warning(_("both files modified: '%s' and '%s'."),
+					wtdir.buf, rdir.buf);
+				warning(_("working tree file has been left."));
+				warning("");
+				err = 1;
+			} else if (unlink(wtdir.buf) ||
+				   copy_file(wtdir.buf, rdir.buf, st.st_mode))
+				warning_errno(_("could not copy '%s' to '%s'"),
+					      rdir.buf, wtdir.buf);
+		}
+	}
+
+	if (err) {
+		warning(_("temporary files exist in '%s'."), tmpdir);
+		warning(_("you may want to cleanup or recover these."));
+		exit(1);
+	} else
+		exit_cleanup(tmpdir, rc);
+
+finish:
+	free(ce);
+	strbuf_release(&ldir);
+	strbuf_release(&rdir);
+	strbuf_release(&wtdir);
+	strbuf_release(&buf);
+
+	return ret;
+}
+
+static int run_file_diff(int prompt, int argc, const char **argv)
+{
+	struct argv_array args = ARGV_ARRAY_INIT;
+	const char *env[] = {
+		"GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL,
+		NULL
+	};
+	int ret = 0, i;
+
+	if (prompt > 0)
+		env[2] = "GIT_DIFFTOOL_PROMPT=true";
+	else if (!prompt)
+		env[2] = "GIT_DIFFTOOL_NO_PROMPT=true";
+
+	argv_array_push(&args, "diff");
+	for (i = 0; i < argc; i++)
+		argv_array_push(&args, argv[i]);
+	ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, NULL, env);
+	exit(ret);
+}
+
+int cmd_builtin_difftool(int argc, const char ** argv, const char * prefix)
+{
+	int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,
+	    tool_help = 0;
+	static char *difftool_cmd = NULL, *extcmd = NULL;
+
+	struct option builtin_difftool_options[] = {
+		OPT_BOOL('g', "gui", &use_gui_tool,
+			 N_("use `diff.guitool` instead of `diff.tool`")),
+		OPT_BOOL('d', "dir-diff", &dir_diff,
+			 N_("perform a full-directory diff")),
+		{ OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL,
+			N_("do not prompt before launching a diff tool"),
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
+		{ OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL,
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
+			NULL, 1 },
+		OPT_BOOL(0, "symlinks", &symlinks,
+			 N_("use symlinks in dir-diff mode")),
+		OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"),
+			   N_("use the specified diff tool")),
+		OPT_BOOL(0, "tool-help", &tool_help,
+			 N_("print a list of diff tools that may be used with "
+			    "`--tool`")),
+		OPT_BOOL(0, "trust-exit-code", &trust_exit_code,
+			 N_("make 'git-difftool' exit when an invoked diff "
+			    "tool returns a non - zero exit code")),
+		OPT_STRING('x', "extcmd", &extcmd, N_("<command>"),
+			   N_("specify a custom command for viewing diffs")),
+		OPT_END()
+	};
+
+	symlinks = has_symlinks;
+
+	git_config(difftool_config, NULL);
+
+	argc = parse_options(argc, argv, prefix, builtin_difftool_options,
+			     builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |
+			     PARSE_OPT_KEEP_DASHDASH);
+
+	if (tool_help)
+		return print_tool_help();
+
+	if (use_gui_tool && diff_gui_tool && *diff_gui_tool)
+		setenv("GIT_DIFF_TOOL", diff_gui_tool, 1);
+	else if (difftool_cmd) {
+		if (*difftool_cmd)
+			setenv("GIT_DIFF_TOOL", difftool_cmd, 1);
+		else
+			die(_("no <tool> given for --tool=<tool>"));
+	}
+
+	if (extcmd) {
+		if (*extcmd)
+			setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1);
+		else
+			die(_("no <cmd> given for --extcmd=<cmd>"));
+	}
+
+	setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE",
+	       trust_exit_code ? "true" : "false", 1);
+
+	/*
+	 * In directory diff mode, 'git-difftool--helper' is called once
+	 * to compare the a / b directories.In file diff mode, 'git diff'
+	 * will invoke a separate instance of 'git-difftool--helper' for
+	 * each file that changed.
+	 */
+	if (dir_diff)
+		return run_dir_diff(extcmd, symlinks, argc, argv);
+	return run_file_diff(prompt, argc, argv);
+}
diff --git a/git.c b/git.c
index efa1059..eaa0f67 100644
--- a/git.c
+++ b/git.c
@@ -424,6 +424,7 @@ static struct cmd_struct commands[] = {
 	{ "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
 	{ "diff-index", cmd_diff_index, RUN_SETUP },
 	{ "diff-tree", cmd_diff_tree, RUN_SETUP },
+	{ "builtin-difftool", cmd_builtin_difftool, RUN_SETUP | NEED_WORK_TREE },
 	{ "fast-export", cmd_fast_export, RUN_SETUP },
 	{ "fetch", cmd_fetch, RUN_SETUP },
 	{ "fetch-pack", cmd_fetch_pack, RUN_SETUP },
-- 
2.10.1.583.g721a9e0



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

* [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version
  2016-11-22 17:01 [PATCH 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin
  2016-11-22 17:01 ` [PATCH 1/2] difftool: add the builtin Johannes Schindelin
@ 2016-11-22 17:01 ` Johannes Schindelin
  2016-11-23 14:51   ` Dennis Kaarsemaker
  2016-11-23 22:03 ` [PATCH v2 0/1] Show Git Mailing List: a builtin difftool Johannes Schindelin
  2 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-22 17:01 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

The popular difftool command was just converted into a builtin, for
better performance on Windows as well as to reduce the number of Perl
scripts (so that we may, in the very long run, be able to ship Git for
Windows without any Perl interpreter at all).

However, it would be sloppy practice to simply switch over from the
tried-and-tested (albeit slow) scripted version to the spanking new
builtin version that has not seen a whole lot of real-world testing.

So let's add a feature flag.

If the file `use-builtin-difftool` exists in Git's exec path, Git will
now automagically use the builtin version of the difftool, without
requiring the user to call `git builtin-difftool <args>`. This comes in
particularly handy when the difftool command is used from within
scripts.

If the file `use-builtin-difftool` is absent from Git's exec path, which
is the default, Git will use the scripted version as before.

The original idea was to use an environment variable
GIT_USE_BUILTIN_DIFFTOOL, but the test suite resets those variables, and
we do want to use that feature flag to run the tests with, and without,
the feature flag.

Besides, the plan is to add an opt-in flag in Git for Windows'
installer. If we implemented the feature flag as an environment
variable, we would have to modify the user's environment, in order to
make the builtin difftool the default when called from Git Bash, Git CMD
or third-party tools.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitignore        |  1 +
 git-difftool.perl |  7 +++++++
 git.c             | 20 ++++++++++++++++++++
 3 files changed, 28 insertions(+)

diff --git a/.gitignore b/.gitignore
index 4f54531..91bfd09 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+/use-builtin-difftool
 /GIT-BUILD-OPTIONS
 /GIT-CFLAGS
 /GIT-LDFLAGS
diff --git a/git-difftool.perl b/git-difftool.perl
index a5790d0..28e47d8 100755
--- a/git-difftool.perl
+++ b/git-difftool.perl
@@ -23,6 +23,13 @@ use File::Temp qw(tempdir);
 use Getopt::Long qw(:config pass_through);
 use Git;
 
+if (-e Git::exec_path() . '/use-builtin-difftool') {
+	unshift(@ARGV, "builtin-difftool");
+	unshift(@ARGV, "git");
+	exec(@ARGV);
+	die("Could not execute builtin difftool");
+}
+
 sub usage
 {
 	my $exitcode = shift;
diff --git a/git.c b/git.c
index eaa0f67..7a0df7a 100644
--- a/git.c
+++ b/git.c
@@ -2,6 +2,7 @@
 #include "exec_cmd.h"
 #include "help.h"
 #include "run-command.h"
+#include "dir.h"
 
 const char git_usage_string[] =
 	"git [--version] [--help] [-C <path>] [-c name=value]\n"
@@ -542,6 +543,22 @@ static void strip_extension(const char **argv)
 #define strip_extension(cmd)
 #endif
 
+static int use_builtin_difftool(void)
+{
+	static int initialized, use;
+
+	if (!initialized) {
+		struct strbuf buf = STRBUF_INIT;
+		strbuf_addf(&buf, "%s/%s", git_exec_path(),
+			    "use-builtin-difftool");
+		use = file_exists(buf.buf);
+		strbuf_release(&buf);
+		initialized = 1;
+	}
+
+	return use;
+}
+
 static void handle_builtin(int argc, const char **argv)
 {
 	struct argv_array args = ARGV_ARRAY_INIT;
@@ -551,6 +568,9 @@ static void handle_builtin(int argc, const char **argv)
 	strip_extension(argv);
 	cmd = argv[0];
 
+	if (!strcmp("difftool", cmd) && use_builtin_difftool())
+		cmd = "builtin-difftool";
+
 	/* Turn "git cmd --help" into "git help --exclude-guides cmd" */
 	if (argc > 1 && !strcmp(argv[1], "--help")) {
 		int i;
-- 
2.10.1.583.g721a9e0

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

* Re: [PATCH 1/2] difftool: add the builtin
  2016-11-22 17:01 ` [PATCH 1/2] difftool: add the builtin Johannes Schindelin
@ 2016-11-23  8:08   ` David Aguilar
  2016-11-23 11:34     ` Johannes Schindelin
  0 siblings, 1 reply; 86+ messages in thread
From: David Aguilar @ 2016-11-23  8:08 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git, Junio C Hamano

On Tue, Nov 22, 2016 at 06:01:23PM +0100, Johannes Schindelin wrote:
> This adds a builtin difftool that represents a conversion of the current
> Perl script version of the difftool.
> 
> The motivation is that Perl scripts are not at all native on Windows,
> and that `git difftool` therefore is pretty slow on that platform, when
> there is no good reason for it to be slow.
> 
> In addition, Perl does not really have access to Git's internals. That
> means that any script will always have to jump through unnecessary
> hoops.

Nice!

> The current version of the builtin difftool does not, however, make full
> use of the internals but instead chooses to spawn a couple of Git
> processes, still, to make for an easier conversion. There remains a lot
> of room for improvement, left for a later date.
> 
> Note: the original difftool is still called by `git difftool`. To get the
> new, experimental version, call `git builtin-difftool`. The reason: this
> new, experimental, builtin difftool will be shipped as part of Git for
> Windows v2.11.0, to allow for easier large-scale testing.

I like this plan.  I was going to ask for an environment
variable (to preset in git-cola) but since Git for Windows is
handling it then everyone benefits.

> diff --git a/builtin/builtin-difftool.c b/builtin/builtin-difftool.c
> new file mode 100644
> index 0000000..9feefcd
> --- /dev/null
> +++ b/builtin/builtin-difftool.c
> @@ -0,0 +1,680 @@
> +/*
> + * "git difftool" builtin command
> + *
> + * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
> + * git-difftool--helper script.
> + *
> + * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.
> + * The GIT_DIFF* variables are exported for use by git-difftool--helper.
> + *
> + * Any arguments that are unknown to this script are forwarded to 'git diff'.
> + *
> + * Copyright (C) 2016 Johannes Schindelin
> + */
> +#include "cache.h"
> +#include "builtin.h"
> +#include "parse-options.h"
> +#include "run-command.h"
> +#include "argv-array.h"
> +#include "strbuf.h"
> +#include "lockfile.h"
> +
> +static char *diff_gui_tool;
> +static int trust_exit_code;
> +
> +static const char * const builtin_difftool_usage[] = {
> +	N_("git add [<options>] [--] <pathspec>..."),
> +	NULL
> +};

The usage should probably say "difftool" (or "builtin-difftool").

> [...]
> +static void changed_files(struct hashmap *result, const char *index_path,
> +			  const char *workdir)
> +{
> +[...]
> +}
> +
> +#include "dir.h"

Can this mid-file #include go to the top of the file?

> +static int run_dir_diff(const char *extcmd, int symlinks,
> +			int argc, const char **argv)
> +{
> +	char tmpdir[PATH_MAX];
> +	struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;
> +	struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;
> +	struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;
> +	struct strbuf wtdir = STRBUF_INIT;
> +	size_t ldir_len, rdir_len, wtdir_len;
> +	struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1);
> +	const char *workdir, *tmp;
> +	int ret = 0, i;
> +	FILE *fp;
> +	struct hashmap working_tree_dups, submodules, symlinks2;
> +	struct hashmap_iter iter;
> +	struct pair_entry *entry;
> +	enum object_type type;
> +	unsigned long size;
> +	struct index_state wtindex;
> +	struct checkout lstate, rstate;
> +	int rc, flags = RUN_GIT_CMD, err = 0;
> +	struct child_process child = CHILD_PROCESS_INIT;
> +	const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL };
> +	struct hashmap wt_modified, tmp_modified;
> +	int indices_loaded = 0;
> +
> +	setup_work_tree();
> +	workdir = get_git_work_tree();
> +
> +	/* Setup temp directories */
> +	tmp = getenv("TMPDIR");
> +	sprintf(tmpdir, "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp");

Maybe snprintf instead?

getenv() won't return anything longer than PATH_MAX for most
users, but users are weird.

> +	if (!mkdtemp(tmpdir))
> +		return error("could not create temporary directory");

Mention the tmpdir here?

> +	strbuf_addf(&ldir, "%s/left/", tmpdir);
> +	strbuf_addf(&rdir, "%s/right/", tmpdir);
> +	strbuf_addstr(&wtdir, workdir);
> +	if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))
> +		strbuf_addch(&wtdir, '/');
> +	mkdir(ldir.buf, 0777);
> +	mkdir(rdir.buf, 0777);

Seeing the perl mkpath() default 0777 spelled out this way
makes me wonder whether 0700 would be safer.

The mkdtemp() above is already using 0700 so it's ok, but it
might be worth making it consistent (later, perhaps).

> [...]
> +int cmd_builtin_difftool(int argc, const char ** argv, const char * prefix)
> +{
> +	int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,
> +	    tool_help = 0;
> +	static char *difftool_cmd = NULL, *extcmd = NULL;
> +
> +	struct option builtin_difftool_options[] = {
> +		OPT_BOOL('g', "gui", &use_gui_tool,
> +			 N_("use `diff.guitool` instead of `diff.tool`")),
> +		OPT_BOOL('d', "dir-diff", &dir_diff,
> +			 N_("perform a full-directory diff")),
> +		{ OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL,
> +			N_("do not prompt before launching a diff tool"),
> +			PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
> +		{ OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL,
> +			PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
> +			NULL, 1 },
> +		OPT_BOOL(0, "symlinks", &symlinks,
> +			 N_("use symlinks in dir-diff mode")),
> +		OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"),
> +			   N_("use the specified diff tool")),
> +		OPT_BOOL(0, "tool-help", &tool_help,
> +			 N_("print a list of diff tools that may be used with "
> +			    "`--tool`")),
> +		OPT_BOOL(0, "trust-exit-code", &trust_exit_code,
> +			 N_("make 'git-difftool' exit when an invoked diff "
> +			    "tool returns a non - zero exit code")),
> +		OPT_STRING('x', "extcmd", &extcmd, N_("<command>"),
> +			   N_("specify a custom command for viewing diffs")),
> +		OPT_END()
> +	};
> +
> +	symlinks = has_symlinks;
> +
> +	git_config(difftool_config, NULL);
> +
> +	argc = parse_options(argc, argv, prefix, builtin_difftool_options,
> +			     builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |
> +			     PARSE_OPT_KEEP_DASHDASH);
> +
> +	if (tool_help)
> +		return print_tool_help();
> +
> +	if (use_gui_tool && diff_gui_tool && *diff_gui_tool)
> +		setenv("GIT_DIFF_TOOL", diff_gui_tool, 1);
> +	else if (difftool_cmd) {
> +		if (*difftool_cmd)
> +			setenv("GIT_DIFF_TOOL", difftool_cmd, 1);
> +		else
> +			die(_("no <tool> given for --tool=<tool>"));
> +	}
> +
> +	if (extcmd) {
> +		if (*extcmd)
> +			setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1);
> +		else
> +			die(_("no <cmd> given for --extcmd=<cmd>"));
> +	}
> +
> +	setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE",
> +	       trust_exit_code ? "true" : "false", 1);
> +
> +	/*
> +	 * In directory diff mode, 'git-difftool--helper' is called once
> +	 * to compare the a / b directories.In file diff mode, 'git diff'
> +	 * will invoke a separate instance of 'git-difftool--helper' for
> +	 * each file that changed.
> +	 */

Missing space after "." in the comment above.

> +	if (dir_diff)
> +		return run_dir_diff(extcmd, symlinks, argc, argv);
> +	return run_file_diff(prompt, argc, argv);
> +}
> diff --git a/git.c b/git.c
> index efa1059..eaa0f67 100644
> --- a/git.c
> +++ b/git.c
> @@ -424,6 +424,7 @@ static struct cmd_struct commands[] = {
>  	{ "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
>  	{ "diff-index", cmd_diff_index, RUN_SETUP },
>  	{ "diff-tree", cmd_diff_tree, RUN_SETUP },
> +	{ "builtin-difftool", cmd_builtin_difftool, RUN_SETUP | NEED_WORK_TREE },
>  	{ "fast-export", cmd_fast_export, RUN_SETUP },
>  	{ "fetch", cmd_fetch, RUN_SETUP },
>  	{ "fetch-pack", cmd_fetch_pack, RUN_SETUP },

This isn't alphabetical anymore, but it actually is if you
consider that the final plan is to change "builtin-difftool" to
"difftool".

If we want to minimize that future diff we could name
cmd_builtin_difftool() as cmd_difftool() for consistency now so
that the future commit only needs to tweak the string here.
-- 
David

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

* Re: [PATCH 1/2] difftool: add the builtin
  2016-11-23  8:08   ` David Aguilar
@ 2016-11-23 11:34     ` Johannes Schindelin
  0 siblings, 0 replies; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-23 11:34 UTC (permalink / raw)
  To: David Aguilar; +Cc: git, Junio C Hamano

Hi David,

On Wed, 23 Nov 2016, David Aguilar wrote:

> On Tue, Nov 22, 2016 at 06:01:23PM +0100, Johannes Schindelin wrote:
>
> > +static const char * const builtin_difftool_usage[] = {
> > +	N_("git add [<options>] [--] <pathspec>..."),
> > +	NULL
> > +};
> 
> The usage should probably say "difftool" (or "builtin-difftool").

Ah, my dirty secret was spilled. I copy-edited this. *pours ashes over his
head*

> > [...]
> > +static void changed_files(struct hashmap *result, const char *index_path,
> > +			  const char *workdir)
> > +{
> > +[...]
> > +}
> > +
> > +#include "dir.h"
> 
> Can this mid-file #include go to the top of the file?

Yep, thanks.

In case you are interested: You probably guessed it, it was left for a
later clean-up. I worked a bit over the last weeks on getting Git to build
in Visual Studio, to be able to benefit from its quite nice features (I
was always a fan of Visual Studio, long before I started working at
Microsoft). I used the conversion of the difftool as an excuse to make use
of this myself: I did the entire conversion in Visual Studio, reverting to
the old, tedious command-line driven workflow to fix the bugs identified
by t7800-difftool.sh.

> > +static int run_dir_diff(const char *extcmd, int symlinks,
> > +			int argc, const char **argv)
> > +{
> > +	char tmpdir[PATH_MAX];
> > +	struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;
> > +	struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;
> > +	struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;
> > +	struct strbuf wtdir = STRBUF_INIT;
> > +	size_t ldir_len, rdir_len, wtdir_len;
> > +	struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1);
> > +	const char *workdir, *tmp;
> > +	int ret = 0, i;
> > +	FILE *fp;
> > +	struct hashmap working_tree_dups, submodules, symlinks2;
> > +	struct hashmap_iter iter;
> > +	struct pair_entry *entry;
> > +	enum object_type type;
> > +	unsigned long size;
> > +	struct index_state wtindex;
> > +	struct checkout lstate, rstate;
> > +	int rc, flags = RUN_GIT_CMD, err = 0;
> > +	struct child_process child = CHILD_PROCESS_INIT;
> > +	const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL };
> > +	struct hashmap wt_modified, tmp_modified;
> > +	int indices_loaded = 0;
> > +
> > +	setup_work_tree();
> > +	workdir = get_git_work_tree();
> > +
> > +	/* Setup temp directories */
> > +	tmp = getenv("TMPDIR");
> > +	sprintf(tmpdir, "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp");
> 
> Maybe snprintf instead?
> 
> getenv() won't return anything longer than PATH_MAX for most
> users, but users are weird.

True.

> > +	if (!mkdtemp(tmpdir))
> > +		return error("could not create temporary directory");
> 
> Mention the tmpdir here?

Sure thing.

> > +	strbuf_addf(&ldir, "%s/left/", tmpdir);
> > +	strbuf_addf(&rdir, "%s/right/", tmpdir);
> > +	strbuf_addstr(&wtdir, workdir);
> > +	if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))
> > +		strbuf_addch(&wtdir, '/');
> > +	mkdir(ldir.buf, 0777);
> > +	mkdir(rdir.buf, 0777);
> 
> Seeing the perl mkpath() default 0777 spelled out this way
> makes me wonder whether 0700 would be safer.
> 
> The mkdtemp() above is already using 0700 so it's ok, but it
> might be worth making it consistent (later, perhaps).

Ah, of course! I stupidly imitated other `mkdir()` calls elsewhere, but
they refer to directories within the Git worktree...

> > +	/*
> > +	 * In directory diff mode, 'git-difftool--helper' is called once
> > +	 * to compare the a / b directories.In file diff mode, 'git diff'
> > +	 * will invoke a separate instance of 'git-difftool--helper' for
> > +	 * each file that changed.
> > +	 */
> 
> Missing space after "." in the comment above.

Yep. It was two spaces and I deleted one too many (we are so way past
actual print, where the two spaces may have made sense...).

> > +	if (dir_diff)
> > +		return run_dir_diff(extcmd, symlinks, argc, argv);
> > +	return run_file_diff(prompt, argc, argv);
> > +}
> > diff --git a/git.c b/git.c
> > index efa1059..eaa0f67 100644
> > --- a/git.c
> > +++ b/git.c
> > @@ -424,6 +424,7 @@ static struct cmd_struct commands[] = {
> >  	{ "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
> >  	{ "diff-index", cmd_diff_index, RUN_SETUP },
> >  	{ "diff-tree", cmd_diff_tree, RUN_SETUP },
> > +	{ "builtin-difftool", cmd_builtin_difftool, RUN_SETUP | NEED_WORK_TREE },
> >  	{ "fast-export", cmd_fast_export, RUN_SETUP },
> >  	{ "fetch", cmd_fetch, RUN_SETUP },
> >  	{ "fetch-pack", cmd_fetch_pack, RUN_SETUP },
> 
> This isn't alphabetical anymore, but it actually is if you
> consider that the final plan is to change "builtin-difftool" to
> "difftool".

Exactly, that was my thinking.

> If we want to minimize that future diff we could name
> cmd_builtin_difftool() as cmd_difftool() for consistency now so
> that the future commit only needs to tweak the string here.

Yes!

For the record, this is a left-over from an impatient attempt at avoiding
problems with `make` overwriting the Perl version of `git difftool` by the
builtin version; I had originally assumed that a list of builtins was
generated from parsing git.c or builtin.h, but it turns out that the
BUILTIN_OBJS are actually responsible, i.e. the file name.

Fixed.

Thank you for your review!
Dscho

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

* Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version
  2016-11-22 17:01 ` [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version Johannes Schindelin
@ 2016-11-23 14:51   ` Dennis Kaarsemaker
  2016-11-23 17:29     ` Johannes Schindelin
  0 siblings, 1 reply; 86+ messages in thread
From: Dennis Kaarsemaker @ 2016-11-23 14:51 UTC (permalink / raw)
  To: Johannes Schindelin, git; +Cc: Junio C Hamano

On Tue, 2016-11-22 at 18:01 +0100, Johannes Schindelin wrote:
> The original idea was to use an environment variable
> GIT_USE_BUILTIN_DIFFTOOL, but the test suite resets those variables, and
> we do want to use that feature flag to run the tests with, and without,
> the feature flag.
> 
> Besides, the plan is to add an opt-in flag in Git for Windows'
> installer. If we implemented the feature flag as an environment
> variable, we would have to modify the user's environment, in order to
> make the builtin difftool the default when called from Git Bash, Git CMD
> or third-party tools.

Hi Johannes,

Why is this not a normal configuration variable (as in git config
difftool.builtin true or something)? It doesn't make much sense to me
to introduce a way of configuring git by introducing magic files, when
a normal configuration variable would do just fine, and the GfW
installer can also set such variables, like it does for the crlf config
I believe.

-- 
Dennis Kaarsemaker
http://www.kaarsemaker.net

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

* Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version
  2016-11-23 14:51   ` Dennis Kaarsemaker
@ 2016-11-23 17:29     ` Johannes Schindelin
  2016-11-23 17:40       ` Junio C Hamano
  2016-11-23 22:01       ` Johannes Schindelin
  0 siblings, 2 replies; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-23 17:29 UTC (permalink / raw)
  To: Dennis Kaarsemaker; +Cc: git, Junio C Hamano

Hi Dennis,

On Wed, 23 Nov 2016, Dennis Kaarsemaker wrote:

> On Tue, 2016-11-22 at 18:01 +0100, Johannes Schindelin wrote:
> > The original idea was to use an environment variable
> > GIT_USE_BUILTIN_DIFFTOOL, but the test suite resets those variables, and
> > we do want to use that feature flag to run the tests with, and without,
> > the feature flag.
> > 
> > Besides, the plan is to add an opt-in flag in Git for Windows'
> > installer. If we implemented the feature flag as an environment
> > variable, we would have to modify the user's environment, in order to
> > make the builtin difftool the default when called from Git Bash, Git CMD
> > or third-party tools.
> 
> Why is this not a normal configuration variable (as in git config
> difftool.builtin true or something)? It doesn't make much sense to me
> to introduce a way of configuring git by introducing magic files, when
> a normal configuration variable would do just fine, and the GfW
> installer can also set such variables, like it does for the crlf config
> I believe.

I considered that. Adding a config setting would mean we simply test for
it in git-difftool.perl and call the builtin if the setting is active,
right?

The downside is that we actually *do* go through Perl to do that. Only to
go back to a builtin. Which is exactly the thing I intended to avoid.

If we do not go through Perl, we have to set up the git directory and
parse the config in git.c *just* to figure out whether we want to
magically forward difftool to builtin-difftool. That is not only ugly, but
has potential side effects I was not willing to risk.

In any case, this feature flag will be there only for one or two Git for
Windows releases, to give early adopters a chance to send me bug reports
about any regressions.

To be crystal-clear: I never expected this patch to enter git.git.

In that light, I am okay with taking the heat for introducing a temporary,
Git for Windows-only feature flag that is implemented as a "does the file
<xyz> exist?" test.

Ciao,
Dscho

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

* Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version
  2016-11-23 17:29     ` Johannes Schindelin
@ 2016-11-23 17:40       ` Junio C Hamano
  2016-11-23 18:18         ` Junio C Hamano
  2016-11-23 22:01       ` Johannes Schindelin
  1 sibling, 1 reply; 86+ messages in thread
From: Junio C Hamano @ 2016-11-23 17:40 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Dennis Kaarsemaker, git

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

> The downside is that we actually *do* go through Perl to do that. Only to
> go back to a builtin. Which is exactly the thing I intended to avoid.
>
> If we do not go through Perl, we have to set up the git directory and
> parse the config in git.c *just* to figure out whether we want to
> magically forward difftool to builtin-difftool. That is not only ugly, but
> has potential side effects I was not willing to risk.

I won't accept the latter anyway, so do not worry ;-)

> In any case, this feature flag will be there only for one or two Git for
> Windows releases, to give early adopters a chance to send me bug reports
> about any regressions.

I think that is sensible.  I suspect that for early detection of
breakages, you do not need to invent and force people to use a
completely new "mechanism" to switch between two implementations.

Can't you route the control upon seeing "git difftool" to your
experimental "C" difftool and check the configuration there?  Then
you can decide to run_command() a non-builtin one depending what the
configuration says---that way, you would incur cost of spawning Perl
only when you need it, no?


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

* Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version
  2016-11-23 17:40       ` Junio C Hamano
@ 2016-11-23 18:18         ` Junio C Hamano
  2016-11-23 19:55           ` Johannes Schindelin
  0 siblings, 1 reply; 86+ messages in thread
From: Junio C Hamano @ 2016-11-23 18:18 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Dennis Kaarsemaker, git

Junio C Hamano <gitster@pobox.com> writes:

> Can't you route the control upon seeing "git difftool" to your
> experimental "C" difftool and check the configuration there?  Then
> you can decide to run_command() a non-builtin one depending what the
> configuration says---that way, you would incur cost of spawning Perl
> only when you need it, no?

FWIW, the approach taken by 73c2779f42 ("builtin-am: implement
skeletal builtin am", 2015-08-04) is what I had in mind when I wrote
the above.

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

* Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version
  2016-11-23 18:18         ` Junio C Hamano
@ 2016-11-23 19:55           ` Johannes Schindelin
  2016-11-23 20:04             ` Junio C Hamano
  0 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-23 19:55 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Dennis Kaarsemaker, git

Hi Junio,

On Wed, 23 Nov 2016, Junio C Hamano wrote:

> Junio C Hamano <gitster@pobox.com> writes:
> 
> > Can't you route the control upon seeing "git difftool" to your
> > experimental "C" difftool and check the configuration there?  Then
> > you can decide to run_command() a non-builtin one depending what the
> > configuration says---that way, you would incur cost of spawning Perl
> > only when you need it, no?
> 
> FWIW, the approach taken by 73c2779f42 ("builtin-am: implement
> skeletal builtin am", 2015-08-04) is what I had in mind when I wrote
> the above.

Maybe that worked back then. But I doubt it, because checking out that
revision, I get this "warning":

Makefile:1732: warning: overriding recipe for target 'git-am'
Makefile:1696: warning: ignoring old recipe for target 'git-am'

Seems like a matter of luck whether the `make` executable you happen to
use guesses what we want: to munge git-am.sh into git-am, as opposed to
hard-linking git to git-am.

Ciao,
Dscho

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

* Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version
  2016-11-23 19:55           ` Johannes Schindelin
@ 2016-11-23 20:04             ` Junio C Hamano
  0 siblings, 0 replies; 86+ messages in thread
From: Junio C Hamano @ 2016-11-23 20:04 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Dennis Kaarsemaker, git

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

> Hi Junio,
>
> On Wed, 23 Nov 2016, Junio C Hamano wrote:
>
>> Junio C Hamano <gitster@pobox.com> writes:
>> 
>> > Can't you route the control upon seeing "git difftool" to your
>> > experimental "C" difftool and check the configuration there?  Then
>> > you can decide to run_command() a non-builtin one depending what the
>> > configuration says---that way, you would incur cost of spawning Perl
>> > only when you need it, no?
>> 
>> FWIW, the approach taken by 73c2779f42 ("builtin-am: implement
>> skeletal builtin am", 2015-08-04) is what I had in mind when I wrote
>> the above.
>
> Maybe that worked back then. But I doubt it, because checking out that
> revision, I get this "warning":
>
> Makefile:1732: warning: overriding recipe for target 'git-am'
> Makefile:1696: warning: ignoring old recipe for target 'git-am'
>
> Seems like a matter of luck whether the `make` executable you happen to
> use guesses what we want: to munge git-am.sh into git-am, as opposed to
> hard-linking git to git-am.

You do not need to keep two copies of "git-cmd", though.  commands[]
table can have an entry "difftool" that points at cmd_difftool(),
which switches between run_command("difftool-scripted") or makes a
function call to a static difftool_builtin() that you wrote in 1/2.

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

* Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version
  2016-11-23 17:29     ` Johannes Schindelin
  2016-11-23 17:40       ` Junio C Hamano
@ 2016-11-23 22:01       ` Johannes Schindelin
  1 sibling, 0 replies; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-23 22:01 UTC (permalink / raw)
  To: Dennis Kaarsemaker; +Cc: git, Junio C Hamano

Hi Dennis,

On Wed, 23 Nov 2016, Johannes Schindelin wrote:

> On Wed, 23 Nov 2016, Dennis Kaarsemaker wrote:
> 
> > On Tue, 2016-11-22 at 18:01 +0100, Johannes Schindelin wrote:
> > > The original idea was to use an environment variable
> > > GIT_USE_BUILTIN_DIFFTOOL, but the test suite resets those variables, and
> > > we do want to use that feature flag to run the tests with, and without,
> > > the feature flag.
> > > 
> > > Besides, the plan is to add an opt-in flag in Git for Windows'
> > > installer. If we implemented the feature flag as an environment
> > > variable, we would have to modify the user's environment, in order to
> > > make the builtin difftool the default when called from Git Bash, Git CMD
> > > or third-party tools.
> > 
> > Why is this not a normal configuration variable (as in git config
> > difftool.builtin true or something)? It doesn't make much sense to me
> > to introduce a way of configuring git by introducing magic files, when
> > a normal configuration variable would do just fine, and the GfW
> > installer can also set such variables, like it does for the crlf config
> > I believe.
> 
> I considered that. Adding a config setting would mean we simply test for
> it in git-difftool.perl and call the builtin if the setting is active,
> right?
> 
> The downside is that we actually *do* go through Perl to do that. Only to
> go back to a builtin. Which is exactly the thing I intended to avoid.

Okay, I reconsidered. Junio's comment about how git-am did it made me
rethink the issue: I need not keep the name "difftool" for the script. So
what I do now is rename the Perl script to git-legacy-difftool and always
read the config in the builtin difftool, handing off to the legacy
difftool unless core.useBuiltinDifftool=true.

This is an easy way to do it, and a portable and clean blueprint for
similar feature-flags in the future.

Ciao,
Johannes

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

* [PATCH v2 0/1] Show Git Mailing List: a builtin difftool
  2016-11-22 17:01 [PATCH 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin
  2016-11-22 17:01 ` [PATCH 1/2] difftool: add the builtin Johannes Schindelin
  2016-11-22 17:01 ` [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version Johannes Schindelin
@ 2016-11-23 22:03 ` Johannes Schindelin
  2016-11-23 22:03   ` [PATCH v2 1/1] difftool: add the builtin Johannes Schindelin
  2016-11-24 20:55   ` [PATCH v3 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin
  2 siblings, 2 replies; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-23 22:03 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker

I have been working on the builtin difftool for almost two weeks,
for two reasons:

1. Perl is really not native on Windows. Not only is there a performance
   penalty to be paid just for running Perl scripts, we also have to deal
   with the fact that users may have different Perl installations, with
   different options, and some other Perl installation may decide to set
   PERL5LIB globally, wreaking havoc with Git for Windows' Perl (which we
   have to use because almost all other Perl distributions lack the
   Subversion bindings we need for `git svn`).

2. Perl makes for a rather large reason that Git for Windows' installer
   weighs in with >30MB. While one Perl script less does not relieve us
   of that burden, it is one step in the right direction.

This patch serves two purposes: to ask for reviews, and to show what I
plan to release as part of Git for Windows v2.11.0 (which is due this
coming Wednesday, if Git v2.11.0 is released on Tuesday, as planned).

Changes since v1:

- fixed the usage (pointed out by David Aguilar)

- moved the stray #include "dir.h" to cuddle with the other #include's
  (also pointed out by David Aguilar)

- changed the `sprintf()` call to a much safer `xsnprintf()` call
  (again, pointed out by David Aguilar)

- changed an error message to include the offending path (need I say,
  pointed out by David Aguilar?)

- used more restrictive permissions for the temporary directories (once
  again, David Aguilar's suggestion)

- fixed a comment that lacked a space after a period (another fix thanks
  to David Aguilar).

- switched the opt-in feature flag triggering the use of the builtin
  difftool to a config variable (this suggestion came from Junio
  Hamano).

- made difftool respect core.symlinks by moving the usage of
  has_symlinks after the config was parsed.


Johannes Schindelin (1):
  difftool: add the builtin

 .gitignore                                    |   1 +
 Makefile                                      |   3 +-
 builtin.h                                     |   1 +
 builtin/difftool.c                            | 692 ++++++++++++++++++++++++++
 git-difftool.perl => git-legacy-difftool.perl |   0
 git.c                                         |   1 +
 6 files changed, 697 insertions(+), 1 deletion(-)
 create mode 100644 builtin/difftool.c
 rename git-difftool.perl => git-legacy-difftool.perl (100%)


base-commit: 1e37181391e305a7ab0c382ca3c3b2de998d4138
Published-As: https://github.com/dscho/git/releases/tag/builtin-difftool-v2
Fetch-It-Via: git fetch https://github.com/dscho/git builtin-difftool-v2

Interdiff vs v1:

 diff --git a/.gitignore b/.gitignore
 index 91bfd09..f96e50e 100644
 --- a/.gitignore
 +++ b/.gitignore
 @@ -1,4 +1,3 @@
 -/use-builtin-difftool
  /GIT-BUILD-OPTIONS
  /GIT-CFLAGS
  /GIT-LDFLAGS
 @@ -52,7 +51,6 @@
  /git-diff-tree
  /git-difftool
  /git-difftool--helper
 -/git-builtin-difftool
  /git-describe
  /git-fast-export
  /git-fast-import
 @@ -78,6 +76,7 @@
  /git-init-db
  /git-interpret-trailers
  /git-instaweb
 +/git-legacy-difftool
  /git-log
  /git-ls-files
  /git-ls-remote
 diff --git a/Makefile b/Makefile
 index f764174..7863bc2 100644
 --- a/Makefile
 +++ b/Makefile
 @@ -527,7 +527,7 @@ SCRIPT_LIB += git-sh-setup
  SCRIPT_LIB += git-sh-i18n
  
  SCRIPT_PERL += git-add--interactive.perl
 -SCRIPT_PERL += git-difftool.perl
 +SCRIPT_PERL += git-legacy-difftool.perl
  SCRIPT_PERL += git-archimport.perl
  SCRIPT_PERL += git-cvsexportcommit.perl
  SCRIPT_PERL += git-cvsimport.perl
 @@ -888,7 +888,7 @@ BUILTIN_OBJS += builtin/diff-files.o
  BUILTIN_OBJS += builtin/diff-index.o
  BUILTIN_OBJS += builtin/diff-tree.o
  BUILTIN_OBJS += builtin/diff.o
 -BUILTIN_OBJS += builtin/builtin-difftool.o
 +BUILTIN_OBJS += builtin/difftool.o
  BUILTIN_OBJS += builtin/fast-export.o
  BUILTIN_OBJS += builtin/fetch-pack.o
  BUILTIN_OBJS += builtin/fetch.o
 diff --git a/builtin.h b/builtin.h
 index 409a61e..67f8051 100644
 --- a/builtin.h
 +++ b/builtin.h
 @@ -60,7 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
  extern int cmd_diff_index(int argc, const char **argv, const char *prefix);
  extern int cmd_diff(int argc, const char **argv, const char *prefix);
  extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
 -extern int cmd_builtin_difftool(int argc, const char **argv, const char *prefix);
 +extern int cmd_difftool(int argc, const char **argv, const char *prefix);
  extern int cmd_fast_export(int argc, const char **argv, const char *prefix);
  extern int cmd_fetch(int argc, const char **argv, const char *prefix);
  extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);
 diff --git a/builtin/builtin-difftool.c b/builtin/difftool.c
 similarity index 95%
 rename from builtin/builtin-difftool.c
 rename to builtin/difftool.c
 index 9feefcd..f845879 100644
 --- a/builtin/builtin-difftool.c
 +++ b/builtin/difftool.c
 @@ -18,12 +18,15 @@
  #include "argv-array.h"
  #include "strbuf.h"
  #include "lockfile.h"
 +#include "dir.h"
 +#include "exec_cmd.h"
  
  static char *diff_gui_tool;
  static int trust_exit_code;
 +static int use_builtin_difftool;
  
 -static const char * const builtin_difftool_usage[] = {
 -	N_("git add [<options>] [--] <pathspec>..."),
 +static const char *const builtin_difftool_usage[] = {
 +	N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"),
  	NULL
  };
  
 @@ -39,6 +42,11 @@ static int difftool_config(const char *var, const char *value, void *cb)
  		return 0;
  	}
  
 +	if (!strcmp(var, "core.usebuiltindifftool")) {
 +		use_builtin_difftool = git_config_bool(var, value);
 +		return 0;
 +	}
 +
  	return git_default_config(var, value, cb);
  }
  
 @@ -227,8 +235,6 @@ static void changed_files(struct hashmap *result, const char *index_path,
  	strbuf_release(&buf);
  }
  
 -#include "dir.h"
 -
  static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)
  {
  	struct strbuf buf = STRBUF_INIT;
 @@ -282,16 +288,16 @@ static int run_dir_diff(const char *extcmd, int symlinks,
  
  	/* Setup temp directories */
  	tmp = getenv("TMPDIR");
 -	sprintf(tmpdir, "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp");
 +	xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp");
  	if (!mkdtemp(tmpdir))
 -		return error("could not create temporary directory");
 +		return error("could not create '%s'", tmpdir);
  	strbuf_addf(&ldir, "%s/left/", tmpdir);
  	strbuf_addf(&rdir, "%s/right/", tmpdir);
  	strbuf_addstr(&wtdir, workdir);
  	if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))
  		strbuf_addch(&wtdir, '/');
 -	mkdir(ldir.buf, 0777);
 -	mkdir(rdir.buf, 0777);
 +	mkdir(ldir.buf, 0700);
 +	mkdir(rdir.buf, 0700);
  
  	memset(&wtindex, 0, sizeof(wtindex));
  
 @@ -606,12 +612,11 @@ static int run_file_diff(int prompt, int argc, const char **argv)
  	exit(ret);
  }
  
 -int cmd_builtin_difftool(int argc, const char ** argv, const char * prefix)
 +int cmd_difftool(int argc, const char ** argv, const char * prefix)
  {
  	int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,
  	    tool_help = 0;
  	static char *difftool_cmd = NULL, *extcmd = NULL;
 -
  	struct option builtin_difftool_options[] = {
  		OPT_BOOL('g', "gui", &use_gui_tool,
  			 N_("use `diff.guitool` instead of `diff.tool`")),
 @@ -638,9 +643,16 @@ int cmd_builtin_difftool(int argc, const char ** argv, const char * prefix)
  		OPT_END()
  	};
  
 +	git_config(difftool_config, NULL);
  	symlinks = has_symlinks;
 +	if (!use_builtin_difftool) {
 +		const char *path = mkpath("%s/git-legacy-difftool", git_exec_path());
  
 -	git_config(difftool_config, NULL);
 +		if (sane_execvp(path, (char **)argv) < 0)
 +			die_errno("could not exec %s", path);
 +
 +		return 0;
 +	}
  
  	argc = parse_options(argc, argv, prefix, builtin_difftool_options,
  			     builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |
 @@ -670,7 +682,7 @@ int cmd_builtin_difftool(int argc, const char ** argv, const char * prefix)
  
  	/*
  	 * In directory diff mode, 'git-difftool--helper' is called once
 -	 * to compare the a / b directories.In file diff mode, 'git diff'
 +	 * to compare the a / b directories. In file diff mode, 'git diff'
  	 * will invoke a separate instance of 'git-difftool--helper' for
  	 * each file that changed.
  	 */
 diff --git a/git-difftool.perl b/git-legacy-difftool.perl
 similarity index 98%
 rename from git-difftool.perl
 rename to git-legacy-difftool.perl
 index 28e47d8..a5790d0 100755
 --- a/git-difftool.perl
 +++ b/git-legacy-difftool.perl
 @@ -23,13 +23,6 @@ use File::Temp qw(tempdir);
  use Getopt::Long qw(:config pass_through);
  use Git;
  
 -if (-e Git::exec_path() . '/use-builtin-difftool') {
 -	unshift(@ARGV, "builtin-difftool");
 -	unshift(@ARGV, "git");
 -	exec(@ARGV);
 -	die("Could not execute builtin difftool");
 -}
 -
  sub usage
  {
  	my $exitcode = shift;
 diff --git a/git.c b/git.c
 index 7a0df7a..0e6bbee 100644
 --- a/git.c
 +++ b/git.c
 @@ -2,7 +2,6 @@
  #include "exec_cmd.h"
  #include "help.h"
  #include "run-command.h"
 -#include "dir.h"
  
  const char git_usage_string[] =
  	"git [--version] [--help] [-C <path>] [-c name=value]\n"
 @@ -425,7 +424,7 @@ static struct cmd_struct commands[] = {
  	{ "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
  	{ "diff-index", cmd_diff_index, RUN_SETUP },
  	{ "diff-tree", cmd_diff_tree, RUN_SETUP },
 -	{ "builtin-difftool", cmd_builtin_difftool, RUN_SETUP | NEED_WORK_TREE },
 +	{ "difftool", cmd_difftool, RUN_SETUP | NEED_WORK_TREE },
  	{ "fast-export", cmd_fast_export, RUN_SETUP },
  	{ "fetch", cmd_fetch, RUN_SETUP },
  	{ "fetch-pack", cmd_fetch_pack, RUN_SETUP },
 @@ -543,22 +542,6 @@ static void strip_extension(const char **argv)
  #define strip_extension(cmd)
  #endif
  
 -static int use_builtin_difftool(void)
 -{
 -	static int initialized, use;
 -
 -	if (!initialized) {
 -		struct strbuf buf = STRBUF_INIT;
 -		strbuf_addf(&buf, "%s/%s", git_exec_path(),
 -			    "use-builtin-difftool");
 -		use = file_exists(buf.buf);
 -		strbuf_release(&buf);
 -		initialized = 1;
 -	}
 -
 -	return use;
 -}
 -
  static void handle_builtin(int argc, const char **argv)
  {
  	struct argv_array args = ARGV_ARRAY_INIT;
 @@ -568,9 +551,6 @@ static void handle_builtin(int argc, const char **argv)
  	strip_extension(argv);
  	cmd = argv[0];
  
 -	if (!strcmp("difftool", cmd) && use_builtin_difftool())
 -		cmd = "builtin-difftool";
 -
  	/* Turn "git cmd --help" into "git help --exclude-guides cmd" */
  	if (argc > 1 && !strcmp(argv[1], "--help")) {
  		int i;

-- 
2.10.1.583.g721a9e0


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

* [PATCH v2 1/1] difftool: add the builtin
  2016-11-23 22:03 ` [PATCH v2 0/1] Show Git Mailing List: a builtin difftool Johannes Schindelin
@ 2016-11-23 22:03   ` Johannes Schindelin
  2016-11-23 22:25     ` Junio C Hamano
  2016-11-24 20:55   ` [PATCH v3 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin
  1 sibling, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-23 22:03 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker

This adds a builtin difftool that represents a conversion of the current
Perl script version of the difftool.

The motivation is that Perl scripts are not at all native on Windows,
and that `git difftool` therefore is pretty slow on that platform, when
there is no good reason for it to be slow.

In addition, Perl does not really have access to Git's internals. That
means that any script will always have to jump through unnecessary
hoops.

The current version of the builtin difftool does not, however, make full
use of the internals but instead chooses to spawn a couple of Git
processes, still, to make for an easier conversion. There remains a lot
of room for improvement, left for a later date.

Note: the original difftool is now called `git legacy-difftool`, but to
play it safe, it is still called by difftool unless the config setting
core.useBuiltinDifftool=true.

The reason: this new, experimental, builtin difftool will be shipped as
part of Git for Windows v2.11.0, to allow for easier large-scale
testing, but of course as an opt-in feature.

Sadly, the speedup is more noticable on Linux than on Windows: a quick
test shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s)
(real/user/sys) in a Linux VM, down from  (6.529s/3.112s/0.644s), while
on Windows, it is (36.064s/2.730s/7.194s), down from
(47.637s/2.407s/6.863s). The culprit is most likely the overhead
incurred from *still* having to shell out to mergetool-lib.sh and
difftool--helper.sh.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitignore                                    |   1 +
 Makefile                                      |   3 +-
 builtin.h                                     |   1 +
 builtin/difftool.c                            | 692 ++++++++++++++++++++++++++
 git-difftool.perl => git-legacy-difftool.perl |   0
 git.c                                         |   1 +
 6 files changed, 697 insertions(+), 1 deletion(-)
 create mode 100644 builtin/difftool.c
 rename git-difftool.perl => git-legacy-difftool.perl (100%)

diff --git a/.gitignore b/.gitignore
index 05cb58a..f96e50e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -76,6 +76,7 @@
 /git-init-db
 /git-interpret-trailers
 /git-instaweb
+/git-legacy-difftool
 /git-log
 /git-ls-files
 /git-ls-remote
diff --git a/Makefile b/Makefile
index f53fcc9..7863bc2 100644
--- a/Makefile
+++ b/Makefile
@@ -527,7 +527,7 @@ SCRIPT_LIB += git-sh-setup
 SCRIPT_LIB += git-sh-i18n
 
 SCRIPT_PERL += git-add--interactive.perl
-SCRIPT_PERL += git-difftool.perl
+SCRIPT_PERL += git-legacy-difftool.perl
 SCRIPT_PERL += git-archimport.perl
 SCRIPT_PERL += git-cvsexportcommit.perl
 SCRIPT_PERL += git-cvsimport.perl
@@ -888,6 +888,7 @@ BUILTIN_OBJS += builtin/diff-files.o
 BUILTIN_OBJS += builtin/diff-index.o
 BUILTIN_OBJS += builtin/diff-tree.o
 BUILTIN_OBJS += builtin/diff.o
+BUILTIN_OBJS += builtin/difftool.o
 BUILTIN_OBJS += builtin/fast-export.o
 BUILTIN_OBJS += builtin/fetch-pack.o
 BUILTIN_OBJS += builtin/fetch.o
diff --git a/builtin.h b/builtin.h
index b9122bc..67f8051 100644
--- a/builtin.h
+++ b/builtin.h
@@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_index(int argc, const char **argv, const char *prefix);
 extern int cmd_diff(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_difftool(int argc, const char **argv, const char *prefix);
 extern int cmd_fast_export(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);
diff --git a/builtin/difftool.c b/builtin/difftool.c
new file mode 100644
index 0000000..f845879
--- /dev/null
+++ b/builtin/difftool.c
@@ -0,0 +1,692 @@
+/*
+ * "git difftool" builtin command
+ *
+ * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
+ * git-difftool--helper script.
+ *
+ * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.
+ * The GIT_DIFF* variables are exported for use by git-difftool--helper.
+ *
+ * Any arguments that are unknown to this script are forwarded to 'git diff'.
+ *
+ * Copyright (C) 2016 Johannes Schindelin
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+#include "run-command.h"
+#include "argv-array.h"
+#include "strbuf.h"
+#include "lockfile.h"
+#include "dir.h"
+#include "exec_cmd.h"
+
+static char *diff_gui_tool;
+static int trust_exit_code;
+static int use_builtin_difftool;
+
+static const char *const builtin_difftool_usage[] = {
+	N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"),
+	NULL
+};
+
+static int difftool_config(const char *var, const char *value, void *cb)
+{
+	if (!strcmp(var, "diff.guitool")) {
+		diff_gui_tool = xstrdup(value);
+		return 0;
+	}
+
+	if (!strcmp(var, "difftool.trustexitcode")) {
+		trust_exit_code = git_config_bool(var, value);
+		return 0;
+	}
+
+	if (!strcmp(var, "core.usebuiltindifftool")) {
+		use_builtin_difftool = git_config_bool(var, value);
+		return 0;
+	}
+
+	return git_default_config(var, value, cb);
+}
+
+static int print_tool_help(void)
+{
+	const char *argv[] = { "mergetool", "--tool-help=diff", NULL };
+	return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
+static int parse_index_info(char *p, int *mode1, int *mode2,
+			    struct object_id *oid1, struct object_id *oid2,
+			    char *status)
+{
+	if (*p != ':')
+		return error("expected ':', got '%c'", *p);
+	*mode1 = (int)strtol(p + 1, &p, 8);
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	*mode2 = (int)strtol(p + 1, &p, 8);
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	if (get_oid_hex(++p, oid1))
+		return error("expected object ID, got '%s'", p + 1);
+	p += GIT_SHA1_HEXSZ;
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	if (get_oid_hex(++p, oid2))
+		return error("expected object ID, got '%s'", p + 1);
+	p += GIT_SHA1_HEXSZ;
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	*status = *++p;
+	if (!status || p[1])
+		return error("unexpected trailer: '%s'", p);
+	return 0;
+}
+
+/*
+ * Remove any trailing slash from $workdir
+ * before starting to avoid double slashes in symlink targets.
+ */
+static void add_path(struct strbuf *buf, size_t base_len, const char *path)
+{
+	strbuf_setlen(buf, base_len);
+	if (buf->len && buf->buf[buf->len - 1] != '/')
+		strbuf_addch(buf, '/');
+	strbuf_addstr(buf, path);
+}
+
+/*
+ * Determine whether we can simply reuse the file in the worktree.
+ */
+static int use_wt_file(const char *workdir, const char *name,
+		       struct object_id *oid)
+{
+	struct strbuf buf = STRBUF_INIT;
+	struct stat st;
+	int use = 0;
+
+	strbuf_addstr(&buf, workdir);
+	add_path(&buf, buf.len, name);
+
+	if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) {
+		struct object_id wt_oid;
+		int fd = open(buf.buf, O_RDONLY);
+
+		if (!index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {
+			if (is_null_oid(oid)) {
+				oidcpy(oid, &wt_oid);
+				use = 1;
+			} else if (!oidcmp(oid, &wt_oid))
+				use = 1;
+		}
+	}
+
+	strbuf_release(&buf);
+
+	return use;
+}
+
+struct working_tree_entry {
+	struct hashmap_entry entry;
+	char path[FLEX_ARRAY];
+};
+
+static int working_tree_entry_cmp(struct working_tree_entry *a,
+				  struct working_tree_entry *b, void *keydata)
+{
+	return strcmp(a->path, b->path);
+}
+
+/*
+ * The `left` and `right` entries hold paths for the symlinks hashmap,
+ * and a SHA-1 surrounded by brief text for submodules.
+ */
+struct pair_entry {
+	struct hashmap_entry entry;
+	char left[PATH_MAX], right[PATH_MAX];
+	const char path[FLEX_ARRAY];
+};
+
+static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata)
+{
+	return strcmp(a->path, b->path);
+}
+
+static void add_left_or_right(struct hashmap *map, const char *path,
+			      const char *content, int is_right)
+{
+	struct pair_entry *e, *existing;
+
+	FLEX_ALLOC_STR(e, path, path);
+	hashmap_entry_init(e, strhash(path));
+	existing = hashmap_get(map, e, NULL);
+	if (existing) {
+		free(e);
+		e = existing;
+	} else {
+		e->left[0] = e->right[0] = '\0';
+		hashmap_add(map, e);
+	}
+	strcpy(is_right ? e->right : e->left, content);
+}
+
+struct path_entry {
+	struct hashmap_entry entry;
+	char path[FLEX_ARRAY];
+};
+
+int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key)
+{
+	return strcmp(a->path, key ? key : b->path);
+}
+
+static void changed_files(struct hashmap *result, const char *index_path,
+			  const char *workdir)
+{
+	struct child_process update_index = CHILD_PROCESS_INIT;
+	struct child_process diff_files = CHILD_PROCESS_INIT;
+	struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT;
+	const char *git_dir = absolute_path(get_git_dir()), *env[] = {
+		NULL, NULL
+	};
+	FILE *fp;
+
+	strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path);
+	env[0] = index_env.buf;
+
+	argv_array_pushl(&update_index.args,
+			 "--git-dir", git_dir, "--work-tree", workdir,
+			 "update-index", "--really-refresh", "-q",
+			 "--unmerged", NULL);
+	update_index.no_stdin = 1;
+	update_index.no_stdout = 1;
+	update_index.no_stderr = 1;
+	update_index.git_cmd = 1;
+	update_index.use_shell = 0;
+	update_index.clean_on_exit = 1;
+	update_index.dir = workdir;
+	update_index.env = env;
+	/* Ignore any errors of update-index */
+	run_command(&update_index);
+
+	argv_array_pushl(&diff_files.args,
+			 "--git-dir", git_dir, "--work-tree", workdir,
+			 "diff-files", "--name-only", "-z", NULL);
+	diff_files.no_stdin = 1;
+	diff_files.git_cmd = 1;
+	diff_files.use_shell = 0;
+	diff_files.clean_on_exit = 1;
+	diff_files.out = -1;
+	diff_files.dir = workdir;
+	diff_files.env = env;
+	if (start_command(&diff_files))
+		die("could not obtain raw diff");
+	fp = xfdopen(diff_files.out, "r");
+	while (!strbuf_getline_nul(&buf, fp)) {
+		struct path_entry *entry;
+		FLEX_ALLOC_STR(entry, path, buf.buf);
+		hashmap_entry_init(entry, strhash(buf.buf));
+		hashmap_add(result, entry);
+	}
+	if (finish_command(&diff_files))
+		die("diff-files did not exit properly");
+	strbuf_release(&index_env);
+	strbuf_release(&buf);
+}
+
+static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)
+{
+	struct strbuf buf = STRBUF_INIT;
+	strbuf_addstr(&buf, tmpdir);
+	remove_dir_recursively(&buf, 0);
+	if (exit_code)
+		warning(_("failed: %d"), exit_code);
+	exit(exit_code);
+}
+
+static int ensure_leading_directories(char *path)
+{
+	switch (safe_create_leading_directories(path)) {
+		case SCLD_OK:
+		case SCLD_EXISTS:
+			return 0;
+		default:
+			return error(_("could not create leading directories "
+				       "of '%s'"), path);
+	}
+}
+
+static int run_dir_diff(const char *extcmd, int symlinks,
+			int argc, const char **argv)
+{
+	char tmpdir[PATH_MAX];
+	struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;
+	struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;
+	struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;
+	struct strbuf wtdir = STRBUF_INIT;
+	size_t ldir_len, rdir_len, wtdir_len;
+	struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1);
+	const char *workdir, *tmp;
+	int ret = 0, i;
+	FILE *fp;
+	struct hashmap working_tree_dups, submodules, symlinks2;
+	struct hashmap_iter iter;
+	struct pair_entry *entry;
+	enum object_type type;
+	unsigned long size;
+	struct index_state wtindex;
+	struct checkout lstate, rstate;
+	int rc, flags = RUN_GIT_CMD, err = 0;
+	struct child_process child = CHILD_PROCESS_INIT;
+	const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL };
+	struct hashmap wt_modified, tmp_modified;
+	int indices_loaded = 0;
+
+	setup_work_tree();
+	workdir = get_git_work_tree();
+
+	/* Setup temp directories */
+	tmp = getenv("TMPDIR");
+	xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp");
+	if (!mkdtemp(tmpdir))
+		return error("could not create '%s'", tmpdir);
+	strbuf_addf(&ldir, "%s/left/", tmpdir);
+	strbuf_addf(&rdir, "%s/right/", tmpdir);
+	strbuf_addstr(&wtdir, workdir);
+	if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))
+		strbuf_addch(&wtdir, '/');
+	mkdir(ldir.buf, 0700);
+	mkdir(rdir.buf, 0700);
+
+	memset(&wtindex, 0, sizeof(wtindex));
+
+	memset(&lstate, 0, sizeof(lstate));
+	lstate.base_dir = ldir.buf;
+	lstate.base_dir_len = ldir.len;
+	lstate.force = 1;
+	memset(&rstate, 0, sizeof(rstate));
+	rstate.base_dir = rdir.buf;
+	rstate.base_dir_len = rdir.len;
+	rstate.force = 1;
+
+	ldir_len = ldir.len;
+	rdir_len = rdir.len;
+	wtdir_len = wtdir.len;
+
+	hashmap_init(&working_tree_dups,
+		     (hashmap_cmp_fn)working_tree_entry_cmp, 0);
+	hashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0);
+	hashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0);
+
+	child.no_stdin = 1;
+	child.git_cmd = 1;
+	child.use_shell = 0;
+	child.clean_on_exit = 1;
+	child.out = -1;
+	argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z",
+			 NULL);
+	for (i = 0; i < argc; i++)
+		argv_array_push(&child.args, argv[i]);
+	if (start_command(&child))
+		die("could not obtain raw diff");
+	fp = xfdopen(child.out, "r");
+
+	/* Build index info for left and right sides of the diff */
+	while (!strbuf_getline_nul(&info, fp)) {
+		int lmode, rmode;
+		struct object_id loid, roid;
+		char status;
+		const char *src_path, *dst_path;
+		size_t src_path_len, dst_path_len;
+
+		if (starts_with(info.buf, "::"))
+			die(N_("combined diff formats('-c' and '--cc') are "
+			       "not supported in\n"
+			       "directory diff mode('-d' and '--dir-diff')."));
+
+		if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,
+				     &status))
+			break;
+		if (strbuf_getline_nul(&lpath, fp))
+			break;
+		src_path = lpath.buf;
+		src_path_len = lpath.len;
+
+		if (status != 'C' && status != 'R') {
+			dst_path = src_path;
+			dst_path_len = src_path_len;
+		} else {
+			if (strbuf_getline_nul(&rpath, fp))
+				break;
+			dst_path = rpath.buf;
+			dst_path_len = rpath.len;
+		}
+
+		if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) {
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "Subproject commit %s",
+				    oid_to_hex(&loid));
+			add_left_or_right(&submodules, src_path, buf.buf, 0);
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "Subproject commit %s",
+				    oid_to_hex(&roid));
+			if (!oidcmp(&loid, &roid))
+				strbuf_addstr(&buf, "-dirty");
+			add_left_or_right(&submodules, dst_path, buf.buf, 1);
+			continue;
+		}
+
+		if (S_ISLNK(lmode)) {
+			char *content = read_sha1_file(loid.hash, &type, &size);
+			add_left_or_right(&symlinks2, src_path, content, 0);
+			free(content);
+		}
+
+		if (S_ISLNK(rmode)) {
+			char *content = read_sha1_file(roid.hash, &type, &size);
+			add_left_or_right(&symlinks2, dst_path, content, 1);
+			free(content);
+		}
+
+		if (lmode && status != 'C') {
+			ce->ce_mode = lmode;
+			oidcpy(&ce->oid, &loid);
+			strcpy(ce->name, src_path);
+			ce->ce_namelen = src_path_len;
+			if (checkout_entry(ce, &lstate, NULL))
+				return error("could not write '%s'", src_path);
+		}
+
+		if (rmode) {
+			struct working_tree_entry *entry;
+
+			/* Avoid duplicate working_tree entries */
+			FLEX_ALLOC_STR(entry, path, dst_path);
+			hashmap_entry_init(entry, strhash(dst_path));
+			if (hashmap_get(&working_tree_dups, entry, NULL)) {
+				free(entry);
+				continue;
+			}
+			hashmap_add(&working_tree_dups, entry);
+
+			if (!use_wt_file(workdir, dst_path, &roid)) {
+				ce->ce_mode = rmode;
+				oidcpy(&ce->oid, &roid);
+				strcpy(ce->name, dst_path);
+				ce->ce_namelen = dst_path_len;
+				if (checkout_entry(ce, &rstate, NULL))
+					return error("could not write '%s'",
+						     dst_path);
+			} else if (!is_null_oid(&roid)) {
+				/*
+				 * Changes in the working tree need special
+				 * treatment since they are not part of the
+				 * index.
+				 */
+				struct cache_entry *ce2 =
+					make_cache_entry(rmode, roid.hash,
+							 dst_path, 0, 0);
+				ce_mode_from_stat(ce2, rmode);
+
+				add_index_entry(&wtindex, ce2,
+						ADD_CACHE_JUST_APPEND);
+
+				add_path(&wtdir, wtdir_len, dst_path);
+				add_path(&rdir, rdir_len, dst_path);
+				if (ensure_leading_directories(rdir.buf))
+					return error("could not create "
+						     "directory for '%s'",
+						     dst_path);
+				if (symlinks) {
+					if (symlink(wtdir.buf, rdir.buf)) {
+						ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf);
+						goto finish;
+					}
+				} else {
+					struct stat st;
+					if (stat(wtdir.buf, &st))
+						st.st_mode = 0644;
+					if (copy_file(rdir.buf, wtdir.buf,
+						      st.st_mode)) {
+						ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf);
+						goto finish;
+					}
+				}
+			}
+		}
+	}
+	if (finish_command(&child)) {
+		ret = error("error occurred running diff --raw");
+		goto finish;
+	}
+
+	/*
+	 * Changes to submodules require special treatment.This loop writes a
+	 * temporary file to both the left and right directories to show the
+	 * change in the recorded SHA1 for the submodule.
+	 */
+	hashmap_iter_init(&submodules, &iter);
+	while ((entry = hashmap_iter_next(&iter))) {
+		if (*entry->left) {
+			add_path(&ldir, ldir_len, entry->path);
+			ensure_leading_directories(ldir.buf);
+			write_file(ldir.buf, "%s", entry->left);
+		}
+		if (*entry->right) {
+			add_path(&rdir, rdir_len, entry->path);
+			ensure_leading_directories(rdir.buf);
+			write_file(rdir.buf, "%s", entry->right);
+		}
+	}
+
+	/*
+	 * Symbolic links require special treatment.The standard "git diff"
+	 * shows only the link itself, not the contents of the link target.
+	 * This loop replicates that behavior.
+	 */
+	hashmap_iter_init(&symlinks2, &iter);
+	while ((entry = hashmap_iter_next(&iter))) {
+		if (*entry->left) {
+			add_path(&ldir, ldir_len, entry->path);
+			ensure_leading_directories(ldir.buf);
+			write_file(ldir.buf, "%s", entry->left);
+		}
+		if (*entry->right) {
+			add_path(&rdir, rdir_len, entry->path);
+			ensure_leading_directories(rdir.buf);
+			write_file(rdir.buf, "%s", entry->right);
+		}
+	}
+
+	strbuf_release(&buf);
+
+	strbuf_setlen(&ldir, ldir_len);
+	helper_argv[1] = ldir.buf;
+	strbuf_setlen(&rdir, rdir_len);
+	helper_argv[2] = rdir.buf;
+
+	if (extcmd) {
+		helper_argv[0] = extcmd;
+		flags = 0;
+	} else
+		setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1);
+	rc = run_command_v_opt(helper_argv, flags);
+
+	/*
+	 * If the diff includes working copy files and those
+	 * files were modified during the diff, then the changes
+	 * should be copied back to the working tree.
+	 * Do not copy back files when symlinks are used and the
+	 * external tool did not replace the original link with a file.
+	 *
+	 * These hashes are loaded lazily since they aren't needed
+	 * in the common case of --symlinks and the difftool updating
+	 * files through the symlink.
+	 */
+	hashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp,
+		     wtindex.cache_nr);
+	hashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp,
+		     wtindex.cache_nr);
+
+	for (i = 0; i < wtindex.cache_nr; i++) {
+		struct hashmap_entry dummy;
+		const char *name = wtindex.cache[i]->name;
+		struct stat st;
+
+		add_path(&rdir, rdir_len, name);
+		if (lstat(rdir.buf, &st))
+			continue;
+
+		if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode))
+			continue;
+
+		if (!indices_loaded) {
+			static struct lock_file lock;
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "%s/wtindex", tmpdir);
+			if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
+			    write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {
+				ret = error("could not write %s", buf.buf);
+				rollback_lock_file(&lock);
+				goto finish;
+			}
+			changed_files(&wt_modified, buf.buf, workdir);
+			strbuf_setlen(&rdir, rdir_len);
+			changed_files(&tmp_modified, buf.buf, rdir.buf);
+			add_path(&rdir, rdir_len, name);
+			indices_loaded = 1;
+		}
+
+		hashmap_entry_init(&dummy, strhash(name));
+		if (hashmap_get(&tmp_modified, &dummy, name)) {
+			add_path(&wtdir, wtdir_len, name);
+			if (hashmap_get(&wt_modified, &dummy, name)) {
+				warning(_("both files modified: '%s' and '%s'."),
+					wtdir.buf, rdir.buf);
+				warning(_("working tree file has been left."));
+				warning("");
+				err = 1;
+			} else if (unlink(wtdir.buf) ||
+				   copy_file(wtdir.buf, rdir.buf, st.st_mode))
+				warning_errno(_("could not copy '%s' to '%s'"),
+					      rdir.buf, wtdir.buf);
+		}
+	}
+
+	if (err) {
+		warning(_("temporary files exist in '%s'."), tmpdir);
+		warning(_("you may want to cleanup or recover these."));
+		exit(1);
+	} else
+		exit_cleanup(tmpdir, rc);
+
+finish:
+	free(ce);
+	strbuf_release(&ldir);
+	strbuf_release(&rdir);
+	strbuf_release(&wtdir);
+	strbuf_release(&buf);
+
+	return ret;
+}
+
+static int run_file_diff(int prompt, int argc, const char **argv)
+{
+	struct argv_array args = ARGV_ARRAY_INIT;
+	const char *env[] = {
+		"GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL,
+		NULL
+	};
+	int ret = 0, i;
+
+	if (prompt > 0)
+		env[2] = "GIT_DIFFTOOL_PROMPT=true";
+	else if (!prompt)
+		env[2] = "GIT_DIFFTOOL_NO_PROMPT=true";
+
+	argv_array_push(&args, "diff");
+	for (i = 0; i < argc; i++)
+		argv_array_push(&args, argv[i]);
+	ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, NULL, env);
+	exit(ret);
+}
+
+int cmd_difftool(int argc, const char ** argv, const char * prefix)
+{
+	int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,
+	    tool_help = 0;
+	static char *difftool_cmd = NULL, *extcmd = NULL;
+	struct option builtin_difftool_options[] = {
+		OPT_BOOL('g', "gui", &use_gui_tool,
+			 N_("use `diff.guitool` instead of `diff.tool`")),
+		OPT_BOOL('d', "dir-diff", &dir_diff,
+			 N_("perform a full-directory diff")),
+		{ OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL,
+			N_("do not prompt before launching a diff tool"),
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
+		{ OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL,
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
+			NULL, 1 },
+		OPT_BOOL(0, "symlinks", &symlinks,
+			 N_("use symlinks in dir-diff mode")),
+		OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"),
+			   N_("use the specified diff tool")),
+		OPT_BOOL(0, "tool-help", &tool_help,
+			 N_("print a list of diff tools that may be used with "
+			    "`--tool`")),
+		OPT_BOOL(0, "trust-exit-code", &trust_exit_code,
+			 N_("make 'git-difftool' exit when an invoked diff "
+			    "tool returns a non - zero exit code")),
+		OPT_STRING('x', "extcmd", &extcmd, N_("<command>"),
+			   N_("specify a custom command for viewing diffs")),
+		OPT_END()
+	};
+
+	git_config(difftool_config, NULL);
+	symlinks = has_symlinks;
+	if (!use_builtin_difftool) {
+		const char *path = mkpath("%s/git-legacy-difftool", git_exec_path());
+
+		if (sane_execvp(path, (char **)argv) < 0)
+			die_errno("could not exec %s", path);
+
+		return 0;
+	}
+
+	argc = parse_options(argc, argv, prefix, builtin_difftool_options,
+			     builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |
+			     PARSE_OPT_KEEP_DASHDASH);
+
+	if (tool_help)
+		return print_tool_help();
+
+	if (use_gui_tool && diff_gui_tool && *diff_gui_tool)
+		setenv("GIT_DIFF_TOOL", diff_gui_tool, 1);
+	else if (difftool_cmd) {
+		if (*difftool_cmd)
+			setenv("GIT_DIFF_TOOL", difftool_cmd, 1);
+		else
+			die(_("no <tool> given for --tool=<tool>"));
+	}
+
+	if (extcmd) {
+		if (*extcmd)
+			setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1);
+		else
+			die(_("no <cmd> given for --extcmd=<cmd>"));
+	}
+
+	setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE",
+	       trust_exit_code ? "true" : "false", 1);
+
+	/*
+	 * In directory diff mode, 'git-difftool--helper' is called once
+	 * to compare the a / b directories. In file diff mode, 'git diff'
+	 * will invoke a separate instance of 'git-difftool--helper' for
+	 * each file that changed.
+	 */
+	if (dir_diff)
+		return run_dir_diff(extcmd, symlinks, argc, argv);
+	return run_file_diff(prompt, argc, argv);
+}
diff --git a/git-difftool.perl b/git-legacy-difftool.perl
similarity index 100%
rename from git-difftool.perl
rename to git-legacy-difftool.perl
diff --git a/git.c b/git.c
index efa1059..0e6bbee 100644
--- a/git.c
+++ b/git.c
@@ -424,6 +424,7 @@ static struct cmd_struct commands[] = {
 	{ "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
 	{ "diff-index", cmd_diff_index, RUN_SETUP },
 	{ "diff-tree", cmd_diff_tree, RUN_SETUP },
+	{ "difftool", cmd_difftool, RUN_SETUP | NEED_WORK_TREE },
 	{ "fast-export", cmd_fast_export, RUN_SETUP },
 	{ "fetch", cmd_fetch, RUN_SETUP },
 	{ "fetch-pack", cmd_fetch_pack, RUN_SETUP },
-- 
2.10.1.583.g721a9e0

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

* Re: [PATCH v2 1/1] difftool: add the builtin
  2016-11-23 22:03   ` [PATCH v2 1/1] difftool: add the builtin Johannes Schindelin
@ 2016-11-23 22:25     ` Junio C Hamano
  2016-11-23 22:30       ` Junio C Hamano
  0 siblings, 1 reply; 86+ messages in thread
From: Junio C Hamano @ 2016-11-23 22:25 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git, David Aguilar, Dennis Kaarsemaker

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

> +	if (!strcmp(var, "core.usebuiltindifftool")) {
> +		use_builtin_difftool = git_config_bool(var, value);
> +		return 0;
> +	}

This no way belongs to the core set; difftool.usebuiltin would be
more appropriate.

> +	if (!use_builtin_difftool) {
> +		const char *path = mkpath("%s/git-legacy-difftool", git_exec_path());
> +
> +		if (sane_execvp(path, (char **)argv) < 0)
> +			die_errno("could not exec %s", path);
> +
> +		return 0;
> +	}
> + ...
> diff --git a/git-difftool.perl b/git-legacy-difftool.perl
> similarity index 100%
> rename from git-difftool.perl
> rename to git-legacy-difftool.perl
> diff --git a/git.c b/git.c
> index efa1059..0e6bbee 100644
> --- a/git.c
> +++ b/git.c
> @@ -424,6 +424,7 @@ static struct cmd_struct commands[] = {
>  	{ "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
>  	{ "diff-index", cmd_diff_index, RUN_SETUP },
>  	{ "diff-tree", cmd_diff_tree, RUN_SETUP },
> +	{ "difftool", cmd_difftool, RUN_SETUP | NEED_WORK_TREE },

Running set-up would mean that the spawning of legacy-difftool would
be done after you chdir(2) up to the root level of the working tree,
no?  I do not think you can safely add these two bits here until the
migration completes.

I doubt that setting core.usebuiltindifftool to false and running
the tool from a subdirectory and a pathspec work correctly with this
patch.  If running difftool from a subdirectory with a pathspec is
not tested in t7800, perhaps we should.

It is nice that we can now lose PERL prerequisite from t7800 ;-)

Thanks.

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

* Re: [PATCH v2 1/1] difftool: add the builtin
  2016-11-23 22:25     ` Junio C Hamano
@ 2016-11-23 22:30       ` Junio C Hamano
  2016-11-24 10:38         ` Johannes Schindelin
  0 siblings, 1 reply; 86+ messages in thread
From: Junio C Hamano @ 2016-11-23 22:30 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git, David Aguilar, Dennis Kaarsemaker

Junio C Hamano <gitster@pobox.com> writes:

> ... I do not think you can safely add these two bits here until the
> migration completes.

I accidentally removed a more useful bit I wrote after the above
sentence while editing.

The NEEDSWORK comment in 73c2779f42 ("builtin-am: implement skeletal
builtin am", 2015-08-04) mentions why it calls setup-git-directory
and setup-work-tree instead of letting run_builtin() do so; perhaps
you can do something similar here to fix this.

> I doubt that setting core.usebuiltindifftool to false and running
> the tool from a subdirectory and a pathspec work correctly with this
> patch.  If running difftool from a subdirectory with a pathspec is
> not tested in t7800, perhaps we should.
>
> It is nice that we can now lose PERL prerequisite from t7800 ;-)
>
> Thanks.

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

* Re: [PATCH v2 1/1] difftool: add the builtin
  2016-11-23 22:30       ` Junio C Hamano
@ 2016-11-24 10:38         ` Johannes Schindelin
  0 siblings, 0 replies; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-24 10:38 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, David Aguilar, Dennis Kaarsemaker

Hi Junio,

On Wed, 23 Nov 2016, Junio C Hamano wrote:

> Junio C Hamano <gitster@pobox.com> writes:
> 
> > ... I do not think you can safely add these two bits here until the
> > migration completes.
> 
> I accidentally removed a more useful bit I wrote after the above
> sentence while editing.
> 
> The NEEDSWORK comment in 73c2779f42 ("builtin-am: implement skeletal
> builtin am", 2015-08-04) mentions why it calls setup-git-directory
> and setup-work-tree instead of letting run_builtin() do so; perhaps
> you can do something similar here to fix this.

This is the Catch-22 I mentioned a couple times: if you insist on a config
setting, the config has to be read. For that to work,
setup_git_directory() has to be called.

So no matter what you do, if you want to have conditional code that
depends on the config, and that wants setup_git_directory() *not* to be
called before, you are simply out of luck.

Sadly, I now bought into your comment that using a file in exec-path as a
feature flag is a bad thing, and that we have to use a config setting. So
now I have to spend more time on fixing something that was not a problem
in my original patches.

However, this exchange has something else in it, apart from creating
unneeded work for me.

What you really accidentally did was to identify a fundamental problem
with the builtin difftool: when called from a subdirectory, the RUN_SETUP
flag would make it chdir() to the top-level directory, and the
subsequently spawned Git processes would get the wrong idea about relative
paths.

Thank you for that,
Dscho

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

* [PATCH v3 0/2] Show Git Mailing List: a builtin difftool
  2016-11-23 22:03 ` [PATCH v2 0/1] Show Git Mailing List: a builtin difftool Johannes Schindelin
  2016-11-23 22:03   ` [PATCH v2 1/1] difftool: add the builtin Johannes Schindelin
@ 2016-11-24 20:55   ` Johannes Schindelin
  2016-11-24 20:55     ` [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin Johannes Schindelin
                       ` (2 more replies)
  1 sibling, 3 replies; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-24 20:55 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker

I have been working on the builtin difftool for almost two weeks,
for two reasons:

1. Perl is really not native on Windows. Not only is there a performance
   penalty to be paid just for running Perl scripts, we also have to deal
   with the fact that users may have different Perl installations, with
   different options, and some other Perl installation may decide to set
   PERL5LIB globally, wreaking havoc with Git for Windows' Perl (which we
   have to use because almost all other Perl distributions lack the
   Subversion bindings we need for `git svn`).

2. Perl makes for a rather large reason that Git for Windows' installer
   weighs in with >30MB. While one Perl script less does not relieve us
   of that burden, it is one step in the right direction.

This patch serves two purposes: to ask for reviews, and to show what I
plan to release as part of Git for Windows v2.11.0 (which is due this
coming Wednesday, if Git v2.11.0 is released on Tuesday, as planned).

Changes since v2:

- adjusted the config setting's name according to Junio's concerns

- fixed launching difftool in a subdirectory

- fixed dir-diff mode when there are no changes (it did not exit early
  but tried to diff two empty directories)


Johannes Schindelin (2):
  difftool: add a skeleton for the upcoming builtin
  difftool: implement the functionality in the builtin

 .gitignore                                    |   1 +
 Makefile                                      |   3 +-
 builtin.h                                     |   1 +
 builtin/difftool.c                            | 731 ++++++++++++++++++++++++++
 git-difftool.perl => git-legacy-difftool.perl |   0
 git.c                                         |   6 +
 t/t7800-difftool.sh                           |   2 +
 7 files changed, 743 insertions(+), 1 deletion(-)
 create mode 100644 builtin/difftool.c
 rename git-difftool.perl => git-legacy-difftool.perl (100%)


base-commit: e2b2d6a172b76d44cb7b1ddb12ea5bfac9613a44
Published-As: https://github.com/dscho/git/releases/tag/builtin-difftool-v3
Fetch-It-Via: git fetch https://github.com/dscho/git builtin-difftool-v3

Interdiff vs v2:

 diff --git a/builtin/difftool.c b/builtin/difftool.c
 index f845879..3480920 100644
 --- a/builtin/difftool.c
 +++ b/builtin/difftool.c
 @@ -13,17 +13,16 @@
   */
  #include "cache.h"
  #include "builtin.h"
 -#include "parse-options.h"
  #include "run-command.h"
 +#include "exec_cmd.h"
 +#include "parse-options.h"
  #include "argv-array.h"
  #include "strbuf.h"
  #include "lockfile.h"
  #include "dir.h"
 -#include "exec_cmd.h"
  
  static char *diff_gui_tool;
  static int trust_exit_code;
 -static int use_builtin_difftool;
  
  static const char *const builtin_difftool_usage[] = {
  	N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"),
 @@ -42,11 +41,6 @@ static int difftool_config(const char *var, const char *value, void *cb)
  		return 0;
  	}
  
 -	if (!strcmp(var, "core.usebuiltindifftool")) {
 -		use_builtin_difftool = git_config_bool(var, value);
 -		return 0;
 -	}
 -
  	return git_default_config(var, value, cb);
  }
  
 @@ -257,7 +251,7 @@ static int ensure_leading_directories(char *path)
  	}
  }
  
 -static int run_dir_diff(const char *extcmd, int symlinks,
 +static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
  			int argc, const char **argv)
  {
  	char tmpdir[PATH_MAX];
 @@ -283,7 +277,6 @@ static int run_dir_diff(const char *extcmd, int symlinks,
  	struct hashmap wt_modified, tmp_modified;
  	int indices_loaded = 0;
  
 -	setup_work_tree();
  	workdir = get_git_work_tree();
  
  	/* Setup temp directories */
 @@ -323,6 +316,7 @@ static int run_dir_diff(const char *extcmd, int symlinks,
  	child.git_cmd = 1;
  	child.use_shell = 0;
  	child.clean_on_exit = 1;
 +	child.dir = prefix;
  	child.out = -1;
  	argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z",
  			 NULL);
 @@ -333,6 +327,7 @@ static int run_dir_diff(const char *extcmd, int symlinks,
  	fp = xfdopen(child.out, "r");
  
  	/* Build index info for left and right sides of the diff */
 +	i = 0;
  	while (!strbuf_getline_nul(&info, fp)) {
  		int lmode, rmode;
  		struct object_id loid, roid;
 @@ -353,6 +348,7 @@ static int run_dir_diff(const char *extcmd, int symlinks,
  		src_path = lpath.buf;
  		src_path_len = lpath.len;
  
 +		i++;
  		if (status != 'C' && status != 'R') {
  			dst_path = src_path;
  			dst_path_len = src_path_len;
 @@ -456,11 +452,15 @@ static int run_dir_diff(const char *extcmd, int symlinks,
  			}
  		}
  	}
 +
  	if (finish_command(&child)) {
  		ret = error("error occurred running diff --raw");
  		goto finish;
  	}
  
 +	if (!i)
 +		return 0;
 +
  	/*
  	 * Changes to submodules require special treatment.This loop writes a
  	 * temporary file to both the left and right directories to show the
 @@ -591,7 +591,8 @@ static int run_dir_diff(const char *extcmd, int symlinks,
  	return ret;
  }
  
 -static int run_file_diff(int prompt, int argc, const char **argv)
 +static int run_file_diff(int prompt, const char *prefix,
 +			 int argc, const char **argv)
  {
  	struct argv_array args = ARGV_ARRAY_INIT;
  	const char *env[] = {
 @@ -605,14 +606,39 @@ static int run_file_diff(int prompt, int argc, const char **argv)
  	else if (!prompt)
  		env[2] = "GIT_DIFFTOOL_NO_PROMPT=true";
  
 +
  	argv_array_push(&args, "diff");
  	for (i = 0; i < argc; i++)
  		argv_array_push(&args, argv[i]);
 -	ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, NULL, env);
 +	ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env);
  	exit(ret);
  }
  
 -int cmd_difftool(int argc, const char ** argv, const char * prefix)
 +/*
 + * NEEDSWORK: this function can go once the legacy-difftool Perl script is
 + * retired.
 + *
 + * We intentionally avoid reading the config directly here, to avoid messing up
 + * the GIT_* environment variables when we need to fall back to exec()ing the
 + * Perl script.
 + */
 +static int use_builtin_difftool(void) {
 +	struct child_process cp = CHILD_PROCESS_INIT;
 +	struct strbuf out = STRBUF_INIT;
 +	int ret;
 +
 +	argv_array_pushl(&cp.args,
 +			 "config", "--bool", "difftool.usebuiltin", NULL);
 +	cp.git_cmd = 1;
 +	if (capture_command(&cp, &out, 6))
 +		return 0;
 +	strbuf_trim(&out);
 +	ret = !strcmp("true", out.buf);
 +	strbuf_release(&out);
 +	return ret;
 +}
 +
 +int cmd_difftool(int argc, const char **argv, const char *prefix)
  {
  	int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,
  	    tool_help = 0;
 @@ -643,16 +669,29 @@ int cmd_difftool(int argc, const char ** argv, const char * prefix)
  		OPT_END()
  	};
  
 -	git_config(difftool_config, NULL);
 -	symlinks = has_symlinks;
 -	if (!use_builtin_difftool) {
 -		const char *path = mkpath("%s/git-legacy-difftool", git_exec_path());
 +	/*
 +	 * NEEDSWORK: Once the builtin difftool has been tested enough
 +	 * and git-legacy-difftool.perl is retired to contrib/, this preamble
 +	 * can be removed.
 +	 */
 +	if (!use_builtin_difftool()) {
 +		const char *path = mkpath("%s/git-legacy-difftool",
 +					  git_exec_path());
  
  		if (sane_execvp(path, (char **)argv) < 0)
  			die_errno("could not exec %s", path);
  
  		return 0;
  	}
 +	prefix = setup_git_directory();
 +	trace_repo_setup(prefix);
 +	setup_work_tree();
 +	/* NEEDSWORK: once we no longer spawn anything, remove this */
 +	setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);
 +	setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);
 +
 +	git_config(difftool_config, NULL);
 +	symlinks = has_symlinks;
  
  	argc = parse_options(argc, argv, prefix, builtin_difftool_options,
  			     builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |
 @@ -687,6 +726,6 @@ int cmd_difftool(int argc, const char ** argv, const char * prefix)
  	 * each file that changed.
  	 */
  	if (dir_diff)
 -		return run_dir_diff(extcmd, symlinks, argc, argv);
 -	return run_file_diff(prompt, argc, argv);
 +		return run_dir_diff(extcmd, symlinks, prefix, argc, argv);
 +	return run_file_diff(prompt, prefix, argc, argv);
  }
 diff --git a/git.c b/git.c
 index e68b6eb..a8e6a15 100644
 --- a/git.c
 +++ b/git.c
 @@ -424,7 +424,12 @@ static struct cmd_struct commands[] = {
  	{ "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
  	{ "diff-index", cmd_diff_index, RUN_SETUP },
  	{ "diff-tree", cmd_diff_tree, RUN_SETUP },
 -	{ "difftool", cmd_difftool, RUN_SETUP | NEED_WORK_TREE },
 +	/*
 +	 * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in
 +	 * builtin/difftool.c has been removed, this entry should be changed to
 +	 * RUN_SETUP | NEED_WORK_TREE
 +	 */
 +	{ "difftool", cmd_difftool },
  	{ "fast-export", cmd_fast_export, RUN_SETUP },
  	{ "fetch", cmd_fetch, RUN_SETUP },
  	{ "fetch-pack", cmd_fetch_pack, RUN_SETUP },
 diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
 index 70a2de4..b6a6c30 100755
 --- a/t/t7800-difftool.sh
 +++ b/t/t7800-difftool.sh
 @@ -23,6 +23,8 @@ prompt_given ()
  	test "$prompt" = "Launch 'test-tool' [Y/n]? branch"
  }
  
 +# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.
 +
  # Create a file on master and change it on branch
  test_expect_success PERL 'setup' '
  	echo master >file &&

-- 
2.10.1.583.g721a9e0


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

* [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-24 20:55   ` [PATCH v3 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin
@ 2016-11-24 20:55     ` Johannes Schindelin
  2016-11-24 21:08       ` Jeff King
  2016-11-24 20:55     ` [PATCH v3 2/2] difftool: implement the functionality in the builtin Johannes Schindelin
  2017-01-02 16:16     ` [PATCH v4 0/4] Show Git Mailing List: a builtin difftool Johannes Schindelin
  2 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-24 20:55 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker

This adds a builtin difftool that still falls back to the legacy Perl
version, which has been renamed to `legacy-difftool`.

The idea is that the new, experimental, builtin difftool immediately hands
off to the legacy difftool for now, unless the config variable
difftool.useBuiltin is set to true.

This feature flag will be used in the upcoming Git for Windows v2.11.0
release, to allow early testers to opt-in to use the builtin difftool and
flesh out any bugs.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitignore                                    |  1 +
 Makefile                                      |  3 +-
 builtin.h                                     |  1 +
 builtin/difftool.c                            | 63 +++++++++++++++++++++++++++
 git-difftool.perl => git-legacy-difftool.perl |  0
 git.c                                         |  6 +++
 t/t7800-difftool.sh                           |  2 +
 7 files changed, 75 insertions(+), 1 deletion(-)
 create mode 100644 builtin/difftool.c
 rename git-difftool.perl => git-legacy-difftool.perl (100%)

diff --git a/.gitignore b/.gitignore
index 05cb58a..f96e50e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -76,6 +76,7 @@
 /git-init-db
 /git-interpret-trailers
 /git-instaweb
+/git-legacy-difftool
 /git-log
 /git-ls-files
 /git-ls-remote
diff --git a/Makefile b/Makefile
index f53fcc9..7863bc2 100644
--- a/Makefile
+++ b/Makefile
@@ -527,7 +527,7 @@ SCRIPT_LIB += git-sh-setup
 SCRIPT_LIB += git-sh-i18n
 
 SCRIPT_PERL += git-add--interactive.perl
-SCRIPT_PERL += git-difftool.perl
+SCRIPT_PERL += git-legacy-difftool.perl
 SCRIPT_PERL += git-archimport.perl
 SCRIPT_PERL += git-cvsexportcommit.perl
 SCRIPT_PERL += git-cvsimport.perl
@@ -888,6 +888,7 @@ BUILTIN_OBJS += builtin/diff-files.o
 BUILTIN_OBJS += builtin/diff-index.o
 BUILTIN_OBJS += builtin/diff-tree.o
 BUILTIN_OBJS += builtin/diff.o
+BUILTIN_OBJS += builtin/difftool.o
 BUILTIN_OBJS += builtin/fast-export.o
 BUILTIN_OBJS += builtin/fetch-pack.o
 BUILTIN_OBJS += builtin/fetch.o
diff --git a/builtin.h b/builtin.h
index b9122bc..67f8051 100644
--- a/builtin.h
+++ b/builtin.h
@@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_index(int argc, const char **argv, const char *prefix);
 extern int cmd_diff(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_difftool(int argc, const char **argv, const char *prefix);
 extern int cmd_fast_export(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);
diff --git a/builtin/difftool.c b/builtin/difftool.c
new file mode 100644
index 0000000..53870bb
--- /dev/null
+++ b/builtin/difftool.c
@@ -0,0 +1,63 @@
+/*
+ * "git difftool" builtin command
+ *
+ * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
+ * git-difftool--helper script.
+ *
+ * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.
+ * The GIT_DIFF* variables are exported for use by git-difftool--helper.
+ *
+ * Any arguments that are unknown to this script are forwarded to 'git diff'.
+ *
+ * Copyright (C) 2016 Johannes Schindelin
+ */
+#include "builtin.h"
+#include "run-command.h"
+#include "exec_cmd.h"
+
+/*
+ * NEEDSWORK: this function can go once the legacy-difftool Perl script is
+ * retired.
+ *
+ * We intentionally avoid reading the config directly here, to avoid messing up
+ * the GIT_* environment variables when we need to fall back to exec()ing the
+ * Perl script.
+ */
+static int use_builtin_difftool(void) {
+	struct child_process cp = CHILD_PROCESS_INIT;
+	struct strbuf out = STRBUF_INIT;
+	int ret;
+
+	argv_array_pushl(&cp.args,
+			 "config", "--bool", "difftool.usebuiltin", NULL);
+	cp.git_cmd = 1;
+	if (capture_command(&cp, &out, 6))
+		return 0;
+	strbuf_trim(&out);
+	ret = !strcmp("true", out.buf);
+	strbuf_release(&out);
+	return ret;
+}
+
+int cmd_difftool(int argc, const char **argv, const char *prefix)
+{
+	/*
+	 * NEEDSWORK: Once the builtin difftool has been tested enough
+	 * and git-legacy-difftool.perl is retired to contrib/, this preamble
+	 * can be removed.
+	 */
+	if (!use_builtin_difftool()) {
+		const char *path = mkpath("%s/git-legacy-difftool",
+					  git_exec_path());
+
+		if (sane_execvp(path, (char **)argv) < 0)
+			die_errno("could not exec %s", path);
+
+		return 0;
+	}
+	prefix = setup_git_directory();
+	trace_repo_setup(prefix);
+	setup_work_tree();
+
+	die("TODO");
+}
diff --git a/git-difftool.perl b/git-legacy-difftool.perl
similarity index 100%
rename from git-difftool.perl
rename to git-legacy-difftool.perl
diff --git a/git.c b/git.c
index e8b2baf..a8e6a15 100644
--- a/git.c
+++ b/git.c
@@ -424,6 +424,12 @@ static struct cmd_struct commands[] = {
 	{ "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
 	{ "diff-index", cmd_diff_index, RUN_SETUP },
 	{ "diff-tree", cmd_diff_tree, RUN_SETUP },
+	/*
+	 * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in
+	 * builtin/difftool.c has been removed, this entry should be changed to
+	 * RUN_SETUP | NEED_WORK_TREE
+	 */
+	{ "difftool", cmd_difftool },
 	{ "fast-export", cmd_fast_export, RUN_SETUP },
 	{ "fetch", cmd_fetch, RUN_SETUP },
 	{ "fetch-pack", cmd_fetch_pack, RUN_SETUP },
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index 70a2de4..b6a6c30 100755
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
@@ -23,6 +23,8 @@ prompt_given ()
 	test "$prompt" = "Launch 'test-tool' [Y/n]? branch"
 }
 
+# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.
+
 # Create a file on master and change it on branch
 test_expect_success PERL 'setup' '
 	echo master >file &&
-- 
2.10.1.583.g721a9e0



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

* [PATCH v3 2/2] difftool: implement the functionality in the builtin
  2016-11-24 20:55   ` [PATCH v3 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin
  2016-11-24 20:55     ` [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin Johannes Schindelin
@ 2016-11-24 20:55     ` Johannes Schindelin
  2016-11-25 21:24       ` Jakub Narębski
  2017-01-02 16:16     ` [PATCH v4 0/4] Show Git Mailing List: a builtin difftool Johannes Schindelin
  2 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-24 20:55 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker

This patch gives life to the skeleton added in the previous patch.

The motivation for converting the difftool is that Perl scripts are not at
all native on Windows, and that `git difftool` therefore is pretty slow on
that platform, when there is no good reason for it to be slow.

In addition, Perl does not really have access to Git's internals. That
means that any script will always have to jump through unnecessary
hoops.

The current version of the builtin difftool does not, however, make full
use of the internals but instead chooses to spawn a couple of Git
processes, still, to make for an easier conversion. There remains a lot
of room for improvement, left for a later date.

Note: to play it safe, the original difftool is still called unless the
config setting difftool.useBuiltin is set to true.

The reason: this new, experimental, builtin difftool will be shipped as
part of Git for Windows v2.11.0, to allow for easier large-scale
testing, but of course as an opt-in feature.

Sadly, the speedup is more noticable on Linux than on Windows: a quick
test shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s)
(real/user/sys) in a Linux VM, down from  (6.529s/3.112s/0.644s), while
on Windows, it is (36.064s/2.730s/7.194s), down from
(47.637s/2.407s/6.863s). The culprit is most likely the overhead
incurred from *still* having to shell out to mergetool-lib.sh and
difftool--helper.sh.

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

diff --git a/builtin/difftool.c b/builtin/difftool.c
index 53870bb..3480920 100644
--- a/builtin/difftool.c
+++ b/builtin/difftool.c
@@ -11,9 +11,608 @@
  *
  * Copyright (C) 2016 Johannes Schindelin
  */
+#include "cache.h"
 #include "builtin.h"
 #include "run-command.h"
 #include "exec_cmd.h"
+#include "parse-options.h"
+#include "argv-array.h"
+#include "strbuf.h"
+#include "lockfile.h"
+#include "dir.h"
+
+static char *diff_gui_tool;
+static int trust_exit_code;
+
+static const char *const builtin_difftool_usage[] = {
+	N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"),
+	NULL
+};
+
+static int difftool_config(const char *var, const char *value, void *cb)
+{
+	if (!strcmp(var, "diff.guitool")) {
+		diff_gui_tool = xstrdup(value);
+		return 0;
+	}
+
+	if (!strcmp(var, "difftool.trustexitcode")) {
+		trust_exit_code = git_config_bool(var, value);
+		return 0;
+	}
+
+	return git_default_config(var, value, cb);
+}
+
+static int print_tool_help(void)
+{
+	const char *argv[] = { "mergetool", "--tool-help=diff", NULL };
+	return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
+static int parse_index_info(char *p, int *mode1, int *mode2,
+			    struct object_id *oid1, struct object_id *oid2,
+			    char *status)
+{
+	if (*p != ':')
+		return error("expected ':', got '%c'", *p);
+	*mode1 = (int)strtol(p + 1, &p, 8);
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	*mode2 = (int)strtol(p + 1, &p, 8);
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	if (get_oid_hex(++p, oid1))
+		return error("expected object ID, got '%s'", p + 1);
+	p += GIT_SHA1_HEXSZ;
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	if (get_oid_hex(++p, oid2))
+		return error("expected object ID, got '%s'", p + 1);
+	p += GIT_SHA1_HEXSZ;
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	*status = *++p;
+	if (!status || p[1])
+		return error("unexpected trailer: '%s'", p);
+	return 0;
+}
+
+/*
+ * Remove any trailing slash from $workdir
+ * before starting to avoid double slashes in symlink targets.
+ */
+static void add_path(struct strbuf *buf, size_t base_len, const char *path)
+{
+	strbuf_setlen(buf, base_len);
+	if (buf->len && buf->buf[buf->len - 1] != '/')
+		strbuf_addch(buf, '/');
+	strbuf_addstr(buf, path);
+}
+
+/*
+ * Determine whether we can simply reuse the file in the worktree.
+ */
+static int use_wt_file(const char *workdir, const char *name,
+		       struct object_id *oid)
+{
+	struct strbuf buf = STRBUF_INIT;
+	struct stat st;
+	int use = 0;
+
+	strbuf_addstr(&buf, workdir);
+	add_path(&buf, buf.len, name);
+
+	if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) {
+		struct object_id wt_oid;
+		int fd = open(buf.buf, O_RDONLY);
+
+		if (!index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {
+			if (is_null_oid(oid)) {
+				oidcpy(oid, &wt_oid);
+				use = 1;
+			} else if (!oidcmp(oid, &wt_oid))
+				use = 1;
+		}
+	}
+
+	strbuf_release(&buf);
+
+	return use;
+}
+
+struct working_tree_entry {
+	struct hashmap_entry entry;
+	char path[FLEX_ARRAY];
+};
+
+static int working_tree_entry_cmp(struct working_tree_entry *a,
+				  struct working_tree_entry *b, void *keydata)
+{
+	return strcmp(a->path, b->path);
+}
+
+/*
+ * The `left` and `right` entries hold paths for the symlinks hashmap,
+ * and a SHA-1 surrounded by brief text for submodules.
+ */
+struct pair_entry {
+	struct hashmap_entry entry;
+	char left[PATH_MAX], right[PATH_MAX];
+	const char path[FLEX_ARRAY];
+};
+
+static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata)
+{
+	return strcmp(a->path, b->path);
+}
+
+static void add_left_or_right(struct hashmap *map, const char *path,
+			      const char *content, int is_right)
+{
+	struct pair_entry *e, *existing;
+
+	FLEX_ALLOC_STR(e, path, path);
+	hashmap_entry_init(e, strhash(path));
+	existing = hashmap_get(map, e, NULL);
+	if (existing) {
+		free(e);
+		e = existing;
+	} else {
+		e->left[0] = e->right[0] = '\0';
+		hashmap_add(map, e);
+	}
+	strcpy(is_right ? e->right : e->left, content);
+}
+
+struct path_entry {
+	struct hashmap_entry entry;
+	char path[FLEX_ARRAY];
+};
+
+int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key)
+{
+	return strcmp(a->path, key ? key : b->path);
+}
+
+static void changed_files(struct hashmap *result, const char *index_path,
+			  const char *workdir)
+{
+	struct child_process update_index = CHILD_PROCESS_INIT;
+	struct child_process diff_files = CHILD_PROCESS_INIT;
+	struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT;
+	const char *git_dir = absolute_path(get_git_dir()), *env[] = {
+		NULL, NULL
+	};
+	FILE *fp;
+
+	strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path);
+	env[0] = index_env.buf;
+
+	argv_array_pushl(&update_index.args,
+			 "--git-dir", git_dir, "--work-tree", workdir,
+			 "update-index", "--really-refresh", "-q",
+			 "--unmerged", NULL);
+	update_index.no_stdin = 1;
+	update_index.no_stdout = 1;
+	update_index.no_stderr = 1;
+	update_index.git_cmd = 1;
+	update_index.use_shell = 0;
+	update_index.clean_on_exit = 1;
+	update_index.dir = workdir;
+	update_index.env = env;
+	/* Ignore any errors of update-index */
+	run_command(&update_index);
+
+	argv_array_pushl(&diff_files.args,
+			 "--git-dir", git_dir, "--work-tree", workdir,
+			 "diff-files", "--name-only", "-z", NULL);
+	diff_files.no_stdin = 1;
+	diff_files.git_cmd = 1;
+	diff_files.use_shell = 0;
+	diff_files.clean_on_exit = 1;
+	diff_files.out = -1;
+	diff_files.dir = workdir;
+	diff_files.env = env;
+	if (start_command(&diff_files))
+		die("could not obtain raw diff");
+	fp = xfdopen(diff_files.out, "r");
+	while (!strbuf_getline_nul(&buf, fp)) {
+		struct path_entry *entry;
+		FLEX_ALLOC_STR(entry, path, buf.buf);
+		hashmap_entry_init(entry, strhash(buf.buf));
+		hashmap_add(result, entry);
+	}
+	if (finish_command(&diff_files))
+		die("diff-files did not exit properly");
+	strbuf_release(&index_env);
+	strbuf_release(&buf);
+}
+
+static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)
+{
+	struct strbuf buf = STRBUF_INIT;
+	strbuf_addstr(&buf, tmpdir);
+	remove_dir_recursively(&buf, 0);
+	if (exit_code)
+		warning(_("failed: %d"), exit_code);
+	exit(exit_code);
+}
+
+static int ensure_leading_directories(char *path)
+{
+	switch (safe_create_leading_directories(path)) {
+		case SCLD_OK:
+		case SCLD_EXISTS:
+			return 0;
+		default:
+			return error(_("could not create leading directories "
+				       "of '%s'"), path);
+	}
+}
+
+static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
+			int argc, const char **argv)
+{
+	char tmpdir[PATH_MAX];
+	struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;
+	struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;
+	struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;
+	struct strbuf wtdir = STRBUF_INIT;
+	size_t ldir_len, rdir_len, wtdir_len;
+	struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1);
+	const char *workdir, *tmp;
+	int ret = 0, i;
+	FILE *fp;
+	struct hashmap working_tree_dups, submodules, symlinks2;
+	struct hashmap_iter iter;
+	struct pair_entry *entry;
+	enum object_type type;
+	unsigned long size;
+	struct index_state wtindex;
+	struct checkout lstate, rstate;
+	int rc, flags = RUN_GIT_CMD, err = 0;
+	struct child_process child = CHILD_PROCESS_INIT;
+	const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL };
+	struct hashmap wt_modified, tmp_modified;
+	int indices_loaded = 0;
+
+	workdir = get_git_work_tree();
+
+	/* Setup temp directories */
+	tmp = getenv("TMPDIR");
+	xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp");
+	if (!mkdtemp(tmpdir))
+		return error("could not create '%s'", tmpdir);
+	strbuf_addf(&ldir, "%s/left/", tmpdir);
+	strbuf_addf(&rdir, "%s/right/", tmpdir);
+	strbuf_addstr(&wtdir, workdir);
+	if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))
+		strbuf_addch(&wtdir, '/');
+	mkdir(ldir.buf, 0700);
+	mkdir(rdir.buf, 0700);
+
+	memset(&wtindex, 0, sizeof(wtindex));
+
+	memset(&lstate, 0, sizeof(lstate));
+	lstate.base_dir = ldir.buf;
+	lstate.base_dir_len = ldir.len;
+	lstate.force = 1;
+	memset(&rstate, 0, sizeof(rstate));
+	rstate.base_dir = rdir.buf;
+	rstate.base_dir_len = rdir.len;
+	rstate.force = 1;
+
+	ldir_len = ldir.len;
+	rdir_len = rdir.len;
+	wtdir_len = wtdir.len;
+
+	hashmap_init(&working_tree_dups,
+		     (hashmap_cmp_fn)working_tree_entry_cmp, 0);
+	hashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0);
+	hashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0);
+
+	child.no_stdin = 1;
+	child.git_cmd = 1;
+	child.use_shell = 0;
+	child.clean_on_exit = 1;
+	child.dir = prefix;
+	child.out = -1;
+	argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z",
+			 NULL);
+	for (i = 0; i < argc; i++)
+		argv_array_push(&child.args, argv[i]);
+	if (start_command(&child))
+		die("could not obtain raw diff");
+	fp = xfdopen(child.out, "r");
+
+	/* Build index info for left and right sides of the diff */
+	i = 0;
+	while (!strbuf_getline_nul(&info, fp)) {
+		int lmode, rmode;
+		struct object_id loid, roid;
+		char status;
+		const char *src_path, *dst_path;
+		size_t src_path_len, dst_path_len;
+
+		if (starts_with(info.buf, "::"))
+			die(N_("combined diff formats('-c' and '--cc') are "
+			       "not supported in\n"
+			       "directory diff mode('-d' and '--dir-diff')."));
+
+		if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,
+				     &status))
+			break;
+		if (strbuf_getline_nul(&lpath, fp))
+			break;
+		src_path = lpath.buf;
+		src_path_len = lpath.len;
+
+		i++;
+		if (status != 'C' && status != 'R') {
+			dst_path = src_path;
+			dst_path_len = src_path_len;
+		} else {
+			if (strbuf_getline_nul(&rpath, fp))
+				break;
+			dst_path = rpath.buf;
+			dst_path_len = rpath.len;
+		}
+
+		if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) {
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "Subproject commit %s",
+				    oid_to_hex(&loid));
+			add_left_or_right(&submodules, src_path, buf.buf, 0);
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "Subproject commit %s",
+				    oid_to_hex(&roid));
+			if (!oidcmp(&loid, &roid))
+				strbuf_addstr(&buf, "-dirty");
+			add_left_or_right(&submodules, dst_path, buf.buf, 1);
+			continue;
+		}
+
+		if (S_ISLNK(lmode)) {
+			char *content = read_sha1_file(loid.hash, &type, &size);
+			add_left_or_right(&symlinks2, src_path, content, 0);
+			free(content);
+		}
+
+		if (S_ISLNK(rmode)) {
+			char *content = read_sha1_file(roid.hash, &type, &size);
+			add_left_or_right(&symlinks2, dst_path, content, 1);
+			free(content);
+		}
+
+		if (lmode && status != 'C') {
+			ce->ce_mode = lmode;
+			oidcpy(&ce->oid, &loid);
+			strcpy(ce->name, src_path);
+			ce->ce_namelen = src_path_len;
+			if (checkout_entry(ce, &lstate, NULL))
+				return error("could not write '%s'", src_path);
+		}
+
+		if (rmode) {
+			struct working_tree_entry *entry;
+
+			/* Avoid duplicate working_tree entries */
+			FLEX_ALLOC_STR(entry, path, dst_path);
+			hashmap_entry_init(entry, strhash(dst_path));
+			if (hashmap_get(&working_tree_dups, entry, NULL)) {
+				free(entry);
+				continue;
+			}
+			hashmap_add(&working_tree_dups, entry);
+
+			if (!use_wt_file(workdir, dst_path, &roid)) {
+				ce->ce_mode = rmode;
+				oidcpy(&ce->oid, &roid);
+				strcpy(ce->name, dst_path);
+				ce->ce_namelen = dst_path_len;
+				if (checkout_entry(ce, &rstate, NULL))
+					return error("could not write '%s'",
+						     dst_path);
+			} else if (!is_null_oid(&roid)) {
+				/*
+				 * Changes in the working tree need special
+				 * treatment since they are not part of the
+				 * index.
+				 */
+				struct cache_entry *ce2 =
+					make_cache_entry(rmode, roid.hash,
+							 dst_path, 0, 0);
+				ce_mode_from_stat(ce2, rmode);
+
+				add_index_entry(&wtindex, ce2,
+						ADD_CACHE_JUST_APPEND);
+
+				add_path(&wtdir, wtdir_len, dst_path);
+				add_path(&rdir, rdir_len, dst_path);
+				if (ensure_leading_directories(rdir.buf))
+					return error("could not create "
+						     "directory for '%s'",
+						     dst_path);
+				if (symlinks) {
+					if (symlink(wtdir.buf, rdir.buf)) {
+						ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf);
+						goto finish;
+					}
+				} else {
+					struct stat st;
+					if (stat(wtdir.buf, &st))
+						st.st_mode = 0644;
+					if (copy_file(rdir.buf, wtdir.buf,
+						      st.st_mode)) {
+						ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf);
+						goto finish;
+					}
+				}
+			}
+		}
+	}
+
+	if (finish_command(&child)) {
+		ret = error("error occurred running diff --raw");
+		goto finish;
+	}
+
+	if (!i)
+		return 0;
+
+	/*
+	 * Changes to submodules require special treatment.This loop writes a
+	 * temporary file to both the left and right directories to show the
+	 * change in the recorded SHA1 for the submodule.
+	 */
+	hashmap_iter_init(&submodules, &iter);
+	while ((entry = hashmap_iter_next(&iter))) {
+		if (*entry->left) {
+			add_path(&ldir, ldir_len, entry->path);
+			ensure_leading_directories(ldir.buf);
+			write_file(ldir.buf, "%s", entry->left);
+		}
+		if (*entry->right) {
+			add_path(&rdir, rdir_len, entry->path);
+			ensure_leading_directories(rdir.buf);
+			write_file(rdir.buf, "%s", entry->right);
+		}
+	}
+
+	/*
+	 * Symbolic links require special treatment.The standard "git diff"
+	 * shows only the link itself, not the contents of the link target.
+	 * This loop replicates that behavior.
+	 */
+	hashmap_iter_init(&symlinks2, &iter);
+	while ((entry = hashmap_iter_next(&iter))) {
+		if (*entry->left) {
+			add_path(&ldir, ldir_len, entry->path);
+			ensure_leading_directories(ldir.buf);
+			write_file(ldir.buf, "%s", entry->left);
+		}
+		if (*entry->right) {
+			add_path(&rdir, rdir_len, entry->path);
+			ensure_leading_directories(rdir.buf);
+			write_file(rdir.buf, "%s", entry->right);
+		}
+	}
+
+	strbuf_release(&buf);
+
+	strbuf_setlen(&ldir, ldir_len);
+	helper_argv[1] = ldir.buf;
+	strbuf_setlen(&rdir, rdir_len);
+	helper_argv[2] = rdir.buf;
+
+	if (extcmd) {
+		helper_argv[0] = extcmd;
+		flags = 0;
+	} else
+		setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1);
+	rc = run_command_v_opt(helper_argv, flags);
+
+	/*
+	 * If the diff includes working copy files and those
+	 * files were modified during the diff, then the changes
+	 * should be copied back to the working tree.
+	 * Do not copy back files when symlinks are used and the
+	 * external tool did not replace the original link with a file.
+	 *
+	 * These hashes are loaded lazily since they aren't needed
+	 * in the common case of --symlinks and the difftool updating
+	 * files through the symlink.
+	 */
+	hashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp,
+		     wtindex.cache_nr);
+	hashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp,
+		     wtindex.cache_nr);
+
+	for (i = 0; i < wtindex.cache_nr; i++) {
+		struct hashmap_entry dummy;
+		const char *name = wtindex.cache[i]->name;
+		struct stat st;
+
+		add_path(&rdir, rdir_len, name);
+		if (lstat(rdir.buf, &st))
+			continue;
+
+		if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode))
+			continue;
+
+		if (!indices_loaded) {
+			static struct lock_file lock;
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "%s/wtindex", tmpdir);
+			if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
+			    write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {
+				ret = error("could not write %s", buf.buf);
+				rollback_lock_file(&lock);
+				goto finish;
+			}
+			changed_files(&wt_modified, buf.buf, workdir);
+			strbuf_setlen(&rdir, rdir_len);
+			changed_files(&tmp_modified, buf.buf, rdir.buf);
+			add_path(&rdir, rdir_len, name);
+			indices_loaded = 1;
+		}
+
+		hashmap_entry_init(&dummy, strhash(name));
+		if (hashmap_get(&tmp_modified, &dummy, name)) {
+			add_path(&wtdir, wtdir_len, name);
+			if (hashmap_get(&wt_modified, &dummy, name)) {
+				warning(_("both files modified: '%s' and '%s'."),
+					wtdir.buf, rdir.buf);
+				warning(_("working tree file has been left."));
+				warning("");
+				err = 1;
+			} else if (unlink(wtdir.buf) ||
+				   copy_file(wtdir.buf, rdir.buf, st.st_mode))
+				warning_errno(_("could not copy '%s' to '%s'"),
+					      rdir.buf, wtdir.buf);
+		}
+	}
+
+	if (err) {
+		warning(_("temporary files exist in '%s'."), tmpdir);
+		warning(_("you may want to cleanup or recover these."));
+		exit(1);
+	} else
+		exit_cleanup(tmpdir, rc);
+
+finish:
+	free(ce);
+	strbuf_release(&ldir);
+	strbuf_release(&rdir);
+	strbuf_release(&wtdir);
+	strbuf_release(&buf);
+
+	return ret;
+}
+
+static int run_file_diff(int prompt, const char *prefix,
+			 int argc, const char **argv)
+{
+	struct argv_array args = ARGV_ARRAY_INIT;
+	const char *env[] = {
+		"GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL,
+		NULL
+	};
+	int ret = 0, i;
+
+	if (prompt > 0)
+		env[2] = "GIT_DIFFTOOL_PROMPT=true";
+	else if (!prompt)
+		env[2] = "GIT_DIFFTOOL_NO_PROMPT=true";
+
+
+	argv_array_push(&args, "diff");
+	for (i = 0; i < argc; i++)
+		argv_array_push(&args, argv[i]);
+	ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env);
+	exit(ret);
+}
 
 /*
  * NEEDSWORK: this function can go once the legacy-difftool Perl script is
@@ -41,6 +640,35 @@ static int use_builtin_difftool(void) {
 
 int cmd_difftool(int argc, const char **argv, const char *prefix)
 {
+	int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,
+	    tool_help = 0;
+	static char *difftool_cmd = NULL, *extcmd = NULL;
+	struct option builtin_difftool_options[] = {
+		OPT_BOOL('g', "gui", &use_gui_tool,
+			 N_("use `diff.guitool` instead of `diff.tool`")),
+		OPT_BOOL('d', "dir-diff", &dir_diff,
+			 N_("perform a full-directory diff")),
+		{ OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL,
+			N_("do not prompt before launching a diff tool"),
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
+		{ OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL,
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
+			NULL, 1 },
+		OPT_BOOL(0, "symlinks", &symlinks,
+			 N_("use symlinks in dir-diff mode")),
+		OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"),
+			   N_("use the specified diff tool")),
+		OPT_BOOL(0, "tool-help", &tool_help,
+			 N_("print a list of diff tools that may be used with "
+			    "`--tool`")),
+		OPT_BOOL(0, "trust-exit-code", &trust_exit_code,
+			 N_("make 'git-difftool' exit when an invoked diff "
+			    "tool returns a non - zero exit code")),
+		OPT_STRING('x', "extcmd", &extcmd, N_("<command>"),
+			   N_("specify a custom command for viewing diffs")),
+		OPT_END()
+	};
+
 	/*
 	 * NEEDSWORK: Once the builtin difftool has been tested enough
 	 * and git-legacy-difftool.perl is retired to contrib/, this preamble
@@ -58,6 +686,46 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)
 	prefix = setup_git_directory();
 	trace_repo_setup(prefix);
 	setup_work_tree();
+	/* NEEDSWORK: once we no longer spawn anything, remove this */
+	setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);
+	setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);
+
+	git_config(difftool_config, NULL);
+	symlinks = has_symlinks;
+
+	argc = parse_options(argc, argv, prefix, builtin_difftool_options,
+			     builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |
+			     PARSE_OPT_KEEP_DASHDASH);
 
-	die("TODO");
+	if (tool_help)
+		return print_tool_help();
+
+	if (use_gui_tool && diff_gui_tool && *diff_gui_tool)
+		setenv("GIT_DIFF_TOOL", diff_gui_tool, 1);
+	else if (difftool_cmd) {
+		if (*difftool_cmd)
+			setenv("GIT_DIFF_TOOL", difftool_cmd, 1);
+		else
+			die(_("no <tool> given for --tool=<tool>"));
+	}
+
+	if (extcmd) {
+		if (*extcmd)
+			setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1);
+		else
+			die(_("no <cmd> given for --extcmd=<cmd>"));
+	}
+
+	setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE",
+	       trust_exit_code ? "true" : "false", 1);
+
+	/*
+	 * In directory diff mode, 'git-difftool--helper' is called once
+	 * to compare the a / b directories. In file diff mode, 'git diff'
+	 * will invoke a separate instance of 'git-difftool--helper' for
+	 * each file that changed.
+	 */
+	if (dir_diff)
+		return run_dir_diff(extcmd, symlinks, prefix, argc, argv);
+	return run_file_diff(prompt, prefix, argc, argv);
 }
-- 
2.10.1.583.g721a9e0

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-24 20:55     ` [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin Johannes Schindelin
@ 2016-11-24 21:08       ` Jeff King
  2016-11-24 21:56         ` Johannes Schindelin
  0 siblings, 1 reply; 86+ messages in thread
From: Jeff King @ 2016-11-24 21:08 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker

On Thu, Nov 24, 2016 at 09:55:07PM +0100, Johannes Schindelin wrote:

> +/*
> + * NEEDSWORK: this function can go once the legacy-difftool Perl script is
> + * retired.
> + *
> + * We intentionally avoid reading the config directly here, to avoid messing up
> + * the GIT_* environment variables when we need to fall back to exec()ing the
> + * Perl script.
> + */
> +static int use_builtin_difftool(void) {
> +	struct child_process cp = CHILD_PROCESS_INIT;
> +	struct strbuf out = STRBUF_INIT;
> +	int ret;
> +
> +	argv_array_pushl(&cp.args,
> +			 "config", "--bool", "difftool.usebuiltin", NULL);
> +	cp.git_cmd = 1;
> +	if (capture_command(&cp, &out, 6))
> +		return 0;
> +	strbuf_trim(&out);
> +	ret = !strcmp("true", out.buf);
> +	strbuf_release(&out);
> +	return ret;
> +}

FWIW, it should mostly Just Work to use the internal config functions
these days, with the caveat that they will not read repo-level config if
you haven't done repo setup yet.

I think it would probably be OK to ship with that caveat (people would
probably use --global config, or "git -c" for a quick override), but if
you really wanted to address it, you can do something like what
pager.c:read_early_config() does.

Of course, your method here is fine, too; I just know you are sensitive
to forking extra processes.

Also, a minor nit: capture_command() might return data in "out" with a
non-zero exit if the command both generates stdout and exits non-zero
itself. I'm not sure that's possible with git-config, though, so it
might not be worth worrying about.

-Peff

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-24 21:08       ` Jeff King
@ 2016-11-24 21:56         ` Johannes Schindelin
  2016-11-25  3:18           ` Jeff King
  0 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-24 21:56 UTC (permalink / raw)
  To: Jeff King; +Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker

Hi Peff,

On Thu, 24 Nov 2016, Jeff King wrote:

> On Thu, Nov 24, 2016 at 09:55:07PM +0100, Johannes Schindelin wrote:
> 
> > +/*
> > + * NEEDSWORK: this function can go once the legacy-difftool Perl script is
> > + * retired.
> > + *
> > + * We intentionally avoid reading the config directly here, to avoid messing up
> > + * the GIT_* environment variables when we need to fall back to exec()ing the
> > + * Perl script.
> > + */
> > +static int use_builtin_difftool(void) {
> > +	struct child_process cp = CHILD_PROCESS_INIT;
> > +	struct strbuf out = STRBUF_INIT;
> > +	int ret;
> > +
> > +	argv_array_pushl(&cp.args,
> > +			 "config", "--bool", "difftool.usebuiltin", NULL);
> > +	cp.git_cmd = 1;
> > +	if (capture_command(&cp, &out, 6))
> > +		return 0;
> > +	strbuf_trim(&out);
> > +	ret = !strcmp("true", out.buf);
> > +	strbuf_release(&out);
> > +	return ret;
> > +}
> 
> FWIW, it should mostly Just Work to use the internal config functions
> these days, with the caveat that they will not read repo-level config if
> you haven't done repo setup yet.
> 
> I think it would probably be OK to ship with that caveat (people would
> probably use --global config, or "git -c" for a quick override), but if
> you really wanted to address it, you can do something like what
> pager.c:read_early_config() does.

The config setting is already overkill (and does even make something much
harder than before: running tests with the builtin difftool used to be as
simply as `touch use-builtin-difftool && make -C t t7800-difftool.sh, now
I have to edit t7800-difftool.sh to configure difftool.useBuiltin, and
without the repo-level config even that would not be working).

Imitating read_early_config() would be overkill deluxe.

> Of course, your method here is fine, too; I just know you are sensitive
> to forking extra processes.
> 
> Also, a minor nit: capture_command() might return data in "out" with a
> non-zero exit if the command both generates stdout and exits non-zero
> itself. I'm not sure that's possible with git-config, though, so it
> might not be worth worrying about.

As it is, I spent way too much time on a feature flag *that will go away
real soon*. And not only I spent too much time on it: everybody who even
bothered to think about it spent too much time on it. It is a temporary
feature flag. It will go away. If it is inefficient, or inelegant, it
won't matter in a month from now.

In other words, it was not really necessary to spend all of that time and
all of that brain power to first discuss the shortcomings of having the
presence of a file in exec path as a feature flag, then converting it into
a config setting, only to find out that this *causes* problems that were
not there before.

Frankly, that was not what I was hoping for. I was hoping to get a decent
review of the *difftool* functionality. David did a fine job to provide
that review. All that discussion about the temporary feature flag was
really a side track for me, and I hate to admit that I let myself get
sucked into it, and it cost me quite some time that I would have rather
spent on release engineering the preview based on v2.11.0-rc3 (including
the difftool with *any* type of working opt-in feature flag).

Maybe I will write more explicitly in the next cover letter what I intend
to do, and what parts of the patch series is intended to be throw-away
material (hence "good enough" is good enough, and discussions about it are
really wasting all of our time). Of course, I cannot dictate what other
people find interesting to comment on, but at least I will have a good
excuse to ignore suggestions that only distract from the work that is
really needed.

My only solace is that I did some substantially more intensive testing in
the wake of this discussion, thanks to the test suite breakages incurred
by switching the feature flag to a soon-to-be-abandoned config setting.
That makes me much more confident about the builtin difftool, which is all
I wanted in the first place.

Ciao,
Dscho

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-24 21:56         ` Johannes Schindelin
@ 2016-11-25  3:18           ` Jeff King
  2016-11-25 11:05             ` Johannes Schindelin
  0 siblings, 1 reply; 86+ messages in thread
From: Jeff King @ 2016-11-25  3:18 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker

On Thu, Nov 24, 2016 at 10:56:23PM +0100, Johannes Schindelin wrote:

> > I think it would probably be OK to ship with that caveat (people would
> > probably use --global config, or "git -c" for a quick override), but if
> > you really wanted to address it, you can do something like what
> > pager.c:read_early_config() does.
> 
> The config setting is already overkill (and does even make something much
> harder than before: running tests with the builtin difftool used to be as
> simply as `touch use-builtin-difftool && make -C t t7800-difftool.sh, now
> I have to edit t7800-difftool.sh to configure difftool.useBuiltin, and
> without the repo-level config even that would not be working).
> 
> Imitating read_early_config() would be overkill deluxe.

I would have expected it to just be a build-time flag, like:

  make BUILTIN_DIFFTOOL=Yes test

but I did not closely follow the rest of the conversation, so I am
probably just repeating bits that were already said. So probably ignore
me.

I'm happy with pretty much anything under the reasoning of "this does not
matter much because it is going away soon".

-Peff

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-25  3:18           ` Jeff King
@ 2016-11-25 11:05             ` Johannes Schindelin
  2016-11-25 17:19               ` Jeff King
  0 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-25 11:05 UTC (permalink / raw)
  To: Jeff King; +Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker

Hi Peff,

On Thu, 24 Nov 2016, Jeff King wrote:

> On Thu, Nov 24, 2016 at 10:56:23PM +0100, Johannes Schindelin wrote:
> 
> > > I think it would probably be OK to ship with that caveat (people would
> > > probably use --global config, or "git -c" for a quick override), but if
> > > you really wanted to address it, you can do something like what
> > > pager.c:read_early_config() does.
> > 
> > The config setting is already overkill (and does even make something much
> > harder than before: running tests with the builtin difftool used to be as
> > simply as `touch use-builtin-difftool && make -C t t7800-difftool.sh, now
> > I have to edit t7800-difftool.sh to configure difftool.useBuiltin, and
> > without the repo-level config even that would not be working).
> > 
> > Imitating read_early_config() would be overkill deluxe.
> 
> I would have expected it to just be a build-time flag, like:
> 
>   make BUILTIN_DIFFTOOL=Yes test

That works for Git developers.

I want to let as many users as possible test the builtin difftool.
Hopefully a lot more users than there are Git developers.

Which means that I need a feature flag in production code, not a build
time flag.

> I'm happy with pretty much anything under the reasoning of "this does not
> matter much because it is going away soon".

Yeah, well, I am more happy with anything along the lines of David's
review, pointing out flaws in the current revision of the builtin difftool
before it bites users ;-)

Ciao,
Dscho

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-25 11:05             ` Johannes Schindelin
@ 2016-11-25 17:19               ` Jeff King
  2016-11-25 17:41                 ` Johannes Schindelin
  0 siblings, 1 reply; 86+ messages in thread
From: Jeff King @ 2016-11-25 17:19 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker

On Fri, Nov 25, 2016 at 12:05:00PM +0100, Johannes Schindelin wrote:

> > I would have expected it to just be a build-time flag, like:
> > 
> >   make BUILTIN_DIFFTOOL=Yes test
> 
> That works for Git developers.
> 
> I want to let as many users as possible test the builtin difftool.
> Hopefully a lot more users than there are Git developers.
> 
> Which means that I need a feature flag in production code, not a build
> time flag.

Ah, I didn't realize that was a requirement. If this is going to be part
of a release and real end-users are going to see it, that does make me
think the config option is the better path (than the presence of some
file), as it's our standard way of tweaking run-time behavior.

The implementation can still remain slightly gross if it's eventually
going away.

> > I'm happy with pretty much anything under the reasoning of "this does not
> > matter much because it is going away soon".
> 
> Yeah, well, I am more happy with anything along the lines of David's
> review, pointing out flaws in the current revision of the builtin difftool
> before it bites users ;-)

Sorry, I can't really help much there, not having much knowledge of
difftool.

-Peff

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-25 17:19               ` Jeff King
@ 2016-11-25 17:41                 ` Johannes Schindelin
  2016-11-25 17:47                   ` Jeff King
  0 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-25 17:41 UTC (permalink / raw)
  To: Jeff King; +Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker

Hi Peff,

On Fri, 25 Nov 2016, Jeff King wrote:

> On Fri, Nov 25, 2016 at 12:05:00PM +0100, Johannes Schindelin wrote:
> 
> > > I would have expected it to just be a build-time flag, like:
> > > 
> > >   make BUILTIN_DIFFTOOL=Yes test
> > 
> > That works for Git developers.
> > 
> > I want to let as many users as possible test the builtin difftool.
> > Hopefully a lot more users than there are Git developers.
> > 
> > Which means that I need a feature flag in production code, not a build
> > time flag.
> 
> Ah, I didn't realize that was a requirement. If this is going to be part
> of a release and real end-users are going to see it, that does make me
> think the config option is the better path (than the presence of some
> file), as it's our standard way of tweaking run-time behavior.

So how do you easily switch back and forth between testing the old vs the
new difftool via the test suite?

Ciao,
Dscho

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-25 17:41                 ` Johannes Schindelin
@ 2016-11-25 17:47                   ` Jeff King
  2016-11-26 12:22                     ` Johannes Schindelin
  0 siblings, 1 reply; 86+ messages in thread
From: Jeff King @ 2016-11-25 17:47 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker

On Fri, Nov 25, 2016 at 06:41:23PM +0100, Johannes Schindelin wrote:

> > Ah, I didn't realize that was a requirement. If this is going to be part
> > of a release and real end-users are going to see it, that does make me
> > think the config option is the better path (than the presence of some
> > file), as it's our standard way of tweaking run-time behavior.
> 
> So how do you easily switch back and forth between testing the old vs the
> new difftool via the test suite?

If it's for a specific tool, I'd consider teaching the test suite to run
the whole script twice: once with the flag set and once without.

That is sometimes more complicated, though, if the script creates many
sub-repos. An environment variable might be more natural. If you already
support flipping the default via config, you can probably do:

  GIT_CONFIG_PARAMETERS="'difftool.usebuiltin=true'"
  export GIT_CONFIG_PARAMETERS

-Peff

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

* Re: [PATCH v3 2/2] difftool: implement the functionality in the builtin
  2016-11-24 20:55     ` [PATCH v3 2/2] difftool: implement the functionality in the builtin Johannes Schindelin
@ 2016-11-25 21:24       ` Jakub Narębski
  2016-11-27 11:10         ` Johannes Schindelin
  0 siblings, 1 reply; 86+ messages in thread
From: Jakub Narębski @ 2016-11-25 21:24 UTC (permalink / raw)
  To: Johannes Schindelin, git
  Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker

W dniu 24.11.2016 o 21:55, Johannes Schindelin pisze:

> The current version of the builtin difftool does not, however, make full
> use of the internals but instead chooses to spawn a couple of Git
> processes, still, to make for an easier conversion. There remains a lot
> of room for improvement, left for a later date.
[...]

> Sadly, the speedup is more noticable on Linux than on Windows: a quick
> test shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s)
> (real/user/sys) in a Linux VM, down from  (6.529s/3.112s/0.644s), while
> on Windows, it is (36.064s/2.730s/7.194s), down from
> (47.637s/2.407s/6.863s). The culprit is most likely the overhead
> incurred from *still* having to shell out to mergetool-lib.sh and
> difftool--helper.sh.

Does this mean that our shell-based testsuite is not well suited to be
benchmark suite for comparing performance on MS Windows?

Or does it mean that "builtin-difftool" spawning Git processes is the
problem?
 
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  builtin/difftool.c | 670 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 669 insertions(+), 1 deletion(-)
> 
> diff --git a/builtin/difftool.c b/builtin/difftool.c
> index 53870bb..3480920 100644
> --- a/builtin/difftool.c
> +++ b/builtin/difftool.c
> @@ -11,9 +11,608 @@
>   *
>   * Copyright (C) 2016 Johannes Schindelin
>   */
> +#include "cache.h"
>  #include "builtin.h"
>  #include "run-command.h"
>  #include "exec_cmd.h"
> +#include "parse-options.h"
> +#include "argv-array.h"
> +#include "strbuf.h"
> +#include "lockfile.h"
> +#include "dir.h"
> +
> +static char *diff_gui_tool;
> +static int trust_exit_code;
> +
> +static const char *const builtin_difftool_usage[] = {
> +	N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"),
> +	NULL
> +};
> +
> +static int difftool_config(const char *var, const char *value, void *cb)
> +{
> +	if (!strcmp(var, "diff.guitool")) {

Shouldn't you also read other configuration variables, like "diff.tool",
and it's mergetool fallbacks ("merge.guitool", "merge.tool")?

> +		diff_gui_tool = xstrdup(value);
> +		return 0;
> +	}
> +
> +	if (!strcmp(var, "difftool.trustexitcode")) {
> +		trust_exit_code = git_config_bool(var, value);
> +		return 0;
> +	}

Why you do not need to check "difftool.prompt"?  And "mergetool.*" fallbacks?

> +
> +	return git_default_config(var, value, cb);
> +}
> +
> +static int print_tool_help(void)
> +{
> +	const char *argv[] = { "mergetool", "--tool-help=diff", NULL };
> +	return run_command_v_opt(argv, RUN_GIT_CMD);

This looks a bit strange to me, but I guess this is to avoid recursively
invoking ourself, and { "legacy-difftool", "--tool-help", NULL }; isn't
that much better.

> +}
> +
> +static int parse_index_info(char *p, int *mode1, int *mode2,
> +			    struct object_id *oid1, struct object_id *oid2,
> +			    char *status)

  There are only two hard things in Computer Science:
  cache invalidation and naming things.
    -- Phil Karlton

Why did you name function that parses "diff --raw" output (aka "diff-tree"
output) parse_index_info() instead of parse_raw_diff() or parse_diff_tree()?
I went searching for `update-index --index-info` formats...

This is not that important, because this function is file-static; though
future developers would thank you for more descriptive naming.

ADDED: Disregard this, I see this function is about index (?) part of
raw diff, that is only a part of "git diff --raw -z" output.  Though...

> +{
> +	if (*p != ':')
> +		return error("expected ':', got '%c'", *p);
> +	*mode1 = (int)strtol(p + 1, &p, 8);
> +	if (*p != ' ')
> +		return error("expected ' ', got '%c'", *p);

Nitpicking.

I guess because this error shouldn't really happen, and because current
implementation is transient, we don't need to worry about better error
messages (was it problem with parsing, or was it unexpected character).

For example '10064x', or '10064\n' would fail parse, but it is not
space that we were expecting...

> +	*mode2 = (int)strtol(p + 1, &p, 8);
> +	if (*p != ' ')
> +		return error("expected ' ', got '%c'", *p);
> +	if (get_oid_hex(++p, oid1))
> +		return error("expected object ID, got '%s'", p + 1);
> +	p += GIT_SHA1_HEXSZ;
> +	if (*p != ' ')
> +		return error("expected ' ', got '%c'", *p);
> +	if (get_oid_hex(++p, oid2))
> +		return error("expected object ID, got '%s'", p + 1);
> +	p += GIT_SHA1_HEXSZ;
> +	if (*p != ' ')
> +		return error("expected ' ', got '%c'", *p);
> +	*status = *++p;
> +	if (!status || p[1])
> +		return error("unexpected trailer: '%s'", p);
> +	return 0;
> +}
> +
> +/*
> + * Remove any trailing slash from $workdir
> + * before starting to avoid double slashes in symlink targets.
> + */

Err... that's not what add_path() does, in its current implementation.
It doesn't remove trailing slashes, but it checks if there is trailing
slash, and if there isn't, it adds it as separator before adding path.

Or was it original comment from the Perl implementation?  It look
like this, with '$workdir'...  If it is meant to be straight copy
of comment from legacy-difftool, a note would be nice.

> +static void add_path(struct strbuf *buf, size_t base_len, const char *path)

Naming: I think strbuf_addpath() would be a better name, but I guess
it is a matter of taste.

> +{
> +	strbuf_setlen(buf, base_len);
> +	if (buf->len && buf->buf[buf->len - 1] != '/')
> +		strbuf_addch(buf, '/');
> +	strbuf_addstr(buf, path);
> +}
> +
> +/*
> + * Determine whether we can simply reuse the file in the worktree.
> + */
> +static int use_wt_file(const char *workdir, const char *name,

Should it be 'name' or 'pathname'?

> +		       struct object_id *oid)
> +{
> +	struct strbuf buf = STRBUF_INIT;
> +	struct stat st;
> +	int use = 0;
> +
> +	strbuf_addstr(&buf, workdir);
> +	add_path(&buf, buf.len, name);

With proposed rename, it would IMVVVHO looks better

  +	strbuf_addstr(&buf, workdir);
  +	strbuf_addpath(&buf, buf.len, name);

But that is a matter of taste (again, the function is file-local).

> +
> +	if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) {
> +		struct object_id wt_oid;
> +		int fd = open(buf.buf, O_RDONLY);
> +
> +		if (!index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {
> +			if (is_null_oid(oid)) {
> +				oidcpy(oid, &wt_oid);
> +				use = 1;
> +			} else if (!oidcmp(oid, &wt_oid))
> +				use = 1;
> +		}
> +	}
> +
> +	strbuf_release(&buf);
> +
> +	return use;
> +}

[...]

> +static int ensure_leading_directories(char *path)
> +{
> +	switch (safe_create_leading_directories(path)) {
> +		case SCLD_OK:
> +		case SCLD_EXISTS:
> +			return 0;
> +		default:
> +			return error(_("could not create leading directories "
> +				       "of '%s'"), path);
> +	}
> +}

Nice function, I wonder if it would be useful in other places.

> +
> +static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
> +			int argc, const char **argv)
> +{
> +	char tmpdir[PATH_MAX];
> +	struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;
> +	struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;

Nitpicking.

To be symmetric, it could be reordered like this:

  +	struct strbuf info = STRBUF_INIT, buf = STRBUF_INIT;
  +	struct strbuf lpath = STRBUF_INIT, rpath = STRBUF_INIT;

See: lpath, rpath; ldir, rdir; ldir_len, rdir_len.

> +	struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;
> +	struct strbuf wtdir = STRBUF_INIT;
> +	size_t ldir_len, rdir_len, wtdir_len;

[...]
> +	argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z",
> +			 NULL);
> +	for (i = 0; i < argc; i++)
> +		argv_array_push(&child.args, argv[i]);
> +	if (start_command(&child))
> +		die("could not obtain raw diff");
> +	fp = xfdopen(child.out, "r");
> +
> +	/* Build index info for left and right sides of the diff */
> +	i = 0;
> +	while (!strbuf_getline_nul(&info, fp)) {
> +		int lmode, rmode;
> +		struct object_id loid, roid;
> +		char status;
> +		const char *src_path, *dst_path;
> +		size_t src_path_len, dst_path_len;
> +
> +		if (starts_with(info.buf, "::"))
> +			die(N_("combined diff formats('-c' and '--cc') are "
> +			       "not supported in\n"
> +			       "directory diff mode('-d' and '--dir-diff')."));
> +
> +		if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,
> +				     &status))

After rename it would read as:

  +		if (parse_raw_diff(info.buf, &lmode, &rmode, &loid, &roid,
  +				   &status))

Though now I see that you parse here index information of raw diff
(I think)... so disregard my musings.

> +			break;
> +		if (strbuf_getline_nul(&lpath, fp))
> +			break;
> +		src_path = lpath.buf;
> +		src_path_len = lpath.len;
> +
> +		i++;
> +		if (status != 'C' && status != 'R') {
> +			dst_path = src_path;
> +			dst_path_len = src_path_len;
> +		} else {
> +			if (strbuf_getline_nul(&rpath, fp))
> +				break;
> +			dst_path = rpath.buf;
> +			dst_path_len = rpath.len;
> +		}


[...]
> +	/*
> +	 * Changes to submodules require special treatment.This loop writes a

Here and in few other places you are missing space after full stop.

  +	 * Changes to submodules require special treatment. This loop writes a


> +	 * temporary file to both the left and right directories to show the
> +	 * change in the recorded SHA1 for the submodule.
> +	 */
> +	hashmap_iter_init(&submodules, &iter);
> +	while ((entry = hashmap_iter_next(&iter))) {
> +		if (*entry->left) {
> +			add_path(&ldir, ldir_len, entry->path);
> +			ensure_leading_directories(ldir.buf);
> +			write_file(ldir.buf, "%s", entry->left);
> +		}
> +		if (*entry->right) {
> +			add_path(&rdir, rdir_len, entry->path);
> +			ensure_leading_directories(rdir.buf);
> +			write_file(rdir.buf, "%s", entry->right);
> +		}
> +	}
> +
> +	/*
> +	 * Symbolic links require special treatment.The standard "git diff"

Same here.

> +	 * shows only the link itself, not the contents of the link target.
> +	 * This loop replicates that behavior.
> +	 */

Best,
-- 
Jakub Narębski


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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-25 17:47                   ` Jeff King
@ 2016-11-26 12:22                     ` Johannes Schindelin
  2016-11-26 16:19                       ` Jeff King
  0 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-26 12:22 UTC (permalink / raw)
  To: Jeff King; +Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker

Hi Peff,

On Fri, 25 Nov 2016, Jeff King wrote:

> On Fri, Nov 25, 2016 at 06:41:23PM +0100, Johannes Schindelin wrote:
> 
> > > Ah, I didn't realize that was a requirement. If this is going to be part
> > > of a release and real end-users are going to see it, that does make me
> > > think the config option is the better path (than the presence of some
> > > file), as it's our standard way of tweaking run-time behavior.
> > 
> > So how do you easily switch back and forth between testing the old vs the
> > new difftool via the test suite?
> 
> If it's for a specific tool, I'd consider teaching the test suite to run
> the whole script twice: once with the flag set and once without.
> 
> That is sometimes more complicated, though, if the script creates many
> sub-repos. An environment variable might be more natural. If you already
> support flipping the default via config, you can probably do:
> 
>   GIT_CONFIG_PARAMETERS="'difftool.usebuiltin=true'"
>   export GIT_CONFIG_PARAMETERS

Except that that does not work, of course. To figure out why, apply this
diff:

-- snip --
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index 17f3008277..27159f65f3 100755
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
@@ -10,6 +10,9 @@ Testing basic diff tool invocation
 
 . ./test-lib.sh
 
+echo "config $(git config difftool.usebuiltin)." >&2
+exit 1
+
 difftool_test_setup ()
 {
 	test_config diff.tool test-tool &&
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 9980a46133..0ddeded92b 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -86,6 +86,7 @@ EDITOR=:
 # /usr/xpg4/bin/sh and /bin/ksh to bail out.  So keep the unsets
 # deriving from the command substitution clustered with the other
 # ones.
+echo "before $(git config difftool.usebuiltin)." >&2
 unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e '
 	my @env = keys %ENV;
 	my $ok = join("|", qw(
@@ -104,6 +105,7 @@ unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e
'
 	my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env);
 	print join("\n", @vars);
 ')
+echo "after $(git config difftool.usebuiltin)." >&2
 unset XDG_CONFIG_HOME
 unset GITPERLLIB
 GIT_AUTHOR_EMAIL=author@example.com
-- snap --

and then weep at this output:

GIT_CONFIG_PARAMETERS="'difftool.usebuiltin=true'"; export GIT_CONFIG_PARAMETERS; bash t7800-difftool.sh -i -v -x
before true.
after .
Initialized empty Git repository in
/home/virtualbox/git/git-for-windows/t/trash
directory.t7800-difftool/.git/
config .
FATAL: Unexpected exit with code 1

In other words, GIT_CONFIG_PARAMETERS is *explicitly scrubbed* from the
environment when we run our tests (by the code block between the "before"
and the "after" statements in the diff above).

Ciao,
Dscho

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-26 16:19                       ` Jeff King
@ 2016-11-26 13:01                         ` Johannes Schindelin
  2016-11-27 16:50                           ` Jeff King
  0 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-26 13:01 UTC (permalink / raw)
  To: Jeff King; +Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker

Hi Peff,

On Sat, 26 Nov 2016, Jeff King wrote:

> On Sat, Nov 26, 2016 at 01:22:28PM +0100, Johannes Schindelin wrote:
> 
> > In other words, GIT_CONFIG_PARAMETERS is *explicitly scrubbed* from the
> > environment when we run our tests (by the code block between the "before"
> > and the "after" statements in the diff above).
> 
> Sorry if I wasn't clear. I meant to modify t7800 to run the tests twice,
> once with the existing script and once with the builtin. I.e., to set
> the variable after test-lib.sh has done its scrubbing, and then use a
> loop or similar to go through the tests twice.
> 
> If you want to control it from outside the test script, you'd need
> something like:
> 
>   if test "$GIT_TEST_DIFFTOOL" = "builtin"

That is a bit magic. I first used "GIT_USE_BUILTIN_DIFFTOOL" and it did
not work. My name is arguably more correct (see also Jakub's note about
"naming is hard"), but yours works because there is a "TEST" substring in
it.

Ciao,
Dscho

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-26 12:22                     ` Johannes Schindelin
@ 2016-11-26 16:19                       ` Jeff King
  2016-11-26 13:01                         ` Johannes Schindelin
  0 siblings, 1 reply; 86+ messages in thread
From: Jeff King @ 2016-11-26 16:19 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker

On Sat, Nov 26, 2016 at 01:22:28PM +0100, Johannes Schindelin wrote:

> In other words, GIT_CONFIG_PARAMETERS is *explicitly scrubbed* from the
> environment when we run our tests (by the code block between the "before"
> and the "after" statements in the diff above).

Sorry if I wasn't clear. I meant to modify t7800 to run the tests twice,
once with the existing script and once with the builtin. I.e., to set
the variable after test-lib.sh has done its scrubbing, and then use a
loop or similar to go through the tests twice.

If you want to control it from outside the test script, you'd need
something like:

  if test "$GIT_TEST_DIFFTOOL" = "builtin"
  then
	GIT_CONFIG_PARAMETERS=...
  fi

-Peff

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

* Re: [PATCH v3 2/2] difftool: implement the functionality in the builtin
  2016-11-25 21:24       ` Jakub Narębski
@ 2016-11-27 11:10         ` Johannes Schindelin
  2016-11-27 11:20           ` Jakub Narębski
  0 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-27 11:10 UTC (permalink / raw)
  To: Jakub Narębski
  Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker

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

Hi Jakub,

On Fri, 25 Nov 2016, Jakub Narębski wrote:

> W dniu 24.11.2016 o 21:55, Johannes Schindelin pisze:
> 
> > The current version of the builtin difftool does not, however, make
> > full use of the internals but instead chooses to spawn a couple of Git
> > processes, still, to make for an easier conversion. There remains a
> > lot of room for improvement, left for a later date.
> [...]
> 
> > Sadly, the speedup is more noticable on Linux than on Windows: a quick
> > test shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s)
> > (real/user/sys) in a Linux VM, down from  (6.529s/3.112s/0.644s),
> > while on Windows, it is (36.064s/2.730s/7.194s), down from
> > (47.637s/2.407s/6.863s). The culprit is most likely the overhead
> > incurred from *still* having to shell out to mergetool-lib.sh and
> > difftool--helper.sh.
> 
> Does this mean that our shell-based testsuite is not well suited to be
> benchmark suite for comparing performance on MS Windows?

It is quite likely the case that shell-based testing will always be
inappropriate for performance testing. Even on Linux.

> Or does it mean that "builtin-difftool" spawning Git processes is the
> problem?

At the moment I would have to guess, and I'd rather not.

> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> >  builtin/difftool.c | 670 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
> >  1 file changed, 669 insertions(+), 1 deletion(-)
> > 
> > diff --git a/builtin/difftool.c b/builtin/difftool.c
> > index 53870bb..3480920 100644
> > --- a/builtin/difftool.c
> > +++ b/builtin/difftool.c
> > @@ -11,9 +11,608 @@
> >   *
> >   * Copyright (C) 2016 Johannes Schindelin
> >   */
> > +#include "cache.h"
> >  #include "builtin.h"
> >  #include "run-command.h"
> >  #include "exec_cmd.h"
> > +#include "parse-options.h"
> > +#include "argv-array.h"
> > +#include "strbuf.h"
> > +#include "lockfile.h"
> > +#include "dir.h"
> > +
> > +static char *diff_gui_tool;
> > +static int trust_exit_code;
> > +
> > +static const char *const builtin_difftool_usage[] = {
> > +	N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"),
> > +	NULL
> > +};
> > +
> > +static int difftool_config(const char *var, const char *value, void *cb)
> > +{
> > +	if (!strcmp(var, "diff.guitool")) {
> 
> Shouldn't you also read other configuration variables, like "diff.tool",
> and it's mergetool fallbacks ("merge.guitool", "merge.tool")?

No, as those configuration variables are not used by the builtin difftool
directly but read by subsequently spawned commands separately. There would
be no use reading them here, for now.

> > +static int print_tool_help(void)
> > +{
> > +	const char *argv[] = { "mergetool", "--tool-help=diff", NULL };
> > +	return run_command_v_opt(argv, RUN_GIT_CMD);
> 
> This looks a bit strange to me, but I guess this is to avoid recursively
> invoking ourself, and { "legacy-difftool", "--tool-help", NULL }; isn't
> that much better.

This is obviously a straight translation of the Perl script (see
https://github.com/git/git/blob/v2.10.2/git-difftool.perl#L40-L46):

	sub print_tool_help
	{
		# See the comment at the bottom of file_diff() for the reason
		# behind
		# using system() followed by exit() instead of exec().
		my $rc = system(qw(git mergetool --tool-help=diff));
		exit($rc | ($rc >> 8));
	}

I read the rest of your review, but it appears that it is more about
style than about substance, while I am only willing to address the latter
issues at the moment. You see, I want to focus on getting difftool correct
first before attempting to make it pretty.

Ciao,
Dscho

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

* Re: [PATCH v3 2/2] difftool: implement the functionality in the builtin
  2016-11-27 11:10         ` Johannes Schindelin
@ 2016-11-27 11:20           ` Jakub Narębski
  0 siblings, 0 replies; 86+ messages in thread
From: Jakub Narębski @ 2016-11-27 11:20 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker

Hello Johannes,

On 27 November 2016 at 12:10, Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:
> On Fri, 25 Nov 2016, Jakub Narębski wrote:
> > W dniu 24.11.2016 o 21:55, Johannes Schindelin pisze:

[...]
> > > +static int difftool_config(const char *var, const char *value, void *cb)
> > > +{
> > > +   if (!strcmp(var, "diff.guitool")) {
> >
> > Shouldn't you also read other configuration variables, like "diff.tool",
> > and it's mergetool fallbacks ("merge.guitool", "merge.tool")?
>
> No, as those configuration variables are not used by the builtin difftool
> directly but read by subsequently spawned commands separately. There would
> be no use reading them here, for now.

Ah, all right then.

Though NEEDSWORK comment would be nice to have here (for when
we don't spawn commands).

[...]
> I read the rest of your review, but it appears that it is more about
> style than about substance, while I am only willing to address the latter
> issues at the moment. You see, I want to focus on getting difftool correct
> first before attempting to make it pretty.
>
> Ciao,
> Dscho

Well, excet for the submodule-relates stuff, which I have skipped,
it looks good to me.
-- 
Jakub Narębski

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-26 13:01                         ` Johannes Schindelin
@ 2016-11-27 16:50                           ` Jeff King
  2016-11-28 17:06                             ` Junio C Hamano
  0 siblings, 1 reply; 86+ messages in thread
From: Jeff King @ 2016-11-27 16:50 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker

On Sat, Nov 26, 2016 at 02:01:36PM +0100, Johannes Schindelin wrote:

> > If you want to control it from outside the test script, you'd need
> > something like:
> > 
> >   if test "$GIT_TEST_DIFFTOOL" = "builtin"
> 
> That is a bit magic. I first used "GIT_USE_BUILTIN_DIFFTOOL" and it did
> not work. My name is arguably more correct (see also Jakub's note about
> "naming is hard"), but yours works because there is a "TEST" substring in
> it.

Yes. You are free to add an exception to the env list in test-lib.sh,
but we usually use GIT_TEST_* to avoid having to do so.

-Peff

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-27 16:50                           ` Jeff King
@ 2016-11-28 17:06                             ` Junio C Hamano
  2016-11-28 17:34                               ` Johannes Schindelin
  0 siblings, 1 reply; 86+ messages in thread
From: Junio C Hamano @ 2016-11-28 17:06 UTC (permalink / raw)
  To: Jeff King; +Cc: Johannes Schindelin, git, David Aguilar, Dennis Kaarsemaker

Jeff King <peff@peff.net> writes:

> On Sat, Nov 26, 2016 at 02:01:36PM +0100, Johannes Schindelin wrote:
>
>> > If you want to control it from outside the test script, you'd need
>> > something like:
>> > 
>> >   if test "$GIT_TEST_DIFFTOOL" = "builtin"
>> 
>> That is a bit magic. I first used "GIT_USE_BUILTIN_DIFFTOOL" and it did
>> not work. My name is arguably more correct (see also Jakub's note about
>> "naming is hard"), but yours works because there is a "TEST" substring in
>> it.
>
> Yes. You are free to add an exception to the env list in test-lib.sh,
> but we usually use GIT_TEST_* to avoid having to do so.

Perhaps

 - The switch between "do I use builtin, or scripted?" mechanism in
   1/2 can look at an environment (just like the old "am" rewrite
   series did), instead of configuration.  This would make the code
   a lot more simppler (you do not have to worry about the
   interaction between "setup" and .git/config).

 - That environment variable can be named GIT_TEST_BUILTIN_DIFFTOOL;
   after all, people are opting into helping to test the new shiny
   to make/prove it ready sooner.

 - The bulk of the existing test for difftool can be moved to a
   dot-included file (in a way similar to t/annotate-tests are
   usable to test both annotate and blame-imitating-annotate).
   Existing PERL prerequisites can all be lost.

 - Two tests can include that dot-included file; one would
   explicitly unset that environment (and gives up without PERL
   prerequisite), while the other explicitly sets it.


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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-28 17:06                             ` Junio C Hamano
@ 2016-11-28 17:34                               ` Johannes Schindelin
  2016-11-28 19:27                                 ` Junio C Hamano
  2016-11-30 16:02                                 ` Jakub Narębski
  0 siblings, 2 replies; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-28 17:34 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker

Hi,

On Mon, 28 Nov 2016, Junio C Hamano wrote:

> Jeff King <peff@peff.net> writes:
> 
> > On Sat, Nov 26, 2016 at 02:01:36PM +0100, Johannes Schindelin wrote:
> >
> >> > If you want to control it from outside the test script, you'd need
> >> > something like:
> >> > 
> >> >   if test "$GIT_TEST_DIFFTOOL" = "builtin"
> >> 
> >> That is a bit magic. I first used "GIT_USE_BUILTIN_DIFFTOOL" and it did
> >> not work. My name is arguably more correct (see also Jakub's note about
> >> "naming is hard"), but yours works because there is a "TEST" substring in
> >> it.
> >
> > Yes. You are free to add an exception to the env list in test-lib.sh,
> > but we usually use GIT_TEST_* to avoid having to do so.
> 
> Perhaps
> 
>  - The switch between "do I use builtin, or scripted?" mechanism in
>    1/2 can look at an environment (just like the old "am" rewrite
>    series did), instead of configuration.  This would make the code
>    a lot more simppler (you do not have to worry about the
>    interaction between "setup" and .git/config).
> 
>  - That environment variable can be named GIT_TEST_BUILTIN_DIFFTOOL;
>    after all, people are opting into helping to test the new shiny
>    to make/prove it ready sooner.
> 
>  - The bulk of the existing test for difftool can be moved to a
>    dot-included file (in a way similar to t/annotate-tests are
>    usable to test both annotate and blame-imitating-annotate).
>    Existing PERL prerequisites can all be lost.
> 
>  - Two tests can include that dot-included file; one would
>    explicitly unset that environment (and gives up without PERL
>    prerequisite), while the other explicitly sets it.

If my main worry was the test suite, I would agree with this plan.

However, I have been bitten time and again by problems that occurred only
in production, our test suite (despite taking already waaaaaay too long to
be truly useful in my daily development) was simply not good enough.

So my plan was different: to let end users opt-in to test this new beast
thoroughly, more thoroughly than any review would.

And for that, environment variables are just not an option. I need
something that can be configured in a portable application, so that the
main Git for Windows installation is unaffected.

My original "create a file in libexec/git-core/" was simple, did the job
reliably, and worked also for testing.

It is a pity that you two gentlemen shot it down for being inelegant. And
ever since, we try to find a solution that is as simple, works as
reliably, also for testing, *and* appeases your tastes.

Ciao,
Dscho

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-28 17:34                               ` Johannes Schindelin
@ 2016-11-28 19:27                                 ` Junio C Hamano
  2016-11-29 20:36                                   ` Johannes Schindelin
  2016-11-30 16:02                                 ` Jakub Narębski
  1 sibling, 1 reply; 86+ messages in thread
From: Junio C Hamano @ 2016-11-28 19:27 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker

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

> However, I have been bitten time and again by problems that occurred only
> in production, our test suite (despite taking already waaaaaay too long to
> be truly useful in my daily development) was simply not good enough.
>
> So my plan was different: to let end users opt-in to test this new beast
> thoroughly, more thoroughly than any review would.

I agree with that 100%.  

We need to ensure "fallback to known working code" escape hatch is
robust for that plan to work well, and that is why (1) I have been
more focused on getting 1/2 right, and (2) I do not think it should
be Windows-only like in your early plan, and (3) I do not think it
would be "this will merely be there for only a month or so", like
you said earlier.

> And for that, environment variables are just not an option. I need
> something that can be configured in a portable application, so that the
> main Git for Windows installation is unaffected.

I am not sure I follow here.  

Are you saying that the users who are opting into the experiment
will keep two installations, one for daily use that avoids getting
hit by the experimental code and the other that is used for testing?
How are they switching between the two?  By using different %PATH%?
I am not sure how it is different from setting an environment
$GIT_TEST_BUILTIN_DIFFTOOL.

In any case, I do not have strong preference between environment and
configuration.  If you can make 1/2 robust with configuration, that
is just as well.  My message you are responding to was merely to
suggest another possibility.

The latter two points in my four-bullet list are hopefully still
viable if you go with the configuration; it may go something like:

 - The bulk of the tests is moved into a common dot-sourced file,
   with (1) PERL prerequite stripped and (2) "git difftool" replaced
   with $git_difftool

 - Two test files do one of

    git_difftool="git difftool"
    git_difftool="git -c difftool.useBuiltin=true difftool"

   and include the dot-sourced file.  The one that does the former
   needs to give up early depending on PERL prerequisite.

perhaps.

> My original "create a file in libexec/git-core/" was simple, did the job
> reliably, and worked also for testing.

It may have been OK for quick-and-dirty hack during development, but
I do not think it was good in anything released.

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-28 19:27                                 ` Junio C Hamano
@ 2016-11-29 20:36                                   ` Johannes Schindelin
  2016-11-29 20:49                                     ` Jeff King
  2016-11-29 20:55                                     ` Junio C Hamano
  0 siblings, 2 replies; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-29 20:36 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker

Hi Junio,

On Mon, 28 Nov 2016, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > However, I have been bitten time and again by problems that occurred only
> > in production, our test suite (despite taking already waaaaaay too long to
> > be truly useful in my daily development) was simply not good enough.
> >
> > So my plan was different: to let end users opt-in to test this new beast
> > thoroughly, more thoroughly than any review would.
> 
> I agree with that 100%.  
> 
> [...]
> 
> > And for that, environment variables are just not an option. I need
> > something that can be configured in a portable application, so that the
> > main Git for Windows installation is unaffected.
> 
> I am not sure I follow here.  
> 
> Are you saying that the users who are opting into the experiment
> will keep two installations, one for daily use that avoids getting
> hit by the experimental code and the other that is used for testing?

I have obviously done a real bad job at explaining the Windows situation
well enough.

Many, many users have multiple installations of Git for Windows. If you
have GitHub for Windows and installed the command-line tools: you got one.
If you installed Git for Windows, you got another one. If you installed
Visual Studio, chances are you have another one. If you got any number of
third-party tools requiring Git functionality, you have another one.

They all live in separate directories that are their own little pseudo
Unix root directory structures, complete with etc/, usr/, var/.

Users do not necessarily keep track, or for that matter, are aware of, the
multiple different installations.

Obviously, I do not want any installation other than the one the user just
installed to pick up on the configuration.

So the suggestion by both you and Peff, to use an environment variable,
which is either global, or requires the user to set it manually per
session, is simply not a good idea at all.

> > My original "create a file in libexec/git-core/" was simple, did the job
> > reliably, and worked also for testing.
> 
> It may have been OK for quick-and-dirty hack during development, but
> I do not think it was good in anything released.

Well, you say that it is quick and dirty.

I say it is the only viable solution I saw so far. All proposed
alternative solutions fall flat on their bellies, simply by not working in
all the cases I need them to work.

As I said elsewhere: I look for a correct solution first, and then I
thrive to make it pretty. You start the other way round, and I do not have
time for that right now.

Ciao,
Dscho

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-29 20:36                                   ` Johannes Schindelin
@ 2016-11-29 20:49                                     ` Jeff King
  2016-11-30 12:30                                       ` Johannes Schindelin
  2016-11-29 20:55                                     ` Junio C Hamano
  1 sibling, 1 reply; 86+ messages in thread
From: Jeff King @ 2016-11-29 20:49 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Junio C Hamano, git, David Aguilar, Dennis Kaarsemaker

On Tue, Nov 29, 2016 at 09:36:55PM +0100, Johannes Schindelin wrote:

> So the suggestion by both you and Peff, to use an environment variable,
> which is either global, or requires the user to set it manually per
> session, is simply not a good idea at all.

No, my suggestion was to use config and have the test suite use an
environment variable to test both cases (preferably automatically,
without the user having to do anything).

I do not see how that fails to cover all of your use cases.

-Peff

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-29 20:36                                   ` Johannes Schindelin
  2016-11-29 20:49                                     ` Jeff King
@ 2016-11-29 20:55                                     ` Junio C Hamano
  2016-11-30 12:30                                       ` Johannes Schindelin
  1 sibling, 1 reply; 86+ messages in thread
From: Junio C Hamano @ 2016-11-29 20:55 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker

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

> So the suggestion by both you and Peff, to use an environment variable,
> which is either global, or requires the user to set it manually per
> session, is simply not a good idea at all.

As I already said, I do not have a strong preference between config
and env.  I raised the env as a possible alternative that you can
think about its pros and cons, and as I already said, if you thought
and your concluded that config would work better for your needs,
that is fine by me.

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-29 20:55                                     ` Junio C Hamano
@ 2016-11-30 12:30                                       ` Johannes Schindelin
  2016-12-01 23:33                                         ` Junio C Hamano
  0 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-30 12:30 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker

Hi Junio,

On Tue, 29 Nov 2016, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > So the suggestion by both you and Peff, to use an environment variable,
> > which is either global, or requires the user to set it manually per
> > session, is simply not a good idea at all.
> 
> As I already said, I do not have a strong preference between config
> and env.  I raised the env as a possible alternative that you can
> think about its pros and cons, and as I already said, if you thought
> and your concluded that config would work better for your needs,
> that is fine by me.

The env flat out fails, on the grounds of not integrating nicely into a
Git for Windows installer.

The config kinda works now. But for what price. It stole 4 hours I did not
have. When the libexec/git-core/use-builtin-difftool solution took me a
grand total of half an hour to devise, implement and test.

And you know what? I still do not really see what is so bad about it.

And I still see what is bad about the config "solution": it *creates* a
chicken-and-egg problem with the order of config reading vs running
scripts. It *creates* problems for requiring to spawn a `git config` call
because reading the config in-process would mess up the global variables
and environment *beyond repair*, making it *impossible* to even spawn the
git-legacy-difftool Perl script.

In short: the config setting now works. But it is ugly as hell. I wish I
never had listened to you.

Ciao,
Dscho

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-29 20:49                                     ` Jeff King
@ 2016-11-30 12:30                                       ` Johannes Schindelin
  2016-11-30 12:35                                         ` Jeff King
  0 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2016-11-30 12:30 UTC (permalink / raw)
  To: Jeff King; +Cc: Junio C Hamano, git, David Aguilar, Dennis Kaarsemaker

Hi Peff,

On Tue, 29 Nov 2016, Jeff King wrote:

> On Tue, Nov 29, 2016 at 09:36:55PM +0100, Johannes Schindelin wrote:
> 
> > So the suggestion by both you and Peff, to use an environment variable,
> > which is either global, or requires the user to set it manually per
> > session, is simply not a good idea at all.
> 
> No, my suggestion was to use config and have the test suite use an
> environment variable to test both cases (preferably automatically,
> without the user having to do anything).
> 
> I do not see how that fails to cover all of your use cases.

Oh, so the suggestion is to have *both* a config *and* an environment
variable. That is not elegant.

Ciao,
Dscho

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-30 12:30                                       ` Johannes Schindelin
@ 2016-11-30 12:35                                         ` Jeff King
  0 siblings, 0 replies; 86+ messages in thread
From: Jeff King @ 2016-11-30 12:35 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Junio C Hamano, git, David Aguilar, Dennis Kaarsemaker

On Wed, Nov 30, 2016 at 01:30:47PM +0100, Johannes Schindelin wrote:

> On Tue, 29 Nov 2016, Jeff King wrote:
> 
> > On Tue, Nov 29, 2016 at 09:36:55PM +0100, Johannes Schindelin wrote:
> > 
> > > So the suggestion by both you and Peff, to use an environment variable,
> > > which is either global, or requires the user to set it manually per
> > > session, is simply not a good idea at all.
> > 
> > No, my suggestion was to use config and have the test suite use an
> > environment variable to test both cases (preferably automatically,
> > without the user having to do anything).
> > 
> > I do not see how that fails to cover all of your use cases.
> 
> Oh, so the suggestion is to have *both* a config *and* an environment
> variable. That is not elegant.

No, that is not at all what I said.  I was going to explain myself
again, but I do not see what good it would do, as clearly my point did
not come across in the other three emails. And then you would just
complain that I am making work for you. So whatever. I do not care about
your difftool topic at all. Do whatever you like (which hey, I already
said before, too).

-Peff

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-28 17:34                               ` Johannes Schindelin
  2016-11-28 19:27                                 ` Junio C Hamano
@ 2016-11-30 16:02                                 ` Jakub Narębski
  2016-11-30 18:39                                   ` Junio C Hamano
  1 sibling, 1 reply; 86+ messages in thread
From: Jakub Narębski @ 2016-11-30 16:02 UTC (permalink / raw)
  To: Johannes Schindelin, Junio C Hamano
  Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker

Hello,

W dniu 28.11.2016 o 18:34, Johannes Schindelin pisze:

> My original "create a file in libexec/git-core/" was simple, did the job
> reliably, and worked also for testing.
> 
> It is a pity that you two gentlemen shot it down for being inelegant. And
> ever since, we try to find a solution that is as simple, works as
> reliably, also for testing, *and* appeases your tastes.

I just would like to note that existence of file is used for both
git-daemon and gitweb (the latter following the git-daemon example).

So there is a precedent for the use of this mechanism.

Best,
-- 
Jakub Narębski


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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-30 16:02                                 ` Jakub Narębski
@ 2016-11-30 18:39                                   ` Junio C Hamano
  0 siblings, 0 replies; 86+ messages in thread
From: Junio C Hamano @ 2016-11-30 18:39 UTC (permalink / raw)
  To: Jakub Narębski
  Cc: Johannes Schindelin, Jeff King, git, David Aguilar,
	Dennis Kaarsemaker

Jakub Narębski <jnareb@gmail.com> writes:

>> My original "create a file in libexec/git-core/" was simple, did the job
>> reliably, and worked also for testing.
>> 
>> It is a pity that you two gentlemen shot it down for being inelegant. And
>> ever since, we try to find a solution that is as simple, works as
>> reliably, also for testing, *and* appeases your tastes.
>
> I just would like to note that existence of file is used for both
> git-daemon and gitweb (the latter following the git-daemon example).
>
> So there is a precedent for the use of this mechanism.

I think you are thinking about git-daemon-export-ok (for 'git
daemon') and $GITWEB_EXPORT_OK file (for 'gitweb').

You do realize that it is apples-and-oranges [*1*] to take these as
analogous to what Dscho is trying to do, don't you?

First of all, these are to control access to each repository on the
server side; the presence of the file is checked in each repository.
What Dscho wants is to control the behaviour of an installation of
Git as a whole, no matter which repository is being accessed [*2*,
*3*].

More importantly, did you notice that git-daemon-export-ok predates
the configuration mechanism by a large margin?  The "does the file
exist?" check done in a87e8be2ae ("Add a "git-daemon" that listens
on a TCP port", 2005-07-13) is a relic from the past [*4*], and
32f4aaccaa ("gitweb: export options", 2006-09-17) added
GITWEB_EXPORT_OK to mimic it, also long time ago [*5*].  They are
not something you would want to mimic in new programs these days.

Besides, $GIT_EXEC_PATH is where you place git subcommands.  Who in
the right mind considers it even remotely sane to design a system
where you have to throw in a file that is not a command to /usr/bin
to control the behaviour of your system? [*6*]

So the "precedent" is irrelevant in the first place, and even if it
were relevant, it is a bad piece of advice to mimic it.


[Footnote]

*1* Or is it apples-and-pineapples these days?

*2* Not that I agree with that desire, if I understand him correctly
    from his description against the approach based on an
    environment variable.  If a user has multiple installations and
    not even aware of which one of them s/he is currently using, a
    mechanism that affects only one of them (instead of consistently
    affecting all of them) would lead to more confusion, I would
    think.  

*3* If such hermetically configured independent installations are
    desirable, etc/gitconfig aka "git config --system" is a more
    appropriate thing to use, and you do not need to do repository
    discovery before you can read it.

*4* If we had config mechanism, we would have used it just like we
    use daemon.* variables to control what services are enabled for
    each repository.

*5* By that time, the config mechanism did already exist, so the
    GITWEB_EXPORT_OK could have been a per-repository configuration,
    but "gitweb" had another excuse to deviate from the norm.  "Is
    this repository visible?" was done during repository listing and
    the script did not want to run "git config" in each and every
    repository-like directory it encountered in File::Find::find().

*6* And I do not think $GIT_EXEC_PATH vs /usr/bin is
    apples-and-oranges analogy.

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-11-30 12:30                                       ` Johannes Schindelin
@ 2016-12-01 23:33                                         ` Junio C Hamano
  2016-12-05 10:36                                           ` Johannes Schindelin
  0 siblings, 1 reply; 86+ messages in thread
From: Junio C Hamano @ 2016-12-01 23:33 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker

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

> The config kinda works now. But for what price. It stole 4 hours I did not
> have. When the libexec/git-core/use-builtin-difftool solution took me a
> grand total of half an hour to devise, implement and test.
>
> And you know what? I still do not really see what is so bad about it.

I was wondering if I should explain myself again, even though I do
not see what good it would do, as clearly my point did not come
across in the other emails.  And then you would just complain that I
am making work for you.  Clearly you do not seem to see why placing
random files in $GIT_EXEC_PATH, which is a place for git subcommand
implementations, is wrong, so I won't repeat it to you again.

But you need to remember that you are not working on a Windows-only
project.  In non-Windows environment, many users would not have
write access to /usr/libexec/git-core directory, but it is not just
easy for them to write into ~/.gitconfig, but that is the way they
are accustomed to, in order to affect the behaviour of Git for them.

As to "I have to spawn config", I think it is sensible to start the
cmd_difftool() wrapper without adding RUN_SETUP to the command
table, then call git_config_get_bool() to check the configuration
only from system and per-user files, and then finally either call
into builtin_difftool() where setup_git_directory() is called, or
spawn the scripted difftool, as Peff already said.  Your "users
opt-in while installing" is not about setting per-repository option.

Calling git_config*(), setup_git_directory() and then git_config*()
in this order should be safe, as setup_git_directory() would clear
potentially cached configuration values read by any previous
git_config*() calls, so any configuration enquiry made by
builtin_difftool() would read from all three sources, not just
system and per-user.

So there is no chicken-and-egg issue, either.

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-12-01 23:33                                         ` Junio C Hamano
@ 2016-12-05 10:36                                           ` Johannes Schindelin
  2016-12-05 18:37                                             ` Junio C Hamano
  0 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2016-12-05 10:36 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker

Hi Junio,

On Thu, 1 Dec 2016, Junio C Hamano wrote:

> As to "I have to spawn config", I think it is sensible to start the
> cmd_difftool() wrapper without adding RUN_SETUP to the command table,
> then call git_config_get_bool() to check the configuration only from
> system and per-user files, and then finally either call into
> builtin_difftool() where setup_git_directory() is called, or spawn the
> scripted difftool, as Peff already said.

I just spent a considerable amount of time to figure out that I overlooked
your comment about "only from system and per-user files".

> Your "users opt-in while installing" is not about setting per-repository
> option.

Wow. That would make things really inconsistent.

I know that *I* would want to be able to switch that opt-in feature off
for repositories where I absolutely rely on some rock-solid difftool. And
as a user I would not only be shocked when it would not work as expected,
but I would be *positively* shocked to learn that this is intended, by
design.

Seriously, do you really think it is a good idea to have
git_config_get_value() *ignore* any value in .git/config?

Really?

It caught *me* by surprise, and it definitely makes for a very, very bad
user experience.

And this is much more fundamental than just difftool.useBuiltin.

I see for example that our pager.<cmd> setting is ignoring per-repository
settings. That is, if the user sets pager.<cmd> in ~/.gitconfig and then
tries to override this in a specific repository (which any user would only
do for very good reasons, I am sure), Git would happily ignore that
careful setup and create an angry user.

The only reason this did not blow up in our face yet is that users
apparently do not make use of this feature yet. Which does not make this
behavior (that "git_config_get_value()" happily ignores .git/config) less
broken.

We need to fix this.

Ciao,
Dscho

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-12-05 10:36                                           ` Johannes Schindelin
@ 2016-12-05 18:37                                             ` Junio C Hamano
  2016-12-06 13:16                                               ` Johannes Schindelin
  0 siblings, 1 reply; 86+ messages in thread
From: Junio C Hamano @ 2016-12-05 18:37 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker

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

> Seriously, do you really think it is a good idea to have
> git_config_get_value() *ignore* any value in .git/config?

When we do not know ".git/" directory we see is the repository we
were told to work on, then we should ignore ".git/config" file.  So
allowing git_config_get_value() to _ignore_ ".git/config" before the
program calls setup_git_directory() does have its uses.

But I agree that "difftool.useBuiltin" that flips between two
implementations is a different use case than the above.  Both
implementations eventually want to work on ".git/" and would
want to read from ".git/config".

> We need to fix this.

I have a feeling that "environment modifications that cannot be
undone" we used as the rationale in 73c2779f42 ("builtin-am:
implement skeletal builtin am", 2015-08-04) might be overly
pessimistic and in order to implement undo_setup_git_directory(),
all we need to do may just be matter of doing a chdir(2) back to
prefix and unsetting GIT_PREFIX environment, but I haven't looked
into details of the setup sequence recently.


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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-12-05 18:37                                             ` Junio C Hamano
@ 2016-12-06 13:16                                               ` Johannes Schindelin
  2016-12-06 13:36                                                 ` Jeff King
  0 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2016-12-06 13:16 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker

Hi Junio,

On Mon, 5 Dec 2016, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > Seriously, do you really think it is a good idea to have
> > git_config_get_value() *ignore* any value in .git/config?
> 
> When we do not know ".git/" directory we see is the repository we were
> told to work on, then we should ignore ".git/config" file.  So allowing
> git_config_get_value() to _ignore_ ".git/config" before the program
> calls setup_git_directory() does have its uses.

I think you are wrong. This is yet another inconsistent behavior that
violates the Law of Least Surprises.

> > We need to fix this.
> 
> I have a feeling that "environment modifications that cannot be undone"
> we used as the rationale in 73c2779f42 ("builtin-am: implement skeletal
> builtin am", 2015-08-04) might be overly pessimistic and in order to
> implement undo_setup_git_directory(), all we need to do may just be
> matter of doing a chdir(2) back to prefix and unsetting GIT_PREFIX
> environment, but I haven't looked into details of the setup sequence
> recently.

The problem is that we may not know enough anymore to "undo
setup_git_directory()", as some environment variables may have been set
before calling Git. If we simply unset the environment variables, we do it
incorrectly. On the other hand, the environment variables *may* have been
set by setup_git_directory(). In which case we *do* have to unset them.

The entire setup_git_directory() logic is a bit of a historically-grown
garden.

Ciao,
Dscho

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-12-06 13:16                                               ` Johannes Schindelin
@ 2016-12-06 13:36                                                 ` Jeff King
  2016-12-06 14:48                                                   ` Johannes Schindelin
  0 siblings, 1 reply; 86+ messages in thread
From: Jeff King @ 2016-12-06 13:36 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Junio C Hamano, git, David Aguilar, Dennis Kaarsemaker

On Tue, Dec 06, 2016 at 02:16:35PM +0100, Johannes Schindelin wrote:

> > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> > 
> > > Seriously, do you really think it is a good idea to have
> > > git_config_get_value() *ignore* any value in .git/config?
> > 
> > When we do not know ".git/" directory we see is the repository we were
> > told to work on, then we should ignore ".git/config" file.  So allowing
> > git_config_get_value() to _ignore_ ".git/config" before the program
> > calls setup_git_directory() does have its uses.
> 
> I think you are wrong. This is yet another inconsistent behavior that
> violates the Law of Least Surprises.

There are surprises in this code any way you turn.  If we have not
called setup_git_directory(), then how does git_config_get_value() know
if we are in a repository or not?

Should it blindly look at ".git/config"? Now your program behaves
differently depending on whether you are in the top-level of the working
tree.

Should it speculatively do repo discovery, and use any discovered config
file? Now some commands respect config that they shouldn't (e.g.,
running "git init foo.git" from inside another repository will
accidentally pick up the value of core.sharedrepository from wherever
you happen to run it).

So I think the caller of the config code has to provide some kind of
context about how it is expecting to run and how the value will be used.

Right now if setup_git_directory() or similar hasn't been called, the
config code does not look. Ideally there would be a way for a caller to
say "I am running early and not even sure yet if we are in a repo;
please speculatively try to find repo config for me".

The pager code does this manually, and without great accuracy; see the
hack in pager.c's read_early_config(). I think the way forward is:

  1. Make that an optional behavior in git_config_with_options() so
     other spots can reuse it (probably alias lookup, and something like
     your difftool config).

  2. Make it more accurate. Right now it blindly looks in .git/config,
     but it should be able to do the usual repo-detection (_without_
     actually entering the repo) to try to find a possible config file.

-Peff

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-12-06 13:36                                                 ` Jeff King
@ 2016-12-06 14:48                                                   ` Johannes Schindelin
  2016-12-06 15:09                                                     ` Jeff King
  0 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2016-12-06 14:48 UTC (permalink / raw)
  To: Jeff King; +Cc: Junio C Hamano, git, David Aguilar, Dennis Kaarsemaker

Hi Peff,

On Tue, 6 Dec 2016, Jeff King wrote:

> On Tue, Dec 06, 2016 at 02:16:35PM +0100, Johannes Schindelin wrote:
> 
> > > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> > > 
> > > > Seriously, do you really think it is a good idea to have
> > > > git_config_get_value() *ignore* any value in .git/config?
> > > 
> > > When we do not know ".git/" directory we see is the repository we
> > > were told to work on, then we should ignore ".git/config" file.  So
> > > allowing git_config_get_value() to _ignore_ ".git/config" before the
> > > program calls setup_git_directory() does have its uses.
> > 
> > I think you are wrong. This is yet another inconsistent behavior that
> > violates the Law of Least Surprises.
> 
> There are surprises in this code any way you turn.  If we have not
> called setup_git_directory(), then how does git_config_get_value() know
> if we are in a repository or not?

My biggest surprise, frankly, would be that `git init` and `git clone` are
not special-cased.

> Should it blindly look at ".git/config"?

Absolutely not, of course. You did not need me to say that.

> Now your program behaves differently depending on whether you are in the
> top-level of the working tree.

Exactly. This, BTW, is already how the code would behave if anybody called
`git_path()` before `setup_git_directory()`, as the former function
implicitly calls `setup_git_env()` which does *not* call
`setup_git_directory()` but *does* set up `git_dir` which is then used by
`do_git_config_sequence()`..

We have a few of these nasty surprises in our code base, where code
silently assumes that global state is set up correctly, and succeeds in
sometimes surprising ways if it is not.

> Should it speculatively do repo discovery, and use any discovered config
> file?

Personally, I find the way we discover the repository most irritating. It
seems that we have multiple, mutually incompatible code paths
(`setup_git_directory()` and `setup_git_env()` come to mind already, and
it does not help that consecutive calls to `setup_git_directory()` will
yield a very unexpected outcome).

Just try to explain to a veteran software engineer why you cannot call
`setup_git_directory_gently()` multiple times and expect the very same
return value every time.

Another irritation is that some commands that clearly would like to use a
repository if there is one (such as `git diff`) are *not* marked with
RUN_SETUP_GENTLY, due to these unfortunate implementation details.

> Now some commands respect config that they shouldn't (e.g., running "git
> init foo.git" from inside another repository will accidentally pick up
> the value of core.sharedrepository from wherever you happen to run it).

Right. That points to another problem with the way we do things: we leak
global state from discovering a git_dir, which means that we can neither
undo nor override it.

If we discovered our git_dir in a robust manner, `git init` could say:
hey, this git_dir is actually not what I wanted, here is what I want.

Likewise, `git submodule` would eventually be able to run in the very same
process as the calling `git`, as would a local fetch.

> So I think the caller of the config code has to provide some kind of
> context about how it is expecting to run and how the value will be used.

Yep.

Maybe even go a step further and say that the config code needs a context
"object".

> Right now if setup_git_directory() or similar hasn't been called, the
> config code does not look.

Correct.

Actually, half correct. If setup_git_directory() has not been called, but,
say, git_path() (and thereby implicitly setup_git_env()), the config code
*does* look. At a hard-coded .git/config.

> Ideally there would be a way for a caller to say "I am running early and
> not even sure yet if we are in a repo; please speculatively try to find
> repo config for me".

And ideally, it would not roll *yet another* way to discover the git_dir,
but it would reuse the same function (fixing it not to chdir()
unilaterally).

Of course, not using `chdir()` means that we have to figure out symbolic
links somehow, in case somebody works from a symlinked subdirectory, e.g.:

	ln -s $PWD/t/ ~/test-directory
	cd ~/test-directory
	git log

> The pager code does this manually, and without great accuracy; see the
> hack in pager.c's read_early_config().

I saw it. And that is what triggered the mail you are responding to (you
probably saw my eye-rolling between the lines).

The real question is: can we fix this? Or is there simply too great
reluctance to change the current code?

> I think the way forward is:
> 
>   1. Make that an optional behavior in git_config_with_options() so
>      other spots can reuse it (probably alias lookup, and something like
>      your difftool config).

Ideally: *any* early call to `git_config_get_value()`. Make things less
surprising.

>   2. Make it more accurate. Right now it blindly looks in .git/config,
>      but it should be able to do the usual repo-detection (_without_
>      actually entering the repo) to try to find a possible config file.

The real trick will be to convince Junio to have a single function for
git_dir discovery, I guess, lest we have multiple, slightly incompatible
ways to discover it. I expect a lot of resistance here, because we would
have to change tried-and-tested (if POLA-violating) code.

Ciao,
Dscho

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-12-06 14:48                                                   ` Johannes Schindelin
@ 2016-12-06 15:09                                                     ` Jeff King
  2016-12-06 18:22                                                       ` Stefan Beller
  0 siblings, 1 reply; 86+ messages in thread
From: Jeff King @ 2016-12-06 15:09 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Junio C Hamano, git, David Aguilar, Dennis Kaarsemaker

On Tue, Dec 06, 2016 at 03:48:38PM +0100, Johannes Schindelin wrote:

> > Should it blindly look at ".git/config"?
> 
> Absolutely not, of course. You did not need me to say that.
> 
> > Now your program behaves differently depending on whether you are in the
> > top-level of the working tree.
> 
> Exactly. This, BTW, is already how the code would behave if anybody called
> `git_path()` before `setup_git_directory()`, as the former function
> implicitly calls `setup_git_env()` which does *not* call
> `setup_git_directory()` but *does* set up `git_dir` which is then used by
> `do_git_config_sequence()`..
> 
> We have a few of these nasty surprises in our code base, where code
> silently assumes that global state is set up correctly, and succeeds in
> sometimes surprising ways if it is not.

Right. I have been working on fixing this. v2.11 has a ton of tweaks in
this area, and my patch to die() rather than default to ".git" is
cooking in next to catch any stragglers.

> > Should it speculatively do repo discovery, and use any discovered config
> > file?
> 
> Personally, I find the way we discover the repository most irritating. It
> seems that we have multiple, mutually incompatible code paths
> (`setup_git_directory()` and `setup_git_env()` come to mind already, and
> it does not help that consecutive calls to `setup_git_directory()` will
> yield a very unexpected outcome).

I agree. We should be killing off setup_git_env(), which is something
I've been slowly working towards over the years.

There are some annoyances with setup_git_directory(), too (like the fact
that you cannot ask "is there a git repository you can find" without
making un-recoverable changes to the process state). I think we should
fix those, too.

> > Now some commands respect config that they shouldn't (e.g., running "git
> > init foo.git" from inside another repository will accidentally pick up
> > the value of core.sharedrepository from wherever you happen to run it).
> 
> Right. That points to another problem with the way we do things: we leak
> global state from discovering a git_dir, which means that we can neither
> undo nor override it.
> 
> If we discovered our git_dir in a robust manner, `git init` could say:
> hey, this git_dir is actually not what I wanted, here is what I want.
> 
> Likewise, `git submodule` would eventually be able to run in the very same
> process as the calling `git`, as would a local fetch.

Yep, I agree with all that. I do think things _have_ been improving over
the years, and the code is way less tangled than it used to be. But
there are so many corner cases, and the code is so fundamental, that it
has been slow going. I'd be happy to review patches if you want to push
it along.

> > So I think the caller of the config code has to provide some kind of
> > context about how it is expecting to run and how the value will be used.
> 
> Yep.
> 
> Maybe even go a step further and say that the config code needs a context
> "object".

If I were writing git from scratch, I'd consider making a "struct
repository" object. I'm not sure how painful it would be to retro-fit it
at this point.

> > Right now if setup_git_directory() or similar hasn't been called, the
> > config code does not look.
> 
> Correct.
> 
> Actually, half correct. If setup_git_directory() has not been called, but,
> say, git_path() (and thereby implicitly setup_git_env()), the config code
> *does* look. At a hard-coded .git/config.

Not since b9605bc4f (config: only read .git/config from configured
repos, 2016-09-12). That's why pager.c needs its little hack.

I guess you could see that as a step backwards, but I think it is
necessary one on the road to doing it right.

> > Ideally there would be a way for a caller to say "I am running early and
> > not even sure yet if we are in a repo; please speculatively try to find
> > repo config for me".
> 
> And ideally, it would not roll *yet another* way to discover the git_dir,
> but it would reuse the same function (fixing it not to chdir()
> unilaterally).

Yes, absolutely.

> Of course, not using `chdir()` means that we have to figure out symbolic
> links somehow, in case somebody works from a symlinked subdirectory, e.g.:
> 
> 	ln -s $PWD/t/ ~/test-directory
> 	cd ~/test-directory
> 	git log

There's work happening elsewhere[1] on making real_path() work without
calling chdir(). Which necessarily involves resolving symlinks
ourselves. I wonder if we could leverage that work here (ideally by
using real_path() under the hood, and not reimplementing the same
readlink() logic ourselves).

[1] http://public-inbox.org/git/1480964316-99305-1-git-send-email-bmwill@google.com/

> > The pager code does this manually, and without great accuracy; see the
> > hack in pager.c's read_early_config().
> 
> I saw it. And that is what triggered the mail you are responding to (you
> probably saw my eye-rolling between the lines).
> 
> The real question is: can we fix this? Or is there simply too great
> reluctance to change the current code?

The code in pager.c is only a month or two old. Like I said, it's ugly,
but I think it's a necessary step on the way forward. So I don't think
there's reluctance at all. The next steps (which I outlined) just
haven't been taken yet.

> > I think the way forward is:
> > 
> >   1. Make that an optional behavior in git_config_with_options() so
> >      other spots can reuse it (probably alias lookup, and something like
> >      your difftool config).
> 
> Ideally: *any* early call to `git_config_get_value()`. Make things less
> surprising.

I'm not convinced that's a good idea. The changes in b9605bc4f were
motivated by a real bug, which your suggestion would reintroduce (namely
low-level code run by git-init ending up with config variables from a
repo that _should_ be unrelated).

In my mental model, the cases are:

  1. We are "early" in the process, before we know if we have a repo or
     not. These early looks should speculatively look at repo config,
     which is confined to generic things like pager config, alias
     config, etc.

  2. We are in a repo. Obviously look at $GIT_DIR/config.

  3. We are in a program which has done setup and determined we are
     _not_ in a repo. Definitely do not look at .git/config or anything
     else.

My plan was for the config code to default to (3) when we are not in a
repo, but let some lookups ask specifically for (1).

If you want to default to (1), you need some way for programs to say "I
am really case (3); do not look for a repo". And it needs to be global,
as the config lookup may be done by much lower-level code. That could be
by turning startup_info->have_repository into a tri-state. It just
wasn't the way I was planning on it.

> >   2. Make it more accurate. Right now it blindly looks in .git/config,
> >      but it should be able to do the usual repo-detection (_without_
> >      actually entering the repo) to try to find a possible config file.
> 
> The real trick will be to convince Junio to have a single function for
> git_dir discovery, I guess, lest we have multiple, slightly incompatible
> ways to discover it. I expect a lot of resistance here, because we would
> have to change tried-and-tested (if POLA-violating) code.

Personally, I haven't seen any resistance from Junio on refactoring this
area. I'm sure he is concerned that we do not regress, but it's not like
the area has been unchanged over the years. It has been slow going
because we want to do it carefully, but I think we are actually at the
point now where the next step is making setup_git_directory() more sane.

-Peff

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-12-06 15:09                                                     ` Jeff King
@ 2016-12-06 18:22                                                       ` Stefan Beller
  2016-12-06 18:35                                                         ` Jeff King
  0 siblings, 1 reply; 86+ messages in thread
From: Stefan Beller @ 2016-12-06 18:22 UTC (permalink / raw)
  To: Jeff King
  Cc: Johannes Schindelin, Junio C Hamano, git@vger.kernel.org,
	David Aguilar, Dennis Kaarsemaker

On Tue, Dec 6, 2016 at 7:09 AM, Jeff King <peff@peff.net> wrote:

>>
>> Maybe even go a step further and say that the config code needs a context
>> "object".
>
> If I were writing git from scratch, I'd consider making a "struct
> repository" object. I'm not sure how painful it would be to retro-fit it
> at this point.

Would it be possible to introduce "the repo" struct similar to "the index"
in cache.h?

From a submodule perspective I would very much welcome this
object oriented approach to repositories.

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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-12-06 18:22                                                       ` Stefan Beller
@ 2016-12-06 18:35                                                         ` Jeff King
  2017-01-18 22:38                                                           ` Brandon Williams
  0 siblings, 1 reply; 86+ messages in thread
From: Jeff King @ 2016-12-06 18:35 UTC (permalink / raw)
  To: Stefan Beller
  Cc: Johannes Schindelin, Junio C Hamano, git@vger.kernel.org,
	David Aguilar, Dennis Kaarsemaker

On Tue, Dec 06, 2016 at 10:22:21AM -0800, Stefan Beller wrote:

> >> Maybe even go a step further and say that the config code needs a context
> >> "object".
> >
> > If I were writing git from scratch, I'd consider making a "struct
> > repository" object. I'm not sure how painful it would be to retro-fit it
> > at this point.
> 
> Would it be possible to introduce "the repo" struct similar to "the index"
> in cache.h?
> 
> From a submodule perspective I would very much welcome this
> object oriented approach to repositories.

I think it may be more complicated, because there's some implicit global
state in "the repo", like where files are relative to our cwd. All of
those low-level functions would have to start caring about which repo
we're talking about so they can prefix the appropriate working tree
path, etc.

For some operations that would be fine, but there are things that would
subtly fail for submodules. I'm thinking we'd end up with some code
state like:

  /* finding a repo does not modify global state; good */
  struct repository *repo = repo_discover(".");

  /* obvious repo-level operations like looking up refs can be done with
   * a repository object; good */
  repo_for_each_ref(repo, callback, NULL);

  /*
   * "enter" the repo so that we are at the top-level of the working
   * tree, etc. After this you can actually look at the index without
   * things breaking.
   */
  repo_enter(repo);

That would be enough to implement a lot of submodule-level stuff, but it
would break pretty subtly as soon as you asked the submodule about its
working tree. The solution is to make everything that accesses the
working tree aware of the idea of a working tree root besides the cwd.
But that's a pretty invasive change.

-Peff

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

* [PATCH v4 0/4] Show Git Mailing List: a builtin difftool
  2016-11-24 20:55   ` [PATCH v3 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin
  2016-11-24 20:55     ` [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin Johannes Schindelin
  2016-11-24 20:55     ` [PATCH v3 2/2] difftool: implement the functionality in the builtin Johannes Schindelin
@ 2017-01-02 16:16     ` Johannes Schindelin
  2017-01-02 16:22       ` [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path() Johannes Schindelin
                         ` (4 more replies)
  2 siblings, 5 replies; 86+ messages in thread
From: Johannes Schindelin @ 2017-01-02 16:16 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

I have been working on the builtin difftool for a while now, for two
reasons:

1. Perl is really not native on Windows. Not only is there a performance
   penalty to be paid just for running Perl scripts, we also have to deal
   with the fact that users may have different Perl installations, with
   different options, and some other Perl installation may decide to set
   PERL5LIB globally, wreaking havoc with Git for Windows' Perl (which we
   have to use because almost all other Perl distributions lack the
   Subversion bindings we need for `git svn`).

2. Perl makes for a rather large reason that Git for Windows' installer
   weighs in with >30MB. While one Perl script less does not relieve us
   of that burden, it is one step in the right direction.

Changes since v3:

- made path_entry_cmp static

- fixed a few bugs identified by Coverity

- fixed overzealous status parsing that did not expect any number after
  C or R


Johannes Schindelin (4):
  Avoid Coverity warning about unfree()d git_exec_path()
  difftool: add a skeleton for the upcoming builtin
  difftool: implement the functionality in the builtin
  t7800: run both builtin and scripted difftool, for now

 .gitignore                                    |   1 +
 Makefile                                      |   3 +-
 builtin.h                                     |   1 +
 builtin/difftool.c                            | 733 ++++++++++++++++++++++++++
 exec_cmd.c                                    |   5 +-
 git-difftool.perl => git-legacy-difftool.perl |   0
 git.c                                         |   6 +
 t/t7800-difftool.sh                           |  29 +
 8 files changed, 776 insertions(+), 2 deletions(-)
 create mode 100644 builtin/difftool.c
 rename git-difftool.perl => git-legacy-difftool.perl (100%)


base-commit: e05806da9ec4aff8adfed142ab2a2b3b02e33c8c
Published-As: https://github.com/dscho/git/releases/tag/builtin-difftool-v4
Fetch-It-Via: git fetch https://github.com/dscho/git builtin-difftool-v4

Interdiff vs v3:

 diff --git a/builtin/difftool.c b/builtin/difftool.c
 index 3480920fea..2115e548a5 100644
 --- a/builtin/difftool.c
 +++ b/builtin/difftool.c
 @@ -73,8 +73,10 @@ static int parse_index_info(char *p, int *mode1, int *mode2,
  	if (*p != ' ')
  		return error("expected ' ', got '%c'", *p);
  	*status = *++p;
 -	if (!status || p[1])
 -		return error("unexpected trailer: '%s'", p);
 +	if (!*status)
 +		return error("missing status");
 +	if (p[1] && !isdigit(p[1]))
 +		return error("unexpected trailer: '%s'", p + 1);
  	return 0;
  }
  
 @@ -107,7 +109,8 @@ static int use_wt_file(const char *workdir, const char *name,
  		struct object_id wt_oid;
  		int fd = open(buf.buf, O_RDONLY);
  
 -		if (!index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {
 +		if (fd >= 0 &&
 +		    !index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {
  			if (is_null_oid(oid)) {
  				oidcpy(oid, &wt_oid);
  				use = 1;
 @@ -162,7 +165,7 @@ static void add_left_or_right(struct hashmap *map, const char *path,
  		e->left[0] = e->right[0] = '\0';
  		hashmap_add(map, e);
  	}
 -	strcpy(is_right ? e->right : e->left, content);
 +	strlcpy(is_right ? e->right : e->left, content, PATH_MAX);
  }
  
  struct path_entry {
 @@ -170,7 +173,7 @@ struct path_entry {
  	char path[FLEX_ARRAY];
  };
  
 -int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key)
 +static int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key)
  {
  	return strcmp(a->path, key ? key : b->path);
  }
 @@ -423,17 +426,16 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
  				struct cache_entry *ce2 =
  					make_cache_entry(rmode, roid.hash,
  							 dst_path, 0, 0);
 -				ce_mode_from_stat(ce2, rmode);
  
  				add_index_entry(&wtindex, ce2,
  						ADD_CACHE_JUST_APPEND);
  
 -				add_path(&wtdir, wtdir_len, dst_path);
  				add_path(&rdir, rdir_len, dst_path);
  				if (ensure_leading_directories(rdir.buf))
  					return error("could not create "
  						     "directory for '%s'",
  						     dst_path);
 +				add_path(&wtdir, wtdir_len, dst_path);
  				if (symlinks) {
  					if (symlink(wtdir.buf, rdir.buf)) {
  						ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf);
 diff --git a/exec_cmd.c b/exec_cmd.c
 index 19ac2146d0..587bd7eb48 100644
 --- a/exec_cmd.c
 +++ b/exec_cmd.c
 @@ -65,6 +65,7 @@ void git_set_argv_exec_path(const char *exec_path)
  const char *git_exec_path(void)
  {
  	const char *env;
 +	static char *system_exec_path;
  
  	if (argv_exec_path)
  		return argv_exec_path;
 @@ -74,7 +75,9 @@ const char *git_exec_path(void)
  		return env;
  	}
  
 -	return system_path(GIT_EXEC_PATH);
 +	if (!system_exec_path)
 +		system_exec_path = system_path(GIT_EXEC_PATH);
 +	return system_exec_path;
  }
  
  static void add_path(struct strbuf *out, const char *path)
 diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
 index e94910c563..273ab55723 100755
 --- a/t/t7800-difftool.sh
 +++ b/t/t7800-difftool.sh
 @@ -23,6 +23,20 @@ prompt_given ()
  	test "$prompt" = "Launch 'test-tool' [Y/n]? branch"
  }
  
 +for use_builtin_difftool in false true
 +do
 +
 +test_expect_success 'verify we are running the correct difftool' '
 +	if test true = '$use_builtin_difftool'
 +	then
 +		test_must_fail ok=129 git difftool -h >help &&
 +		grep "g, --gui" help
 +	else
 +		git difftool -h >help &&
 +		grep "g|--gui" help
 +	fi
 +'
 +
  # NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.
  
  # Create a file on master and change it on branch
 @@ -606,4 +620,17 @@ test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' '
  	)
  '
  
 +test true != $use_builtin_difftool || break
 +
 +test_expect_success 'tear down for re-run' '
 +	rm -rf * .[a-z]* &&
 +	git init
 +'
 +
 +# run as builtin difftool now
 +GIT_CONFIG_PARAMETERS="'difftool.usebuiltin=true'"
 +export GIT_CONFIG_PARAMETERS
 +
 +done
 +
  test_done

-- 
2.11.0.rc3.windows.1


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

* [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path()
  2017-01-02 16:16     ` [PATCH v4 0/4] Show Git Mailing List: a builtin difftool Johannes Schindelin
@ 2017-01-02 16:22       ` Johannes Schindelin
  2017-01-03 20:11         ` Stefan Beller
  2017-01-02 16:22       ` [PATCH v4 2/4] difftool: add a skeleton for the upcoming builtin Johannes Schindelin
                         ` (3 subsequent siblings)
  4 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2017-01-02 16:22 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

Technically, it is correct that git_exec_path() returns a possibly
malloc()ed string. Practically, it is *sometimes* not malloc()ed. So
let's just use a static variable to make it a singleton. That'll shut
Coverity up, hopefully.

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

diff --git a/exec_cmd.c b/exec_cmd.c
index 19ac2146d0..587bd7eb48 100644
--- a/exec_cmd.c
+++ b/exec_cmd.c
@@ -65,6 +65,7 @@ void git_set_argv_exec_path(const char *exec_path)
 const char *git_exec_path(void)
 {
 	const char *env;
+	static char *system_exec_path;
 
 	if (argv_exec_path)
 		return argv_exec_path;
@@ -74,7 +75,9 @@ const char *git_exec_path(void)
 		return env;
 	}
 
-	return system_path(GIT_EXEC_PATH);
+	if (!system_exec_path)
+		system_exec_path = system_path(GIT_EXEC_PATH);
+	return system_exec_path;
 }
 
 static void add_path(struct strbuf *out, const char *path)
-- 
2.11.0.rc3.windows.1



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

* [PATCH v4 2/4] difftool: add a skeleton for the upcoming builtin
  2017-01-02 16:16     ` [PATCH v4 0/4] Show Git Mailing List: a builtin difftool Johannes Schindelin
  2017-01-02 16:22       ` [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path() Johannes Schindelin
@ 2017-01-02 16:22       ` Johannes Schindelin
  2017-01-02 16:22       ` [PATCH v4 3/4] difftool: implement the functionality in the builtin Johannes Schindelin
                         ` (2 subsequent siblings)
  4 siblings, 0 replies; 86+ messages in thread
From: Johannes Schindelin @ 2017-01-02 16:22 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

This adds a builtin difftool that still falls back to the legacy Perl
version, which has been renamed to `legacy-difftool`.

The idea is that the new, experimental, builtin difftool immediately hands
off to the legacy difftool for now, unless the config variable
difftool.useBuiltin is set to true.

This feature flag will be used in the upcoming Git for Windows v2.11.0
release, to allow early testers to opt-in to use the builtin difftool and
flesh out any bugs.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitignore                                    |  1 +
 Makefile                                      |  3 +-
 builtin.h                                     |  1 +
 builtin/difftool.c                            | 63 +++++++++++++++++++++++++++
 git-difftool.perl => git-legacy-difftool.perl |  0
 git.c                                         |  6 +++
 t/t7800-difftool.sh                           |  2 +
 7 files changed, 75 insertions(+), 1 deletion(-)
 create mode 100644 builtin/difftool.c
 rename git-difftool.perl => git-legacy-difftool.perl (100%)

diff --git a/.gitignore b/.gitignore
index 6722f78f9a..5555ae025b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -76,6 +76,7 @@
 /git-init-db
 /git-interpret-trailers
 /git-instaweb
+/git-legacy-difftool
 /git-log
 /git-ls-files
 /git-ls-remote
diff --git a/Makefile b/Makefile
index d861bd9985..8cf5bef034 100644
--- a/Makefile
+++ b/Makefile
@@ -522,7 +522,7 @@ SCRIPT_LIB += git-sh-setup
 SCRIPT_LIB += git-sh-i18n
 
 SCRIPT_PERL += git-add--interactive.perl
-SCRIPT_PERL += git-difftool.perl
+SCRIPT_PERL += git-legacy-difftool.perl
 SCRIPT_PERL += git-archimport.perl
 SCRIPT_PERL += git-cvsexportcommit.perl
 SCRIPT_PERL += git-cvsimport.perl
@@ -883,6 +883,7 @@ BUILTIN_OBJS += builtin/diff-files.o
 BUILTIN_OBJS += builtin/diff-index.o
 BUILTIN_OBJS += builtin/diff-tree.o
 BUILTIN_OBJS += builtin/diff.o
+BUILTIN_OBJS += builtin/difftool.o
 BUILTIN_OBJS += builtin/fast-export.o
 BUILTIN_OBJS += builtin/fetch-pack.o
 BUILTIN_OBJS += builtin/fetch.o
diff --git a/builtin.h b/builtin.h
index b9122bc5f4..67f80519da 100644
--- a/builtin.h
+++ b/builtin.h
@@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_index(int argc, const char **argv, const char *prefix);
 extern int cmd_diff(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_difftool(int argc, const char **argv, const char *prefix);
 extern int cmd_fast_export(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);
diff --git a/builtin/difftool.c b/builtin/difftool.c
new file mode 100644
index 0000000000..53870bbaf7
--- /dev/null
+++ b/builtin/difftool.c
@@ -0,0 +1,63 @@
+/*
+ * "git difftool" builtin command
+ *
+ * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
+ * git-difftool--helper script.
+ *
+ * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.
+ * The GIT_DIFF* variables are exported for use by git-difftool--helper.
+ *
+ * Any arguments that are unknown to this script are forwarded to 'git diff'.
+ *
+ * Copyright (C) 2016 Johannes Schindelin
+ */
+#include "builtin.h"
+#include "run-command.h"
+#include "exec_cmd.h"
+
+/*
+ * NEEDSWORK: this function can go once the legacy-difftool Perl script is
+ * retired.
+ *
+ * We intentionally avoid reading the config directly here, to avoid messing up
+ * the GIT_* environment variables when we need to fall back to exec()ing the
+ * Perl script.
+ */
+static int use_builtin_difftool(void) {
+	struct child_process cp = CHILD_PROCESS_INIT;
+	struct strbuf out = STRBUF_INIT;
+	int ret;
+
+	argv_array_pushl(&cp.args,
+			 "config", "--bool", "difftool.usebuiltin", NULL);
+	cp.git_cmd = 1;
+	if (capture_command(&cp, &out, 6))
+		return 0;
+	strbuf_trim(&out);
+	ret = !strcmp("true", out.buf);
+	strbuf_release(&out);
+	return ret;
+}
+
+int cmd_difftool(int argc, const char **argv, const char *prefix)
+{
+	/*
+	 * NEEDSWORK: Once the builtin difftool has been tested enough
+	 * and git-legacy-difftool.perl is retired to contrib/, this preamble
+	 * can be removed.
+	 */
+	if (!use_builtin_difftool()) {
+		const char *path = mkpath("%s/git-legacy-difftool",
+					  git_exec_path());
+
+		if (sane_execvp(path, (char **)argv) < 0)
+			die_errno("could not exec %s", path);
+
+		return 0;
+	}
+	prefix = setup_git_directory();
+	trace_repo_setup(prefix);
+	setup_work_tree();
+
+	die("TODO");
+}
diff --git a/git-difftool.perl b/git-legacy-difftool.perl
similarity index 100%
rename from git-difftool.perl
rename to git-legacy-difftool.perl
diff --git a/git.c b/git.c
index dce529fcbf..044958a780 100644
--- a/git.c
+++ b/git.c
@@ -424,6 +424,12 @@ static struct cmd_struct commands[] = {
 	{ "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
 	{ "diff-index", cmd_diff_index, RUN_SETUP },
 	{ "diff-tree", cmd_diff_tree, RUN_SETUP },
+	/*
+	 * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in
+	 * builtin/difftool.c has been removed, this entry should be changed to
+	 * RUN_SETUP | NEED_WORK_TREE
+	 */
+	{ "difftool", cmd_difftool },
 	{ "fast-export", cmd_fast_export, RUN_SETUP },
 	{ "fetch", cmd_fetch, RUN_SETUP },
 	{ "fetch-pack", cmd_fetch_pack, RUN_SETUP },
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index 99d4123461..e94910c563 100755
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
@@ -23,6 +23,8 @@ prompt_given ()
 	test "$prompt" = "Launch 'test-tool' [Y/n]? branch"
 }
 
+# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.
+
 # Create a file on master and change it on branch
 test_expect_success PERL 'setup' '
 	echo master >file &&
-- 
2.11.0.rc3.windows.1



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

* [PATCH v4 3/4] difftool: implement the functionality in the builtin
  2017-01-02 16:16     ` [PATCH v4 0/4] Show Git Mailing List: a builtin difftool Johannes Schindelin
  2017-01-02 16:22       ` [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path() Johannes Schindelin
  2017-01-02 16:22       ` [PATCH v4 2/4] difftool: add a skeleton for the upcoming builtin Johannes Schindelin
@ 2017-01-02 16:22       ` Johannes Schindelin
  2017-01-02 16:24       ` [PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now Johannes Schindelin
  2017-01-17 15:54       ` [PATCH v5 0/3] Turn the difftool into a builtin Johannes Schindelin
  4 siblings, 0 replies; 86+ messages in thread
From: Johannes Schindelin @ 2017-01-02 16:22 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

This patch gives life to the skeleton added in the previous patch.

The motivation for converting the difftool is that Perl scripts are not at
all native on Windows, and that `git difftool` therefore is pretty slow on
that platform, when there is no good reason for it to be slow.

In addition, Perl does not really have access to Git's internals. That
means that any script will always have to jump through unnecessary
hoops.

The current version of the builtin difftool does not, however, make full
use of the internals but instead chooses to spawn a couple of Git
processes, still, to make for an easier conversion. There remains a lot
of room for improvement, left for a later date.

Note: to play it safe, the original difftool is still called unless the
config setting difftool.useBuiltin is set to true.

The reason: this new, experimental, builtin difftool will be shipped as
part of Git for Windows v2.11.0, to allow for easier large-scale
testing, but of course as an opt-in feature.

Sadly, the speedup is more noticable on Linux than on Windows: a quick
test shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s)
(real/user/sys) in a Linux VM, down from  (6.529s/3.112s/0.644s), while
on Windows, it is (36.064s/2.730s/7.194s), down from
(47.637s/2.407s/6.863s). The culprit is most likely the overhead
incurred from *still* having to shell out to mergetool-lib.sh and
difftool--helper.sh.

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

diff --git a/builtin/difftool.c b/builtin/difftool.c
index 53870bbaf7..2115e548a5 100644
--- a/builtin/difftool.c
+++ b/builtin/difftool.c
@@ -11,9 +11,610 @@
  *
  * Copyright (C) 2016 Johannes Schindelin
  */
+#include "cache.h"
 #include "builtin.h"
 #include "run-command.h"
 #include "exec_cmd.h"
+#include "parse-options.h"
+#include "argv-array.h"
+#include "strbuf.h"
+#include "lockfile.h"
+#include "dir.h"
+
+static char *diff_gui_tool;
+static int trust_exit_code;
+
+static const char *const builtin_difftool_usage[] = {
+	N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"),
+	NULL
+};
+
+static int difftool_config(const char *var, const char *value, void *cb)
+{
+	if (!strcmp(var, "diff.guitool")) {
+		diff_gui_tool = xstrdup(value);
+		return 0;
+	}
+
+	if (!strcmp(var, "difftool.trustexitcode")) {
+		trust_exit_code = git_config_bool(var, value);
+		return 0;
+	}
+
+	return git_default_config(var, value, cb);
+}
+
+static int print_tool_help(void)
+{
+	const char *argv[] = { "mergetool", "--tool-help=diff", NULL };
+	return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
+static int parse_index_info(char *p, int *mode1, int *mode2,
+			    struct object_id *oid1, struct object_id *oid2,
+			    char *status)
+{
+	if (*p != ':')
+		return error("expected ':', got '%c'", *p);
+	*mode1 = (int)strtol(p + 1, &p, 8);
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	*mode2 = (int)strtol(p + 1, &p, 8);
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	if (get_oid_hex(++p, oid1))
+		return error("expected object ID, got '%s'", p + 1);
+	p += GIT_SHA1_HEXSZ;
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	if (get_oid_hex(++p, oid2))
+		return error("expected object ID, got '%s'", p + 1);
+	p += GIT_SHA1_HEXSZ;
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	*status = *++p;
+	if (!*status)
+		return error("missing status");
+	if (p[1] && !isdigit(p[1]))
+		return error("unexpected trailer: '%s'", p + 1);
+	return 0;
+}
+
+/*
+ * Remove any trailing slash from $workdir
+ * before starting to avoid double slashes in symlink targets.
+ */
+static void add_path(struct strbuf *buf, size_t base_len, const char *path)
+{
+	strbuf_setlen(buf, base_len);
+	if (buf->len && buf->buf[buf->len - 1] != '/')
+		strbuf_addch(buf, '/');
+	strbuf_addstr(buf, path);
+}
+
+/*
+ * Determine whether we can simply reuse the file in the worktree.
+ */
+static int use_wt_file(const char *workdir, const char *name,
+		       struct object_id *oid)
+{
+	struct strbuf buf = STRBUF_INIT;
+	struct stat st;
+	int use = 0;
+
+	strbuf_addstr(&buf, workdir);
+	add_path(&buf, buf.len, name);
+
+	if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) {
+		struct object_id wt_oid;
+		int fd = open(buf.buf, O_RDONLY);
+
+		if (fd >= 0 &&
+		    !index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {
+			if (is_null_oid(oid)) {
+				oidcpy(oid, &wt_oid);
+				use = 1;
+			} else if (!oidcmp(oid, &wt_oid))
+				use = 1;
+		}
+	}
+
+	strbuf_release(&buf);
+
+	return use;
+}
+
+struct working_tree_entry {
+	struct hashmap_entry entry;
+	char path[FLEX_ARRAY];
+};
+
+static int working_tree_entry_cmp(struct working_tree_entry *a,
+				  struct working_tree_entry *b, void *keydata)
+{
+	return strcmp(a->path, b->path);
+}
+
+/*
+ * The `left` and `right` entries hold paths for the symlinks hashmap,
+ * and a SHA-1 surrounded by brief text for submodules.
+ */
+struct pair_entry {
+	struct hashmap_entry entry;
+	char left[PATH_MAX], right[PATH_MAX];
+	const char path[FLEX_ARRAY];
+};
+
+static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata)
+{
+	return strcmp(a->path, b->path);
+}
+
+static void add_left_or_right(struct hashmap *map, const char *path,
+			      const char *content, int is_right)
+{
+	struct pair_entry *e, *existing;
+
+	FLEX_ALLOC_STR(e, path, path);
+	hashmap_entry_init(e, strhash(path));
+	existing = hashmap_get(map, e, NULL);
+	if (existing) {
+		free(e);
+		e = existing;
+	} else {
+		e->left[0] = e->right[0] = '\0';
+		hashmap_add(map, e);
+	}
+	strlcpy(is_right ? e->right : e->left, content, PATH_MAX);
+}
+
+struct path_entry {
+	struct hashmap_entry entry;
+	char path[FLEX_ARRAY];
+};
+
+static int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key)
+{
+	return strcmp(a->path, key ? key : b->path);
+}
+
+static void changed_files(struct hashmap *result, const char *index_path,
+			  const char *workdir)
+{
+	struct child_process update_index = CHILD_PROCESS_INIT;
+	struct child_process diff_files = CHILD_PROCESS_INIT;
+	struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT;
+	const char *git_dir = absolute_path(get_git_dir()), *env[] = {
+		NULL, NULL
+	};
+	FILE *fp;
+
+	strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path);
+	env[0] = index_env.buf;
+
+	argv_array_pushl(&update_index.args,
+			 "--git-dir", git_dir, "--work-tree", workdir,
+			 "update-index", "--really-refresh", "-q",
+			 "--unmerged", NULL);
+	update_index.no_stdin = 1;
+	update_index.no_stdout = 1;
+	update_index.no_stderr = 1;
+	update_index.git_cmd = 1;
+	update_index.use_shell = 0;
+	update_index.clean_on_exit = 1;
+	update_index.dir = workdir;
+	update_index.env = env;
+	/* Ignore any errors of update-index */
+	run_command(&update_index);
+
+	argv_array_pushl(&diff_files.args,
+			 "--git-dir", git_dir, "--work-tree", workdir,
+			 "diff-files", "--name-only", "-z", NULL);
+	diff_files.no_stdin = 1;
+	diff_files.git_cmd = 1;
+	diff_files.use_shell = 0;
+	diff_files.clean_on_exit = 1;
+	diff_files.out = -1;
+	diff_files.dir = workdir;
+	diff_files.env = env;
+	if (start_command(&diff_files))
+		die("could not obtain raw diff");
+	fp = xfdopen(diff_files.out, "r");
+	while (!strbuf_getline_nul(&buf, fp)) {
+		struct path_entry *entry;
+		FLEX_ALLOC_STR(entry, path, buf.buf);
+		hashmap_entry_init(entry, strhash(buf.buf));
+		hashmap_add(result, entry);
+	}
+	if (finish_command(&diff_files))
+		die("diff-files did not exit properly");
+	strbuf_release(&index_env);
+	strbuf_release(&buf);
+}
+
+static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)
+{
+	struct strbuf buf = STRBUF_INIT;
+	strbuf_addstr(&buf, tmpdir);
+	remove_dir_recursively(&buf, 0);
+	if (exit_code)
+		warning(_("failed: %d"), exit_code);
+	exit(exit_code);
+}
+
+static int ensure_leading_directories(char *path)
+{
+	switch (safe_create_leading_directories(path)) {
+		case SCLD_OK:
+		case SCLD_EXISTS:
+			return 0;
+		default:
+			return error(_("could not create leading directories "
+				       "of '%s'"), path);
+	}
+}
+
+static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
+			int argc, const char **argv)
+{
+	char tmpdir[PATH_MAX];
+	struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;
+	struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;
+	struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;
+	struct strbuf wtdir = STRBUF_INIT;
+	size_t ldir_len, rdir_len, wtdir_len;
+	struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1);
+	const char *workdir, *tmp;
+	int ret = 0, i;
+	FILE *fp;
+	struct hashmap working_tree_dups, submodules, symlinks2;
+	struct hashmap_iter iter;
+	struct pair_entry *entry;
+	enum object_type type;
+	unsigned long size;
+	struct index_state wtindex;
+	struct checkout lstate, rstate;
+	int rc, flags = RUN_GIT_CMD, err = 0;
+	struct child_process child = CHILD_PROCESS_INIT;
+	const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL };
+	struct hashmap wt_modified, tmp_modified;
+	int indices_loaded = 0;
+
+	workdir = get_git_work_tree();
+
+	/* Setup temp directories */
+	tmp = getenv("TMPDIR");
+	xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp");
+	if (!mkdtemp(tmpdir))
+		return error("could not create '%s'", tmpdir);
+	strbuf_addf(&ldir, "%s/left/", tmpdir);
+	strbuf_addf(&rdir, "%s/right/", tmpdir);
+	strbuf_addstr(&wtdir, workdir);
+	if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))
+		strbuf_addch(&wtdir, '/');
+	mkdir(ldir.buf, 0700);
+	mkdir(rdir.buf, 0700);
+
+	memset(&wtindex, 0, sizeof(wtindex));
+
+	memset(&lstate, 0, sizeof(lstate));
+	lstate.base_dir = ldir.buf;
+	lstate.base_dir_len = ldir.len;
+	lstate.force = 1;
+	memset(&rstate, 0, sizeof(rstate));
+	rstate.base_dir = rdir.buf;
+	rstate.base_dir_len = rdir.len;
+	rstate.force = 1;
+
+	ldir_len = ldir.len;
+	rdir_len = rdir.len;
+	wtdir_len = wtdir.len;
+
+	hashmap_init(&working_tree_dups,
+		     (hashmap_cmp_fn)working_tree_entry_cmp, 0);
+	hashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0);
+	hashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0);
+
+	child.no_stdin = 1;
+	child.git_cmd = 1;
+	child.use_shell = 0;
+	child.clean_on_exit = 1;
+	child.dir = prefix;
+	child.out = -1;
+	argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z",
+			 NULL);
+	for (i = 0; i < argc; i++)
+		argv_array_push(&child.args, argv[i]);
+	if (start_command(&child))
+		die("could not obtain raw diff");
+	fp = xfdopen(child.out, "r");
+
+	/* Build index info for left and right sides of the diff */
+	i = 0;
+	while (!strbuf_getline_nul(&info, fp)) {
+		int lmode, rmode;
+		struct object_id loid, roid;
+		char status;
+		const char *src_path, *dst_path;
+		size_t src_path_len, dst_path_len;
+
+		if (starts_with(info.buf, "::"))
+			die(N_("combined diff formats('-c' and '--cc') are "
+			       "not supported in\n"
+			       "directory diff mode('-d' and '--dir-diff')."));
+
+		if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,
+				     &status))
+			break;
+		if (strbuf_getline_nul(&lpath, fp))
+			break;
+		src_path = lpath.buf;
+		src_path_len = lpath.len;
+
+		i++;
+		if (status != 'C' && status != 'R') {
+			dst_path = src_path;
+			dst_path_len = src_path_len;
+		} else {
+			if (strbuf_getline_nul(&rpath, fp))
+				break;
+			dst_path = rpath.buf;
+			dst_path_len = rpath.len;
+		}
+
+		if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) {
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "Subproject commit %s",
+				    oid_to_hex(&loid));
+			add_left_or_right(&submodules, src_path, buf.buf, 0);
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "Subproject commit %s",
+				    oid_to_hex(&roid));
+			if (!oidcmp(&loid, &roid))
+				strbuf_addstr(&buf, "-dirty");
+			add_left_or_right(&submodules, dst_path, buf.buf, 1);
+			continue;
+		}
+
+		if (S_ISLNK(lmode)) {
+			char *content = read_sha1_file(loid.hash, &type, &size);
+			add_left_or_right(&symlinks2, src_path, content, 0);
+			free(content);
+		}
+
+		if (S_ISLNK(rmode)) {
+			char *content = read_sha1_file(roid.hash, &type, &size);
+			add_left_or_right(&symlinks2, dst_path, content, 1);
+			free(content);
+		}
+
+		if (lmode && status != 'C') {
+			ce->ce_mode = lmode;
+			oidcpy(&ce->oid, &loid);
+			strcpy(ce->name, src_path);
+			ce->ce_namelen = src_path_len;
+			if (checkout_entry(ce, &lstate, NULL))
+				return error("could not write '%s'", src_path);
+		}
+
+		if (rmode) {
+			struct working_tree_entry *entry;
+
+			/* Avoid duplicate working_tree entries */
+			FLEX_ALLOC_STR(entry, path, dst_path);
+			hashmap_entry_init(entry, strhash(dst_path));
+			if (hashmap_get(&working_tree_dups, entry, NULL)) {
+				free(entry);
+				continue;
+			}
+			hashmap_add(&working_tree_dups, entry);
+
+			if (!use_wt_file(workdir, dst_path, &roid)) {
+				ce->ce_mode = rmode;
+				oidcpy(&ce->oid, &roid);
+				strcpy(ce->name, dst_path);
+				ce->ce_namelen = dst_path_len;
+				if (checkout_entry(ce, &rstate, NULL))
+					return error("could not write '%s'",
+						     dst_path);
+			} else if (!is_null_oid(&roid)) {
+				/*
+				 * Changes in the working tree need special
+				 * treatment since they are not part of the
+				 * index.
+				 */
+				struct cache_entry *ce2 =
+					make_cache_entry(rmode, roid.hash,
+							 dst_path, 0, 0);
+
+				add_index_entry(&wtindex, ce2,
+						ADD_CACHE_JUST_APPEND);
+
+				add_path(&rdir, rdir_len, dst_path);
+				if (ensure_leading_directories(rdir.buf))
+					return error("could not create "
+						     "directory for '%s'",
+						     dst_path);
+				add_path(&wtdir, wtdir_len, dst_path);
+				if (symlinks) {
+					if (symlink(wtdir.buf, rdir.buf)) {
+						ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf);
+						goto finish;
+					}
+				} else {
+					struct stat st;
+					if (stat(wtdir.buf, &st))
+						st.st_mode = 0644;
+					if (copy_file(rdir.buf, wtdir.buf,
+						      st.st_mode)) {
+						ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf);
+						goto finish;
+					}
+				}
+			}
+		}
+	}
+
+	if (finish_command(&child)) {
+		ret = error("error occurred running diff --raw");
+		goto finish;
+	}
+
+	if (!i)
+		return 0;
+
+	/*
+	 * Changes to submodules require special treatment.This loop writes a
+	 * temporary file to both the left and right directories to show the
+	 * change in the recorded SHA1 for the submodule.
+	 */
+	hashmap_iter_init(&submodules, &iter);
+	while ((entry = hashmap_iter_next(&iter))) {
+		if (*entry->left) {
+			add_path(&ldir, ldir_len, entry->path);
+			ensure_leading_directories(ldir.buf);
+			write_file(ldir.buf, "%s", entry->left);
+		}
+		if (*entry->right) {
+			add_path(&rdir, rdir_len, entry->path);
+			ensure_leading_directories(rdir.buf);
+			write_file(rdir.buf, "%s", entry->right);
+		}
+	}
+
+	/*
+	 * Symbolic links require special treatment.The standard "git diff"
+	 * shows only the link itself, not the contents of the link target.
+	 * This loop replicates that behavior.
+	 */
+	hashmap_iter_init(&symlinks2, &iter);
+	while ((entry = hashmap_iter_next(&iter))) {
+		if (*entry->left) {
+			add_path(&ldir, ldir_len, entry->path);
+			ensure_leading_directories(ldir.buf);
+			write_file(ldir.buf, "%s", entry->left);
+		}
+		if (*entry->right) {
+			add_path(&rdir, rdir_len, entry->path);
+			ensure_leading_directories(rdir.buf);
+			write_file(rdir.buf, "%s", entry->right);
+		}
+	}
+
+	strbuf_release(&buf);
+
+	strbuf_setlen(&ldir, ldir_len);
+	helper_argv[1] = ldir.buf;
+	strbuf_setlen(&rdir, rdir_len);
+	helper_argv[2] = rdir.buf;
+
+	if (extcmd) {
+		helper_argv[0] = extcmd;
+		flags = 0;
+	} else
+		setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1);
+	rc = run_command_v_opt(helper_argv, flags);
+
+	/*
+	 * If the diff includes working copy files and those
+	 * files were modified during the diff, then the changes
+	 * should be copied back to the working tree.
+	 * Do not copy back files when symlinks are used and the
+	 * external tool did not replace the original link with a file.
+	 *
+	 * These hashes are loaded lazily since they aren't needed
+	 * in the common case of --symlinks and the difftool updating
+	 * files through the symlink.
+	 */
+	hashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp,
+		     wtindex.cache_nr);
+	hashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp,
+		     wtindex.cache_nr);
+
+	for (i = 0; i < wtindex.cache_nr; i++) {
+		struct hashmap_entry dummy;
+		const char *name = wtindex.cache[i]->name;
+		struct stat st;
+
+		add_path(&rdir, rdir_len, name);
+		if (lstat(rdir.buf, &st))
+			continue;
+
+		if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode))
+			continue;
+
+		if (!indices_loaded) {
+			static struct lock_file lock;
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "%s/wtindex", tmpdir);
+			if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
+			    write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {
+				ret = error("could not write %s", buf.buf);
+				rollback_lock_file(&lock);
+				goto finish;
+			}
+			changed_files(&wt_modified, buf.buf, workdir);
+			strbuf_setlen(&rdir, rdir_len);
+			changed_files(&tmp_modified, buf.buf, rdir.buf);
+			add_path(&rdir, rdir_len, name);
+			indices_loaded = 1;
+		}
+
+		hashmap_entry_init(&dummy, strhash(name));
+		if (hashmap_get(&tmp_modified, &dummy, name)) {
+			add_path(&wtdir, wtdir_len, name);
+			if (hashmap_get(&wt_modified, &dummy, name)) {
+				warning(_("both files modified: '%s' and '%s'."),
+					wtdir.buf, rdir.buf);
+				warning(_("working tree file has been left."));
+				warning("");
+				err = 1;
+			} else if (unlink(wtdir.buf) ||
+				   copy_file(wtdir.buf, rdir.buf, st.st_mode))
+				warning_errno(_("could not copy '%s' to '%s'"),
+					      rdir.buf, wtdir.buf);
+		}
+	}
+
+	if (err) {
+		warning(_("temporary files exist in '%s'."), tmpdir);
+		warning(_("you may want to cleanup or recover these."));
+		exit(1);
+	} else
+		exit_cleanup(tmpdir, rc);
+
+finish:
+	free(ce);
+	strbuf_release(&ldir);
+	strbuf_release(&rdir);
+	strbuf_release(&wtdir);
+	strbuf_release(&buf);
+
+	return ret;
+}
+
+static int run_file_diff(int prompt, const char *prefix,
+			 int argc, const char **argv)
+{
+	struct argv_array args = ARGV_ARRAY_INIT;
+	const char *env[] = {
+		"GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL,
+		NULL
+	};
+	int ret = 0, i;
+
+	if (prompt > 0)
+		env[2] = "GIT_DIFFTOOL_PROMPT=true";
+	else if (!prompt)
+		env[2] = "GIT_DIFFTOOL_NO_PROMPT=true";
+
+
+	argv_array_push(&args, "diff");
+	for (i = 0; i < argc; i++)
+		argv_array_push(&args, argv[i]);
+	ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env);
+	exit(ret);
+}
 
 /*
  * NEEDSWORK: this function can go once the legacy-difftool Perl script is
@@ -41,6 +642,35 @@ static int use_builtin_difftool(void) {
 
 int cmd_difftool(int argc, const char **argv, const char *prefix)
 {
+	int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,
+	    tool_help = 0;
+	static char *difftool_cmd = NULL, *extcmd = NULL;
+	struct option builtin_difftool_options[] = {
+		OPT_BOOL('g', "gui", &use_gui_tool,
+			 N_("use `diff.guitool` instead of `diff.tool`")),
+		OPT_BOOL('d', "dir-diff", &dir_diff,
+			 N_("perform a full-directory diff")),
+		{ OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL,
+			N_("do not prompt before launching a diff tool"),
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
+		{ OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL,
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
+			NULL, 1 },
+		OPT_BOOL(0, "symlinks", &symlinks,
+			 N_("use symlinks in dir-diff mode")),
+		OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"),
+			   N_("use the specified diff tool")),
+		OPT_BOOL(0, "tool-help", &tool_help,
+			 N_("print a list of diff tools that may be used with "
+			    "`--tool`")),
+		OPT_BOOL(0, "trust-exit-code", &trust_exit_code,
+			 N_("make 'git-difftool' exit when an invoked diff "
+			    "tool returns a non - zero exit code")),
+		OPT_STRING('x', "extcmd", &extcmd, N_("<command>"),
+			   N_("specify a custom command for viewing diffs")),
+		OPT_END()
+	};
+
 	/*
 	 * NEEDSWORK: Once the builtin difftool has been tested enough
 	 * and git-legacy-difftool.perl is retired to contrib/, this preamble
@@ -58,6 +688,46 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)
 	prefix = setup_git_directory();
 	trace_repo_setup(prefix);
 	setup_work_tree();
+	/* NEEDSWORK: once we no longer spawn anything, remove this */
+	setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);
+	setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);
+
+	git_config(difftool_config, NULL);
+	symlinks = has_symlinks;
+
+	argc = parse_options(argc, argv, prefix, builtin_difftool_options,
+			     builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |
+			     PARSE_OPT_KEEP_DASHDASH);
 
-	die("TODO");
+	if (tool_help)
+		return print_tool_help();
+
+	if (use_gui_tool && diff_gui_tool && *diff_gui_tool)
+		setenv("GIT_DIFF_TOOL", diff_gui_tool, 1);
+	else if (difftool_cmd) {
+		if (*difftool_cmd)
+			setenv("GIT_DIFF_TOOL", difftool_cmd, 1);
+		else
+			die(_("no <tool> given for --tool=<tool>"));
+	}
+
+	if (extcmd) {
+		if (*extcmd)
+			setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1);
+		else
+			die(_("no <cmd> given for --extcmd=<cmd>"));
+	}
+
+	setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE",
+	       trust_exit_code ? "true" : "false", 1);
+
+	/*
+	 * In directory diff mode, 'git-difftool--helper' is called once
+	 * to compare the a / b directories. In file diff mode, 'git diff'
+	 * will invoke a separate instance of 'git-difftool--helper' for
+	 * each file that changed.
+	 */
+	if (dir_diff)
+		return run_dir_diff(extcmd, symlinks, prefix, argc, argv);
+	return run_file_diff(prompt, prefix, argc, argv);
 }
-- 
2.11.0.rc3.windows.1



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

* [PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now
  2017-01-02 16:16     ` [PATCH v4 0/4] Show Git Mailing List: a builtin difftool Johannes Schindelin
                         ` (2 preceding siblings ...)
  2017-01-02 16:22       ` [PATCH v4 3/4] difftool: implement the functionality in the builtin Johannes Schindelin
@ 2017-01-02 16:24       ` Johannes Schindelin
  2017-01-09  1:38         ` Junio C Hamano
  2017-01-17 15:54       ` [PATCH v5 0/3] Turn the difftool into a builtin Johannes Schindelin
  4 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2017-01-02 16:24 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

This is uglier than a simple

	touch "$GIT_EXEC_PATH/use-builtin-difftool"

of course. But oh well.

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

	This patch implements the good ole' cross-validation technique
	(also known as "GitHub Scientist") that I already used for my
	rebase--helper work.

	I am not sure whether we want to have that patch in `master`,
	ever. But at least for the transition time, it may make sense.

 t/t7800-difftool.sh | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index e94910c563..273ab55723 100755
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
@@ -23,6 +23,20 @@ prompt_given ()
 	test "$prompt" = "Launch 'test-tool' [Y/n]? branch"
 }
 
+for use_builtin_difftool in false true
+do
+
+test_expect_success 'verify we are running the correct difftool' '
+	if test true = '$use_builtin_difftool'
+	then
+		test_must_fail ok=129 git difftool -h >help &&
+		grep "g, --gui" help
+	else
+		git difftool -h >help &&
+		grep "g|--gui" help
+	fi
+'
+
 # NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.
 
 # Create a file on master and change it on branch
@@ -606,4 +620,17 @@ test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' '
 	)
 '
 
+test true != $use_builtin_difftool || break
+
+test_expect_success 'tear down for re-run' '
+	rm -rf * .[a-z]* &&
+	git init
+'
+
+# run as builtin difftool now
+GIT_CONFIG_PARAMETERS="'difftool.usebuiltin=true'"
+export GIT_CONFIG_PARAMETERS
+
+done
+
 test_done
-- 
2.11.0.rc3.windows.1

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

* Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path()
  2017-01-02 16:22       ` [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path() Johannes Schindelin
@ 2017-01-03 20:11         ` Stefan Beller
  2017-01-03 21:33           ` Johannes Schindelin
                             ` (2 more replies)
  0 siblings, 3 replies; 86+ messages in thread
From: Stefan Beller @ 2017-01-03 20:11 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git@vger.kernel.org, Junio C Hamano, David Aguilar,
	Dennis Kaarsemaker, Paul Sbarra

On Mon, Jan 2, 2017 at 8:22 AM, Johannes Schindelin
<johannes.schindelin@gmx.de> wrote:
> Technically, it is correct that git_exec_path() returns a possibly
> malloc()ed string. Practically, it is *sometimes* not malloc()ed. So
> let's just use a static variable to make it a singleton. That'll shut
> Coverity up, hopefully.

I picked up this patch and applied it to the coverity branch
that I maintain at github/stefanbeller/git.

I'd love to see this patch upstream as it reduces my maintenance
burden of the coverity branch by a patch.

Early on when Git was new to coverity, some arguments were made
that patches like these only clutter the main code base which is read
by a lot of people, hence we want these quirks for coverity not upstream.
And I think that still holds.

If this patch is only to appease coverity (as the commit message eludes
to) I think this may be a bad idea for upstream.  If this patch fixes an
actual problem, then the commit message needs to spell that out.

Thanks,
Stefan

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

* Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path()
  2017-01-03 20:11         ` Stefan Beller
@ 2017-01-03 21:33           ` Johannes Schindelin
  2017-01-04 18:09             ` Stefan Beller
  2017-01-04  1:13           ` Jeff King
  2017-01-09  1:25           ` Junio C Hamano
  2 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2017-01-03 21:33 UTC (permalink / raw)
  To: Stefan Beller
  Cc: git@vger.kernel.org, Junio C Hamano, David Aguilar,
	Dennis Kaarsemaker, Paul Sbarra

Hi Stefan,

On Tue, 3 Jan 2017, Stefan Beller wrote:

> On Mon, Jan 2, 2017 at 8:22 AM, Johannes Schindelin
> <johannes.schindelin@gmx.de> wrote:
> > Technically, it is correct that git_exec_path() returns a possibly
> > malloc()ed string. Practically, it is *sometimes* not malloc()ed. So
> > let's just use a static variable to make it a singleton. That'll shut
> > Coverity up, hopefully.
> 
> I picked up this patch and applied it to the coverity branch
> that I maintain at github/stefanbeller/git.
> 
> I'd love to see this patch upstream as it reduces my maintenance
> burden of the coverity branch by a patch.
> 
> Early on when Git was new to coverity, some arguments were made
> that patches like these only clutter the main code base which is read
> by a lot of people, hence we want these quirks for coverity not upstream.
> And I think that still holds.
> 
> If this patch is only to appease coverity (as the commit message eludes
> to) I think this may be a bad idea for upstream.  If this patch fixes an
> actual problem, then the commit message needs to spell that out.

This patch was originally only to appease Coverity, but it actually *does*
plug a very real memory leak: previously, *every* call to git_exec_path()
*possibly* returned a newly-malloc()ed buffer. Now, the first call will
store that pointer in a static variable and reuse it later.

Could you maybe help me with improving the commit message?

Ciao,
Dscho

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

* Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path()
  2017-01-03 20:11         ` Stefan Beller
  2017-01-03 21:33           ` Johannes Schindelin
@ 2017-01-04  1:13           ` Jeff King
  2017-01-09  1:25           ` Junio C Hamano
  2 siblings, 0 replies; 86+ messages in thread
From: Jeff King @ 2017-01-04  1:13 UTC (permalink / raw)
  To: Stefan Beller
  Cc: Johannes Schindelin, git@vger.kernel.org, Junio C Hamano,
	David Aguilar, Dennis Kaarsemaker, Paul Sbarra

On Tue, Jan 03, 2017 at 12:11:25PM -0800, Stefan Beller wrote:

> On Mon, Jan 2, 2017 at 8:22 AM, Johannes Schindelin
> <johannes.schindelin@gmx.de> wrote:
> > Technically, it is correct that git_exec_path() returns a possibly
> > malloc()ed string. Practically, it is *sometimes* not malloc()ed. So
> > let's just use a static variable to make it a singleton. That'll shut
> > Coverity up, hopefully.
> 
> I picked up this patch and applied it to the coverity branch
> that I maintain at github/stefanbeller/git.
> 
> I'd love to see this patch upstream as it reduces my maintenance
> burden of the coverity branch by a patch.

There is another lurking issue in that function, which is that the
return value of getenv() is not guaranteed to last beyond more calls to
getenv() or setenv(). It should probably xstrdup() that result, too.

At that point 2 out of 3 of the return cases would be malloc'd strings,
so we _could_ switch the third and say "caller must free the result".
But I think I prefer something like Dscho's solution (more on that
below).

> Early on when Git was new to coverity, some arguments were made
> that patches like these only clutter the main code base which is read
> by a lot of people, hence we want these quirks for coverity not upstream.
> And I think that still holds.
> 
> If this patch is only to appease coverity (as the commit message eludes
> to) I think this may be a bad idea for upstream.  If this patch fixes an
> actual problem, then the commit message needs to spell that out.

This is a real leak, though in all cases the program typically exits
soon afterwards. But we leak from list_commands(), for example, and it
is not immediately obvious that this is only called right before
exiting.

But I think more important is that caching the result in a static
variable communicates something (both to Coverity and to a reader of the
code). This is a value we expect to live through the life of the
program, and it is OK for it to "leak" when it goes out of scope by the
program exiting.

So even though the behavior does not really change, the annotation has
value.

-Peff

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

* Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path()
  2017-01-03 21:33           ` Johannes Schindelin
@ 2017-01-04 18:09             ` Stefan Beller
  0 siblings, 0 replies; 86+ messages in thread
From: Stefan Beller @ 2017-01-04 18:09 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git@vger.kernel.org, Junio C Hamano, David Aguilar,
	Dennis Kaarsemaker, Paul Sbarra

On Tue, Jan 3, 2017 at 1:33 PM, Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:
>
> This patch was originally only to appease Coverity, but it actually *does*
> plug a very real memory leak: previously, *every* call to git_exec_path()
> *possibly* returned a newly-malloc()ed buffer. Now, the first call will
> store that pointer in a static variable and reuse it later.
>
> Could you maybe help me with improving the commit message?

As someone not familiar with that area of code, this explained it
enough for me to understand, so maybe:

    exec_cmd: do not leak via git_exec_path

    Every call to git_exec_path() possibly returned a newly-malloc()ed
    buffer. Now, the first call will allocate the buffer and subsequent
    calls return a pointer to it, which then prevents leaking memory
    on each call.

The return value of a "const char *" hints to the caller, that the memory
is not owned by the caller, do we need to be explicit there (i.e. a comment
declaring the memory ownership? Probably not.)

Thanks,
Stefan

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

* Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path()
  2017-01-03 20:11         ` Stefan Beller
  2017-01-03 21:33           ` Johannes Schindelin
  2017-01-04  1:13           ` Jeff King
@ 2017-01-09  1:25           ` Junio C Hamano
  2017-01-09  6:00             ` Jeff King
                               ` (2 more replies)
  2 siblings, 3 replies; 86+ messages in thread
From: Junio C Hamano @ 2017-01-09  1:25 UTC (permalink / raw)
  To: Stefan Beller
  Cc: Johannes Schindelin, git@vger.kernel.org, David Aguilar,
	Dennis Kaarsemaker, Paul Sbarra

Stefan Beller <sbeller@google.com> writes:

> On Mon, Jan 2, 2017 at 8:22 AM, Johannes Schindelin
> <johannes.schindelin@gmx.de> wrote:
>> Technically, it is correct that git_exec_path() returns a possibly
>> malloc()ed string. Practically, it is *sometimes* not malloc()ed. So
>> let's just use a static variable to make it a singleton. That'll shut
>> Coverity up, hopefully.
>
> I picked up this patch and applied it to the coverity branch
> that I maintain at github/stefanbeller/git.
>
> I'd love to see this patch upstream as it reduces my maintenance
> burden of the coverity branch by a patch.

So with the above, are you saying "Dscho said 'hopefully', and I
confirm that this change does squelch misdiagnosis by Coverity"?

> If this patch is only to appease coverity (as the commit message eludes
> to) I think this may be a bad idea for upstream.  If this patch fixes an
> actual problem, then the commit message needs to spell that out.

That is true, and I see Peff pointed out another possible issue
around getenv(), but I think from the "one step at a time" point of
view, it is an improvement to call system_path() just once and cache
it in "static char *".  

How about explaining it like this then?

(only the log message has been corrected; diff is from the original).

commit c9bb5d101ca657fa466afa8c4368c43ea7b7aca8
Author: Johannes Schindelin <johannes.schindelin@gmx.de>
Date:   Mon Jan 2 17:22:33 2017 +0100

    git_exec_path: avoid Coverity warning about unfree()d result
    
    Technically, it is correct that git_exec_path() returns a possibly
    malloc()ed string returned from system_path(), and it is sometimes
    not allocated.  Cache the result in a static variable and make sure
    that we call system_path() only once, which plugs a potential leak.
    
    Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
    Signed-off-by: Junio C Hamano <gitster@pobox.com>

diff --git a/exec_cmd.c b/exec_cmd.c
index 9d5703a157..eae56fefba 100644
--- a/exec_cmd.c
+++ b/exec_cmd.c
@@ -69,6 +69,7 @@ void git_set_argv_exec_path(const char *exec_path)
 const char *git_exec_path(void)
 {
 	const char *env;
+	static char *system_exec_path;
 
 	if (argv_exec_path)
 		return argv_exec_path;
@@ -78,7 +79,9 @@ const char *git_exec_path(void)
 		return env;
 	}
 
-	return system_path(GIT_EXEC_PATH);
+	if (!system_exec_path)
+		system_exec_path = system_path(GIT_EXEC_PATH);
+	return system_exec_path;
 }
 
 static void add_path(struct strbuf *out, const char *path)


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

* Re: [PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now
  2017-01-02 16:24       ` [PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now Johannes Schindelin
@ 2017-01-09  1:38         ` Junio C Hamano
  2017-01-09  7:56           ` Johannes Schindelin
  0 siblings, 1 reply; 86+ messages in thread
From: Junio C Hamano @ 2017-01-09  1:38 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

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

> This is uglier than a simple
>
> 	touch "$GIT_EXEC_PATH/use-builtin-difftool"
>
> of course. But oh well.

That is totally irrelevant.  

The more important point is that we do the same set of tests so
instead of running just one, we run BOTH.  If we did a bit more
work, we could even say "even when there is no Perl installed, we
run tests with only the new built-in one".  This does not go that
far yet, but that should not be too hard, I would think.  See below.

> diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
> index e94910c563..273ab55723 100755
> --- a/t/t7800-difftool.sh
> +++ b/t/t7800-difftool.sh
> @@ -23,6 +23,20 @@ prompt_given ()
>  	test "$prompt" = "Launch 'test-tool' [Y/n]? branch"
>  }
>  
> +for use_builtin_difftool in false true
> +do

Instead of the above, I'd suggest to make the loop like so:

	for use_builtin_difftool in $TESTED_VARIANTS
	do

and before the loop begins, set TESTED_VARIANTS to either "false
true" or "true" by checking the PERL prerequisite.

> +test_expect_success 'verify we are running the correct difftool' '
> +	if test true = '$use_builtin_difftool'
> +	then
> +		test_must_fail ok=129 git difftool -h >help &&
> +		grep "g, --gui" help
> +	else
> +		git difftool -h >help &&
> +		grep "g|--gui" help
> +	fi
> +'
> +
>  # NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.

And then we can lose this comment.  As all existing tests have PERL
prereq that can go away with the above suggested change, we'd need
to touch quite a many lines with "s/_success PERL /_success /".

It may be a good time to indent these existing tests to make it
clear that they are inside the new "for use_builtin_difftool" loop.


> +test true != $use_builtin_difftool || break
> +
> +test_expect_success 'tear down for re-run' '
> +	rm -rf * .[a-z]* &&
> +	git init
> +'
> +
> +# run as builtin difftool now
> +GIT_CONFIG_PARAMETERS="'difftool.usebuiltin=true'"
> +export GIT_CONFIG_PARAMETERS

... and indentation would extend to these newly added lines, of
course.

> +done
> +
>  test_done

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

* Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path()
  2017-01-09  1:25           ` Junio C Hamano
@ 2017-01-09  6:00             ` Jeff King
  2017-01-09  7:49             ` Johannes Schindelin
  2017-01-09 19:21             ` Stefan Beller
  2 siblings, 0 replies; 86+ messages in thread
From: Jeff King @ 2017-01-09  6:00 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Stefan Beller, Johannes Schindelin, git@vger.kernel.org,
	David Aguilar, Dennis Kaarsemaker, Paul Sbarra

On Sun, Jan 08, 2017 at 05:25:00PM -0800, Junio C Hamano wrote:

> > If this patch is only to appease coverity (as the commit message eludes
> > to) I think this may be a bad idea for upstream.  If this patch fixes an
> > actual problem, then the commit message needs to spell that out.
> 
> That is true, and I see Peff pointed out another possible issue
> around getenv(), but I think from the "one step at a time" point of
> view, it is an improvement to call system_path() just once and cache
> it in "static char *". 

Yep, I don't think it's a big deal to do it on top, like this:

-- >8 --
Subject: git_exec_path: do not return the result of getenv()

The result of getenv() is not guaranteed by POSIX to last
beyond another call to getenv(), or setenv(), etc.  We
should duplicate the string before returning to the caller
to avoid any surprises.

We already keep a cached pointer to avoid repeatedly leaking
the result of system_path(). We can use the same pointer
here to avoid allocating and leaking for each call.

Signed-off-by: Jeff King <peff@peff.net>
---

To be honest, I do not know how big a problem this is. I looked at the
code paths that call git_exec_path(), and the most likely problem case
is calling a second getenv() is via the strbuf functions, which call
xmalloc(), which checks $GIT_ALLOC_LIMIT. We do cache that value, but
it would be a potential problem if this is the first xmalloc call in
the program.

But we are not really solving that here, as xstrdup() would have the
same problem.  This _is_ safer, in that we've better contained the
length of time that we expect the result to be valid.

I have no idea what platforms, if any, use a single static buffer for
the getenv() return. I don't know that we've ever gotten a bug report
about it (I only knew about it because somebody pointed it out in one
of my patches a few years ago, so I have it in the back of my mind as
a potential problem).

So I don't mind if this is dropped as "too esoteric" until somebody
actually reports a bug about it.

 exec_cmd.c | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/exec_cmd.c b/exec_cmd.c
index 587bd7eb4..fb94aeba9 100644
--- a/exec_cmd.c
+++ b/exec_cmd.c
@@ -64,20 +64,19 @@ void git_set_argv_exec_path(const char *exec_path)
 /* Returns the highest-priority, location to look for git programs. */
 const char *git_exec_path(void)
 {
-	const char *env;
-	static char *system_exec_path;
+	static char *cached_exec_path;
 
 	if (argv_exec_path)
 		return argv_exec_path;
 
-	env = getenv(EXEC_PATH_ENVIRONMENT);
-	if (env && *env) {
-		return env;
+	if (!cached_exec_path) {
+		const char *env = getenv(EXEC_PATH_ENVIRONMENT);
+		if (env && *env)
+			cached_exec_path = xstrdup(env);
+		else
+			cached_exec_path = system_path(GIT_EXEC_PATH);
 	}
-
-	if (!system_exec_path)
-		system_exec_path = system_path(GIT_EXEC_PATH);
-	return system_exec_path;
+	return cached_exec_path;
 }
 
 static void add_path(struct strbuf *out, const char *path)
-- 
2.11.0.531.ge85397315


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

* Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path()
  2017-01-09  1:25           ` Junio C Hamano
  2017-01-09  6:00             ` Jeff King
@ 2017-01-09  7:49             ` Johannes Schindelin
  2017-01-09 19:21             ` Stefan Beller
  2 siblings, 0 replies; 86+ messages in thread
From: Johannes Schindelin @ 2017-01-09  7:49 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Stefan Beller, git@vger.kernel.org, David Aguilar,
	Dennis Kaarsemaker, Paul Sbarra

Hi,

On Sun, 8 Jan 2017, Junio C Hamano wrote:

> How about explaining it like this then?
> 
> (only the log message has been corrected; diff is from the original).
> 
> commit c9bb5d101ca657fa466afa8c4368c43ea7b7aca8
> Author: Johannes Schindelin <johannes.schindelin@gmx.de>
> Date:   Mon Jan 2 17:22:33 2017 +0100
> 
>     git_exec_path: avoid Coverity warning about unfree()d result
>     
>     Technically, it is correct that git_exec_path() returns a possibly
>     malloc()ed string returned from system_path(), and it is sometimes
>     not allocated.  Cache the result in a static variable and make sure
>     that we call system_path() only once, which plugs a potential leak.
>     
>     Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
>     Signed-off-by: Junio C Hamano <gitster@pobox.com>

Sounds good to me.

Ciao,
Dscho

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

* Re: [PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now
  2017-01-09  1:38         ` Junio C Hamano
@ 2017-01-09  7:56           ` Johannes Schindelin
  2017-01-09  9:46             ` Junio C Hamano
  0 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2017-01-09  7:56 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

Hi Junio,

On Sun, 8 Jan 2017, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > This is uglier than a simple
> >
> > 	touch "$GIT_EXEC_PATH/use-builtin-difftool"
> >
> > of course. But oh well.
> 
> That is totally irrelevant.  
> 
> The more important point is that we do the same set of tests so
> instead of running just one, we run BOTH.

And the most important point is that we do all of this only during a
hopefully brief period in time that is mostly spent on reviewing the code
and finding serious bugs and fixing them.

During that period (which I would expect to be spent completely inside
`pu`), the cross-validation does not have to be beautiful, but correct.
And even more importantly: it has to come at a minimal integration cost,
in case David decides to add another test case to t7800.

And after that period, we retire the Perl script and switch to the builtin
difftool and simply drop this patch to cross-validate both difftools'
outputs with one another.

At which point all of this safe-guarding and indenting and all of those
changes that are just meant to cross-validate the output during that
hopefully brief period of time will become moot and all the work spent on
those changes will be worthless.

Oh, at that point also all review that went into *this* patch will have
been spent in vain.

In short: can we please move on to reviewing the *actual* builtin difftool
and spend our effort on making sure that it is correct?

Ciao,
Johannes

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

* Re: [PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now
  2017-01-09  7:56           ` Johannes Schindelin
@ 2017-01-09  9:46             ` Junio C Hamano
  0 siblings, 0 replies; 86+ messages in thread
From: Junio C Hamano @ 2017-01-09  9:46 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

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

> And the most important point is that we do all of this only during a
> hopefully brief period in time that is mostly spent on reviewing the code
> and finding serious bugs and fixing them.

You seem to misunderstand the purpose of the code review.  

We indeed spot bugs while reviewing patches, especially ones from
less experienced folks, but that is the least important part of the
review.  In general, we review for:

 - Design.  Is the feature make sense?  Is it too narrow?  Are there
   better ways?  Does it fit well with the rest of the system?

 - Explanation.  Is the purpose of the change in the bigger picture
   explained well enough to allow future people to answer this
   question: "We now have an additional requirement to the feature.
   If the original author knew about that when this was first
   introduced, would s/he consider that our design for this
   additional thing consistent with the original design?  Should we
   design our enhancement in a different way?"

 - Maintainability.  Does the implementation avoid reinventing data
   structures and helper functions that already exist to interact
   with elements in the system?  Would a future change to some
   elements in the system that are touched by the implementation
   require changes to both existing code _and_ reinvented ones the
   patch introduced?

 - Correctness.  Does the implementation actually reflect the design
   and the way the design was explained?

For the "difftool in C" topic, the first two are mostly irrelevant
as the goal of the topic is to first replicate what already exists
faithfully (even in a bug-to-bug compatible way).  The issues in
correctness is something your daily use before submission would
have caught, use of 'next' users as testers will help, and also
caught by running test suite (again, before submission).  

I honestly do not expect glaring errors in the code from experienced
contributors remaining when their patches are polished enough to be
submit to the list.

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

* Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path()
  2017-01-09  1:25           ` Junio C Hamano
  2017-01-09  6:00             ` Jeff King
  2017-01-09  7:49             ` Johannes Schindelin
@ 2017-01-09 19:21             ` Stefan Beller
  2 siblings, 0 replies; 86+ messages in thread
From: Stefan Beller @ 2017-01-09 19:21 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Johannes Schindelin, git@vger.kernel.org, David Aguilar,
	Dennis Kaarsemaker, Paul Sbarra

On Sun, Jan 8, 2017 at 5:25 PM, Junio C Hamano <gitster@pobox.com> wrote:
> So with the above, are you saying "Dscho said 'hopefully', and I
> confirm that this change does squelch misdiagnosis by Coverity"?

I could not find the coverity issue any more.
(It really misses easy access to "recently fixed" problems)

> commit c9bb5d101ca657fa466afa8c4368c43ea7b7aca8
> Author: Johannes Schindelin <johannes.schindelin@gmx.de>
> Date:   Mon Jan 2 17:22:33 2017 +0100
>
>     git_exec_path: avoid Coverity warning about unfree()d result
>
>     Technically, it is correct that git_exec_path() returns a possibly
>     malloc()ed string returned from system_path(), and it is sometimes
>     not allocated.  Cache the result in a static variable and make sure
>     that we call system_path() only once, which plugs a potential leak.
>
>     Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
>     Signed-off-by: Junio C Hamano <gitster@pobox.com>

Sounds good to me,

Thanks,
Stefan

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

* [PATCH v5 0/3] Turn the difftool into a builtin
  2017-01-02 16:16     ` [PATCH v4 0/4] Show Git Mailing List: a builtin difftool Johannes Schindelin
                         ` (3 preceding siblings ...)
  2017-01-02 16:24       ` [PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now Johannes Schindelin
@ 2017-01-17 15:54       ` Johannes Schindelin
  2017-01-17 15:54         ` [PATCH v5 1/3] difftool: add a skeleton for the upcoming builtin Johannes Schindelin
                           ` (4 more replies)
  4 siblings, 5 replies; 86+ messages in thread
From: Johannes Schindelin @ 2017-01-17 15:54 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

This patch series converts the difftool from a Perl script into a
builtin, for three reasons:

1. Perl is really not native on Windows. Not only is there a performance
   penalty to be paid just for running Perl scripts, we also have to deal
   with the fact that users may have different Perl installations, with
   different options, and some other Perl installation may decide to set
   PERL5LIB globally, wreaking havoc with Git for Windows' Perl (which we
   have to use because almost all other Perl distributions lack the
   Subversion bindings we need for `git svn`).

2. As the Perl script uses Unix-y paths that are not native to Windows,
   the Perl interpreter has to go through a POSIX emulation layer (the
   MSYS2 runtime). This means that paths have to be converted from
   Unix-y paths to Windows-y paths (and vice versa) whenever crossing
   the POSIX emulation barrier, leading to quite possibly surprising path
   translation errors.

3. Perl makes for a rather large reason that Git for Windows' installer
   weighs in with >30MB. While one Perl script less does not relieve us
   of that burden, it is one step in the right direction.

Changes since v4:

- skipped the unrelated Coverity-appeasing patch.

- replaced the cross-validation with the Perl script by a patch that
  retires the Perl script instead.


Johannes Schindelin (3):
  difftool: add a skeleton for the upcoming builtin
  difftool: implement the functionality in the builtin
  Retire the scripted difftool

 Makefile                                           |   2 +-
 builtin.h                                          |   1 +
 builtin/difftool.c                                 | 692 +++++++++++++++++++++
 .../examples/git-difftool.perl                     |   0
 git.c                                              |   1 +
 t/t7800-difftool.sh                                |  92 +--
 6 files changed, 741 insertions(+), 47 deletions(-)
 create mode 100644 builtin/difftool.c
 rename git-difftool.perl => contrib/examples/git-difftool.perl (100%)


base-commit: d7dffce1cebde29a0c4b309a79e4345450bf352a
Published-As: https://github.com/dscho/git/releases/tag/builtin-difftool-v5
Fetch-It-Via: git fetch https://github.com/dscho/git builtin-difftool-v5

Interdiff vs v4:

 diff --git a/.gitignore b/.gitignore
 index 5555ae025b..6722f78f9a 100644
 --- a/.gitignore
 +++ b/.gitignore
 @@ -76,7 +76,6 @@
  /git-init-db
  /git-interpret-trailers
  /git-instaweb
 -/git-legacy-difftool
  /git-log
  /git-ls-files
  /git-ls-remote
 diff --git a/Makefile b/Makefile
 index 8cf5bef034..e9aa6ae57c 100644
 --- a/Makefile
 +++ b/Makefile
 @@ -522,7 +522,6 @@ SCRIPT_LIB += git-sh-setup
  SCRIPT_LIB += git-sh-i18n
  
  SCRIPT_PERL += git-add--interactive.perl
 -SCRIPT_PERL += git-legacy-difftool.perl
  SCRIPT_PERL += git-archimport.perl
  SCRIPT_PERL += git-cvsexportcommit.perl
  SCRIPT_PERL += git-cvsimport.perl
 diff --git a/builtin/difftool.c b/builtin/difftool.c
 index 2115e548a5..42ad9e804a 100644
 --- a/builtin/difftool.c
 +++ b/builtin/difftool.c
 @@ -616,30 +616,6 @@ static int run_file_diff(int prompt, const char *prefix,
  	exit(ret);
  }
  
 -/*
 - * NEEDSWORK: this function can go once the legacy-difftool Perl script is
 - * retired.
 - *
 - * We intentionally avoid reading the config directly here, to avoid messing up
 - * the GIT_* environment variables when we need to fall back to exec()ing the
 - * Perl script.
 - */
 -static int use_builtin_difftool(void) {
 -	struct child_process cp = CHILD_PROCESS_INIT;
 -	struct strbuf out = STRBUF_INIT;
 -	int ret;
 -
 -	argv_array_pushl(&cp.args,
 -			 "config", "--bool", "difftool.usebuiltin", NULL);
 -	cp.git_cmd = 1;
 -	if (capture_command(&cp, &out, 6))
 -		return 0;
 -	strbuf_trim(&out);
 -	ret = !strcmp("true", out.buf);
 -	strbuf_release(&out);
 -	return ret;
 -}
 -
  int cmd_difftool(int argc, const char **argv, const char *prefix)
  {
  	int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,
 @@ -671,23 +647,6 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)
  		OPT_END()
  	};
  
 -	/*
 -	 * NEEDSWORK: Once the builtin difftool has been tested enough
 -	 * and git-legacy-difftool.perl is retired to contrib/, this preamble
 -	 * can be removed.
 -	 */
 -	if (!use_builtin_difftool()) {
 -		const char *path = mkpath("%s/git-legacy-difftool",
 -					  git_exec_path());
 -
 -		if (sane_execvp(path, (char **)argv) < 0)
 -			die_errno("could not exec %s", path);
 -
 -		return 0;
 -	}
 -	prefix = setup_git_directory();
 -	trace_repo_setup(prefix);
 -	setup_work_tree();
  	/* NEEDSWORK: once we no longer spawn anything, remove this */
  	setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);
  	setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);
 diff --git a/git-legacy-difftool.perl b/contrib/examples/git-difftool.perl
 similarity index 100%
 rename from git-legacy-difftool.perl
 rename to contrib/examples/git-difftool.perl
 diff --git a/exec_cmd.c b/exec_cmd.c
 index 587bd7eb48..19ac2146d0 100644
 --- a/exec_cmd.c
 +++ b/exec_cmd.c
 @@ -65,7 +65,6 @@ void git_set_argv_exec_path(const char *exec_path)
  const char *git_exec_path(void)
  {
  	const char *env;
 -	static char *system_exec_path;
  
  	if (argv_exec_path)
  		return argv_exec_path;
 @@ -75,9 +74,7 @@ const char *git_exec_path(void)
  		return env;
  	}
  
 -	if (!system_exec_path)
 -		system_exec_path = system_path(GIT_EXEC_PATH);
 -	return system_exec_path;
 +	return system_path(GIT_EXEC_PATH);
  }
  
  static void add_path(struct strbuf *out, const char *path)
 diff --git a/git.c b/git.c
 index c58181e5ef..bd4d668a21 100644
 --- a/git.c
 +++ b/git.c
 @@ -424,12 +424,7 @@ static struct cmd_struct commands[] = {
  	{ "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
  	{ "diff-index", cmd_diff_index, RUN_SETUP },
  	{ "diff-tree", cmd_diff_tree, RUN_SETUP },
 -	/*
 -	 * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in
 -	 * builtin/difftool.c has been removed, this entry should be changed to
 -	 * RUN_SETUP | NEED_WORK_TREE
 -	 */
 -	{ "difftool", cmd_difftool },
 +	{ "difftool", cmd_difftool, RUN_SETUP | NEED_WORK_TREE },
  	{ "fast-export", cmd_fast_export, RUN_SETUP },
  	{ "fetch", cmd_fetch, RUN_SETUP },
  	{ "fetch-pack", cmd_fetch_pack, RUN_SETUP },
 diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
 index 273ab55723..aa0ef02597 100755
 --- a/t/t7800-difftool.sh
 +++ b/t/t7800-difftool.sh
 @@ -23,24 +23,8 @@ prompt_given ()
  	test "$prompt" = "Launch 'test-tool' [Y/n]? branch"
  }
  
 -for use_builtin_difftool in false true
 -do
 -
 -test_expect_success 'verify we are running the correct difftool' '
 -	if test true = '$use_builtin_difftool'
 -	then
 -		test_must_fail ok=129 git difftool -h >help &&
 -		grep "g, --gui" help
 -	else
 -		git difftool -h >help &&
 -		grep "g|--gui" help
 -	fi
 -'
 -
 -# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.
 -
  # Create a file on master and change it on branch
 -test_expect_success PERL 'setup' '
 +test_expect_success 'setup' '
  	echo master >file &&
  	git add file &&
  	git commit -m "added file" &&
 @@ -52,7 +36,7 @@ test_expect_success PERL 'setup' '
  '
  
  # Configure a custom difftool.<tool>.cmd and use it
 -test_expect_success PERL 'custom commands' '
 +test_expect_success 'custom commands' '
  	difftool_test_setup &&
  	test_config difftool.test-tool.cmd "cat \"\$REMOTE\"" &&
  	echo master >expect &&
 @@ -65,21 +49,21 @@ test_expect_success PERL 'custom commands' '
  	test_cmp expect actual
  '
  
 -test_expect_success PERL 'custom tool commands override built-ins' '
 +test_expect_success 'custom tool commands override built-ins' '
  	test_config difftool.vimdiff.cmd "cat \"\$REMOTE\"" &&
  	echo master >expect &&
  	git difftool --tool vimdiff --no-prompt branch >actual &&
  	test_cmp expect actual
  '
  
 -test_expect_success PERL 'difftool ignores bad --tool values' '
 +test_expect_success 'difftool ignores bad --tool values' '
  	: >expect &&
  	test_must_fail \
  		git difftool --no-prompt --tool=bad-tool branch >actual &&
  	test_cmp expect actual
  '
  
 -test_expect_success PERL 'difftool forwards arguments to diff' '
 +test_expect_success 'difftool forwards arguments to diff' '
  	difftool_test_setup &&
  	>for-diff &&
  	git add for-diff &&
 @@ -92,40 +76,40 @@ test_expect_success PERL 'difftool forwards arguments to diff' '
  	rm for-diff
  '
  
 -test_expect_success PERL 'difftool ignores exit code' '
 +test_expect_success 'difftool ignores exit code' '
  	test_config difftool.error.cmd false &&
  	git difftool -y -t error branch
  '
  
 -test_expect_success PERL 'difftool forwards exit code with --trust-exit-code' '
 +test_expect_success 'difftool forwards exit code with --trust-exit-code' '
  	test_config difftool.error.cmd false &&
  	test_must_fail git difftool -y --trust-exit-code -t error branch
  '
  
 -test_expect_success PERL 'difftool forwards exit code with --trust-exit-code for built-ins' '
 +test_expect_success 'difftool forwards exit code with --trust-exit-code for built-ins' '
  	test_config difftool.vimdiff.path false &&
  	test_must_fail git difftool -y --trust-exit-code -t vimdiff branch
  '
  
 -test_expect_success PERL 'difftool honors difftool.trustExitCode = true' '
 +test_expect_success 'difftool honors difftool.trustExitCode = true' '
  	test_config difftool.error.cmd false &&
  	test_config difftool.trustExitCode true &&
  	test_must_fail git difftool -y -t error branch
  '
  
 -test_expect_success PERL 'difftool honors difftool.trustExitCode = false' '
 +test_expect_success 'difftool honors difftool.trustExitCode = false' '
  	test_config difftool.error.cmd false &&
  	test_config difftool.trustExitCode false &&
  	git difftool -y -t error branch
  '
  
 -test_expect_success PERL 'difftool ignores exit code with --no-trust-exit-code' '
 +test_expect_success 'difftool ignores exit code with --no-trust-exit-code' '
  	test_config difftool.error.cmd false &&
  	test_config difftool.trustExitCode true &&
  	git difftool -y --no-trust-exit-code -t error branch
  '
  
 -test_expect_success PERL 'difftool stops on error with --trust-exit-code' '
 +test_expect_success 'difftool stops on error with --trust-exit-code' '
  	test_when_finished "rm -f for-diff .git/fail-right-file" &&
  	test_when_finished "git reset -- for-diff" &&
  	write_script .git/fail-right-file <<-\EOF &&
 @@ -140,13 +124,13 @@ test_expect_success PERL 'difftool stops on error with --trust-exit-code' '
  	test_cmp expect actual
  '
  
 -test_expect_success PERL 'difftool honors exit status if command not found' '
 +test_expect_success 'difftool honors exit status if command not found' '
  	test_config difftool.nonexistent.cmd i-dont-exist &&
  	test_config difftool.trustExitCode false &&
  	test_must_fail git difftool -y -t nonexistent branch
  '
  
 -test_expect_success PERL 'difftool honors --gui' '
 +test_expect_success 'difftool honors --gui' '
  	difftool_test_setup &&
  	test_config merge.tool bogus-tool &&
  	test_config diff.tool bogus-tool &&
 @@ -157,7 +141,7 @@ test_expect_success PERL 'difftool honors --gui' '
  	test_cmp expect actual
  '
  
 -test_expect_success PERL 'difftool --gui last setting wins' '
 +test_expect_success 'difftool --gui last setting wins' '
  	difftool_test_setup &&
  	: >expect &&
  	git difftool --no-prompt --gui --no-gui >actual &&
 @@ -171,7 +155,7 @@ test_expect_success PERL 'difftool --gui last setting wins' '
  	test_cmp expect actual
  '
  
 -test_expect_success PERL 'difftool --gui works without configured diff.guitool' '
 +test_expect_success 'difftool --gui works without configured diff.guitool' '
  	difftool_test_setup &&
  	echo branch >expect &&
  	git difftool --no-prompt --gui branch >actual &&
 @@ -179,7 +163,7 @@ test_expect_success PERL 'difftool --gui works without configured diff.guitool'
  '
  
  # Specify the diff tool using $GIT_DIFF_TOOL
 -test_expect_success PERL 'GIT_DIFF_TOOL variable' '
 +test_expect_success 'GIT_DIFF_TOOL variable' '
  	difftool_test_setup &&
  	git config --unset diff.tool &&
  	echo branch >expect &&
 @@ -189,7 +173,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL variable' '
  
  # Test the $GIT_*_TOOL variables and ensure
  # that $GIT_DIFF_TOOL always wins unless --tool is specified
 -test_expect_success PERL 'GIT_DIFF_TOOL overrides' '
 +test_expect_success 'GIT_DIFF_TOOL overrides' '
  	difftool_test_setup &&
  	test_config diff.tool bogus-tool &&
  	test_config merge.tool bogus-tool &&
 @@ -207,7 +191,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL overrides' '
  
  # Test that we don't have to pass --no-prompt to difftool
  # when $GIT_DIFFTOOL_NO_PROMPT is true
 -test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' '
 +test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' '
  	difftool_test_setup &&
  	echo branch >expect &&
  	GIT_DIFFTOOL_NO_PROMPT=true git difftool branch >actual &&
 @@ -216,7 +200,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' '
  
  # git-difftool supports the difftool.prompt variable.
  # Test that GIT_DIFFTOOL_PROMPT can override difftool.prompt = false
 -test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' '
 +test_expect_success 'GIT_DIFFTOOL_PROMPT variable' '
  	difftool_test_setup &&
  	test_config difftool.prompt false &&
  	echo >input &&
 @@ -226,7 +210,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' '
  '
  
  # Test that we don't have to pass --no-prompt when difftool.prompt is false
 -test_expect_success PERL 'difftool.prompt config variable is false' '
 +test_expect_success 'difftool.prompt config variable is false' '
  	difftool_test_setup &&
  	test_config difftool.prompt false &&
  	echo branch >expect &&
 @@ -235,7 +219,7 @@ test_expect_success PERL 'difftool.prompt config variable is false' '
  '
  
  # Test that we don't have to pass --no-prompt when mergetool.prompt is false
 -test_expect_success PERL 'difftool merge.prompt = false' '
 +test_expect_success 'difftool merge.prompt = false' '
  	difftool_test_setup &&
  	test_might_fail git config --unset difftool.prompt &&
  	test_config mergetool.prompt false &&
 @@ -245,7 +229,7 @@ test_expect_success PERL 'difftool merge.prompt = false' '
  '
  
  # Test that the -y flag can override difftool.prompt = true
 -test_expect_success PERL 'difftool.prompt can overridden with -y' '
 +test_expect_success 'difftool.prompt can overridden with -y' '
  	difftool_test_setup &&
  	test_config difftool.prompt true &&
  	echo branch >expect &&
 @@ -254,7 +238,7 @@ test_expect_success PERL 'difftool.prompt can overridden with -y' '
  '
  
  # Test that the --prompt flag can override difftool.prompt = false
 -test_expect_success PERL 'difftool.prompt can overridden with --prompt' '
 +test_expect_success 'difftool.prompt can overridden with --prompt' '
  	difftool_test_setup &&
  	test_config difftool.prompt false &&
  	echo >input &&
 @@ -264,7 +248,7 @@ test_expect_success PERL 'difftool.prompt can overridden with --prompt' '
  '
  
  # Test that the last flag passed on the command-line wins
 -test_expect_success PERL 'difftool last flag wins' '
 +test_expect_success 'difftool last flag wins' '
  	difftool_test_setup &&
  	echo branch >expect &&
  	git difftool --prompt --no-prompt branch >actual &&
 @@ -277,7 +261,7 @@ test_expect_success PERL 'difftool last flag wins' '
  
  # git-difftool falls back to git-mergetool config variables
  # so test that behavior here
 -test_expect_success PERL 'difftool + mergetool config variables' '
 +test_expect_success 'difftool + mergetool config variables' '
  	test_config merge.tool test-tool &&
  	test_config mergetool.test-tool.cmd "cat \$LOCAL" &&
  	echo branch >expect &&
 @@ -291,49 +275,49 @@ test_expect_success PERL 'difftool + mergetool config variables' '
  	test_cmp expect actual
  '
  
 -test_expect_success PERL 'difftool.<tool>.path' '
 +test_expect_success 'difftool.<tool>.path' '
  	test_config difftool.tkdiff.path echo &&
  	git difftool --tool=tkdiff --no-prompt branch >output &&
  	lines=$(grep file output | wc -l) &&
  	test "$lines" -eq 1
  '
  
 -test_expect_success PERL 'difftool --extcmd=cat' '
 +test_expect_success 'difftool --extcmd=cat' '
  	echo branch >expect &&
  	echo master >>expect &&
  	git difftool --no-prompt --extcmd=cat branch >actual &&
  	test_cmp expect actual
  '
  
 -test_expect_success PERL 'difftool --extcmd cat' '
 +test_expect_success 'difftool --extcmd cat' '
  	echo branch >expect &&
  	echo master >>expect &&
  	git difftool --no-prompt --extcmd=cat branch >actual &&
  	test_cmp expect actual
  '
  
 -test_expect_success PERL 'difftool -x cat' '
 +test_expect_success 'difftool -x cat' '
  	echo branch >expect &&
  	echo master >>expect &&
  	git difftool --no-prompt -x cat branch >actual &&
  	test_cmp expect actual
  '
  
 -test_expect_success PERL 'difftool --extcmd echo arg1' '
 +test_expect_success 'difftool --extcmd echo arg1' '
  	echo file >expect &&
  	git difftool --no-prompt \
  		--extcmd sh\ -c\ \"echo\ \$1\" branch >actual &&
  	test_cmp expect actual
  '
  
 -test_expect_success PERL 'difftool --extcmd cat arg1' '
 +test_expect_success 'difftool --extcmd cat arg1' '
  	echo master >expect &&
  	git difftool --no-prompt \
  		--extcmd sh\ -c\ \"cat\ \$1\" branch >actual &&
  	test_cmp expect actual
  '
  
 -test_expect_success PERL 'difftool --extcmd cat arg2' '
 +test_expect_success 'difftool --extcmd cat arg2' '
  	echo branch >expect &&
  	git difftool --no-prompt \
  		--extcmd sh\ -c\ \"cat\ \$2\" branch >actual &&
 @@ -341,7 +325,7 @@ test_expect_success PERL 'difftool --extcmd cat arg2' '
  '
  
  # Create a second file on master and a different version on branch
 -test_expect_success PERL 'setup with 2 files different' '
 +test_expect_success 'setup with 2 files different' '
  	echo m2 >file2 &&
  	git add file2 &&
  	git commit -m "added file2" &&
 @@ -353,7 +337,7 @@ test_expect_success PERL 'setup with 2 files different' '
  	git checkout master
  '
  
 -test_expect_success PERL 'say no to the first file' '
 +test_expect_success 'say no to the first file' '
  	(echo n && echo) >input &&
  	git difftool -x cat branch <input >output &&
  	grep m2 output &&
 @@ -362,7 +346,7 @@ test_expect_success PERL 'say no to the first file' '
  	! grep branch output
  '
  
 -test_expect_success PERL 'say no to the second file' '
 +test_expect_success 'say no to the second file' '
  	(echo && echo n) >input &&
  	git difftool -x cat branch <input >output &&
  	grep master output &&
 @@ -371,7 +355,7 @@ test_expect_success PERL 'say no to the second file' '
  	! grep br2 output
  '
  
 -test_expect_success PERL 'ending prompt input with EOF' '
 +test_expect_success 'ending prompt input with EOF' '
  	git difftool -x cat branch </dev/null >output &&
  	! grep master output &&
  	! grep branch output &&
 @@ -379,12 +363,12 @@ test_expect_success PERL 'ending prompt input with EOF' '
  	! grep br2 output
  '
  
 -test_expect_success PERL 'difftool --tool-help' '
 +test_expect_success 'difftool --tool-help' '
  	git difftool --tool-help >output &&
  	grep tool output
  '
  
 -test_expect_success PERL 'setup change in subdirectory' '
 +test_expect_success 'setup change in subdirectory' '
  	git checkout master &&
  	mkdir sub &&
  	echo master >sub/sub &&
 @@ -398,11 +382,11 @@ test_expect_success PERL 'setup change in subdirectory' '
  '
  
  run_dir_diff_test () {
 -	test_expect_success PERL "$1 --no-symlinks" "
 +	test_expect_success "$1 --no-symlinks" "
  		symlinks=--no-symlinks &&
  		$2
  	"
 -	test_expect_success PERL,SYMLINKS "$1 --symlinks" "
 +	test_expect_success SYMLINKS "$1 --symlinks" "
  		symlinks=--symlinks &&
  		$2
  	"
 @@ -524,7 +508,7 @@ do
  done >actual
  EOF
  
 -test_expect_success PERL,SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' '
 +test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' '
  	cat >expect <<-EOF &&
  	file
  	$PWD/file
 @@ -561,7 +545,7 @@ write_script modify-file <<\EOF
  echo "new content" >file
  EOF
  
 -test_expect_success PERL 'difftool --no-symlinks does not overwrite working tree file ' '
 +test_expect_success 'difftool --no-symlinks does not overwrite working tree file ' '
  	echo "orig content" >file &&
  	git difftool --dir-diff --no-symlinks --extcmd "$PWD/modify-file" branch &&
  	echo "new content" >expect &&
 @@ -574,7 +558,7 @@ echo "tmp content" >"$2/file" &&
  echo "$2" >tmpdir
  EOF
  
 -test_expect_success PERL 'difftool --no-symlinks detects conflict ' '
 +test_expect_success 'difftool --no-symlinks detects conflict ' '
  	(
  		TMPDIR=$TRASH_DIRECTORY &&
  		export TMPDIR &&
 @@ -587,7 +571,7 @@ test_expect_success PERL 'difftool --no-symlinks detects conflict ' '
  	)
  '
  
 -test_expect_success PERL 'difftool properly honors gitlink and core.worktree' '
 +test_expect_success 'difftool properly honors gitlink and core.worktree' '
  	git submodule add ./. submod/ule &&
  	test_config -C submod/ule diff.tool checktrees &&
  	test_config -C submod/ule difftool.checktrees.cmd '\''
 @@ -601,7 +585,7 @@ test_expect_success PERL 'difftool properly honors gitlink and core.worktree' '
  	)
  '
  
 -test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' '
 +test_expect_success SYMLINKS 'difftool --dir-diff symlinked directories' '
  	git init dirlinks &&
  	(
  		cd dirlinks &&
 @@ -620,17 +604,4 @@ test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' '
  	)
  '
  
 -test true != $use_builtin_difftool || break
 -
 -test_expect_success 'tear down for re-run' '
 -	rm -rf * .[a-z]* &&
 -	git init
 -'
 -
 -# run as builtin difftool now
 -GIT_CONFIG_PARAMETERS="'difftool.usebuiltin=true'"
 -export GIT_CONFIG_PARAMETERS
 -
 -done
 -
  test_done

-- 
2.11.0.windows.3


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

* [PATCH v5 1/3] difftool: add a skeleton for the upcoming builtin
  2017-01-17 15:54       ` [PATCH v5 0/3] Turn the difftool into a builtin Johannes Schindelin
@ 2017-01-17 15:54         ` Johannes Schindelin
  2017-01-17 15:55         ` [PATCH v5 2/3] difftool: implement the functionality in the builtin Johannes Schindelin
                           ` (3 subsequent siblings)
  4 siblings, 0 replies; 86+ messages in thread
From: Johannes Schindelin @ 2017-01-17 15:54 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

This adds a builtin difftool that still falls back to the legacy Perl
version, which has been renamed to `legacy-difftool`.

The idea is that the new, experimental, builtin difftool immediately hands
off to the legacy difftool for now, unless the config variable
difftool.useBuiltin is set to true.

This feature flag will be used in the upcoming Git for Windows v2.11.0
release, to allow early testers to opt-in to use the builtin difftool and
flesh out any bugs.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitignore                                    |  1 +
 Makefile                                      |  3 +-
 builtin.h                                     |  1 +
 builtin/difftool.c                            | 63 +++++++++++++++++++++++++++
 git-difftool.perl => git-legacy-difftool.perl |  0
 git.c                                         |  6 +++
 t/t7800-difftool.sh                           |  2 +
 7 files changed, 75 insertions(+), 1 deletion(-)
 create mode 100644 builtin/difftool.c
 rename git-difftool.perl => git-legacy-difftool.perl (100%)

diff --git a/.gitignore b/.gitignore
index 6722f78f9a..5555ae025b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -76,6 +76,7 @@
 /git-init-db
 /git-interpret-trailers
 /git-instaweb
+/git-legacy-difftool
 /git-log
 /git-ls-files
 /git-ls-remote
diff --git a/Makefile b/Makefile
index d861bd9985..8cf5bef034 100644
--- a/Makefile
+++ b/Makefile
@@ -522,7 +522,7 @@ SCRIPT_LIB += git-sh-setup
 SCRIPT_LIB += git-sh-i18n
 
 SCRIPT_PERL += git-add--interactive.perl
-SCRIPT_PERL += git-difftool.perl
+SCRIPT_PERL += git-legacy-difftool.perl
 SCRIPT_PERL += git-archimport.perl
 SCRIPT_PERL += git-cvsexportcommit.perl
 SCRIPT_PERL += git-cvsimport.perl
@@ -883,6 +883,7 @@ BUILTIN_OBJS += builtin/diff-files.o
 BUILTIN_OBJS += builtin/diff-index.o
 BUILTIN_OBJS += builtin/diff-tree.o
 BUILTIN_OBJS += builtin/diff.o
+BUILTIN_OBJS += builtin/difftool.o
 BUILTIN_OBJS += builtin/fast-export.o
 BUILTIN_OBJS += builtin/fetch-pack.o
 BUILTIN_OBJS += builtin/fetch.o
diff --git a/builtin.h b/builtin.h
index b9122bc5f4..67f80519da 100644
--- a/builtin.h
+++ b/builtin.h
@@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_index(int argc, const char **argv, const char *prefix);
 extern int cmd_diff(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_difftool(int argc, const char **argv, const char *prefix);
 extern int cmd_fast_export(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);
diff --git a/builtin/difftool.c b/builtin/difftool.c
new file mode 100644
index 0000000000..53870bbaf7
--- /dev/null
+++ b/builtin/difftool.c
@@ -0,0 +1,63 @@
+/*
+ * "git difftool" builtin command
+ *
+ * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
+ * git-difftool--helper script.
+ *
+ * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.
+ * The GIT_DIFF* variables are exported for use by git-difftool--helper.
+ *
+ * Any arguments that are unknown to this script are forwarded to 'git diff'.
+ *
+ * Copyright (C) 2016 Johannes Schindelin
+ */
+#include "builtin.h"
+#include "run-command.h"
+#include "exec_cmd.h"
+
+/*
+ * NEEDSWORK: this function can go once the legacy-difftool Perl script is
+ * retired.
+ *
+ * We intentionally avoid reading the config directly here, to avoid messing up
+ * the GIT_* environment variables when we need to fall back to exec()ing the
+ * Perl script.
+ */
+static int use_builtin_difftool(void) {
+	struct child_process cp = CHILD_PROCESS_INIT;
+	struct strbuf out = STRBUF_INIT;
+	int ret;
+
+	argv_array_pushl(&cp.args,
+			 "config", "--bool", "difftool.usebuiltin", NULL);
+	cp.git_cmd = 1;
+	if (capture_command(&cp, &out, 6))
+		return 0;
+	strbuf_trim(&out);
+	ret = !strcmp("true", out.buf);
+	strbuf_release(&out);
+	return ret;
+}
+
+int cmd_difftool(int argc, const char **argv, const char *prefix)
+{
+	/*
+	 * NEEDSWORK: Once the builtin difftool has been tested enough
+	 * and git-legacy-difftool.perl is retired to contrib/, this preamble
+	 * can be removed.
+	 */
+	if (!use_builtin_difftool()) {
+		const char *path = mkpath("%s/git-legacy-difftool",
+					  git_exec_path());
+
+		if (sane_execvp(path, (char **)argv) < 0)
+			die_errno("could not exec %s", path);
+
+		return 0;
+	}
+	prefix = setup_git_directory();
+	trace_repo_setup(prefix);
+	setup_work_tree();
+
+	die("TODO");
+}
diff --git a/git-difftool.perl b/git-legacy-difftool.perl
similarity index 100%
rename from git-difftool.perl
rename to git-legacy-difftool.perl
diff --git a/git.c b/git.c
index bbaa949e9c..c58181e5ef 100644
--- a/git.c
+++ b/git.c
@@ -424,6 +424,12 @@ static struct cmd_struct commands[] = {
 	{ "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
 	{ "diff-index", cmd_diff_index, RUN_SETUP },
 	{ "diff-tree", cmd_diff_tree, RUN_SETUP },
+	/*
+	 * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in
+	 * builtin/difftool.c has been removed, this entry should be changed to
+	 * RUN_SETUP | NEED_WORK_TREE
+	 */
+	{ "difftool", cmd_difftool },
 	{ "fast-export", cmd_fast_export, RUN_SETUP },
 	{ "fetch", cmd_fetch, RUN_SETUP },
 	{ "fetch-pack", cmd_fetch_pack, RUN_SETUP },
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index 99d4123461..e94910c563 100755
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
@@ -23,6 +23,8 @@ prompt_given ()
 	test "$prompt" = "Launch 'test-tool' [Y/n]? branch"
 }
 
+# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.
+
 # Create a file on master and change it on branch
 test_expect_success PERL 'setup' '
 	echo master >file &&
-- 
2.11.0.windows.3



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

* [PATCH v5 2/3] difftool: implement the functionality in the builtin
  2017-01-17 15:54       ` [PATCH v5 0/3] Turn the difftool into a builtin Johannes Schindelin
  2017-01-17 15:54         ` [PATCH v5 1/3] difftool: add a skeleton for the upcoming builtin Johannes Schindelin
@ 2017-01-17 15:55         ` Johannes Schindelin
  2017-01-17 15:55         ` [PATCH v5 3/3] Retire the scripted difftool Johannes Schindelin
                           ` (2 subsequent siblings)
  4 siblings, 0 replies; 86+ messages in thread
From: Johannes Schindelin @ 2017-01-17 15:55 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

This patch gives life to the skeleton added in the previous patch.

The motivation for converting the difftool is that Perl scripts are not at
all native on Windows, and that `git difftool` therefore is pretty slow on
that platform, when there is no good reason for it to be slow.

In addition, Perl does not really have access to Git's internals. That
means that any script will always have to jump through unnecessary
hoops.

The current version of the builtin difftool does not, however, make full
use of the internals but instead chooses to spawn a couple of Git
processes, still, to make for an easier conversion. There remains a lot
of room for improvement, left for a later date.

Note: to play it safe, the original difftool is still called unless the
config setting difftool.useBuiltin is set to true.

The reason: this new, experimental, builtin difftool will be shipped as
part of Git for Windows v2.11.0, to allow for easier large-scale
testing, but of course as an opt-in feature.

Sadly, the speedup is more noticable on Linux than on Windows: a quick
test shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s)
(real/user/sys) in a Linux VM, down from  (6.529s/3.112s/0.644s), while
on Windows, it is (36.064s/2.730s/7.194s), down from
(47.637s/2.407s/6.863s). The culprit is most likely the overhead
incurred from *still* having to shell out to mergetool-lib.sh and
difftool--helper.sh.

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

diff --git a/builtin/difftool.c b/builtin/difftool.c
index 53870bbaf7..2115e548a5 100644
--- a/builtin/difftool.c
+++ b/builtin/difftool.c
@@ -11,9 +11,610 @@
  *
  * Copyright (C) 2016 Johannes Schindelin
  */
+#include "cache.h"
 #include "builtin.h"
 #include "run-command.h"
 #include "exec_cmd.h"
+#include "parse-options.h"
+#include "argv-array.h"
+#include "strbuf.h"
+#include "lockfile.h"
+#include "dir.h"
+
+static char *diff_gui_tool;
+static int trust_exit_code;
+
+static const char *const builtin_difftool_usage[] = {
+	N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"),
+	NULL
+};
+
+static int difftool_config(const char *var, const char *value, void *cb)
+{
+	if (!strcmp(var, "diff.guitool")) {
+		diff_gui_tool = xstrdup(value);
+		return 0;
+	}
+
+	if (!strcmp(var, "difftool.trustexitcode")) {
+		trust_exit_code = git_config_bool(var, value);
+		return 0;
+	}
+
+	return git_default_config(var, value, cb);
+}
+
+static int print_tool_help(void)
+{
+	const char *argv[] = { "mergetool", "--tool-help=diff", NULL };
+	return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
+static int parse_index_info(char *p, int *mode1, int *mode2,
+			    struct object_id *oid1, struct object_id *oid2,
+			    char *status)
+{
+	if (*p != ':')
+		return error("expected ':', got '%c'", *p);
+	*mode1 = (int)strtol(p + 1, &p, 8);
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	*mode2 = (int)strtol(p + 1, &p, 8);
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	if (get_oid_hex(++p, oid1))
+		return error("expected object ID, got '%s'", p + 1);
+	p += GIT_SHA1_HEXSZ;
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	if (get_oid_hex(++p, oid2))
+		return error("expected object ID, got '%s'", p + 1);
+	p += GIT_SHA1_HEXSZ;
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	*status = *++p;
+	if (!*status)
+		return error("missing status");
+	if (p[1] && !isdigit(p[1]))
+		return error("unexpected trailer: '%s'", p + 1);
+	return 0;
+}
+
+/*
+ * Remove any trailing slash from $workdir
+ * before starting to avoid double slashes in symlink targets.
+ */
+static void add_path(struct strbuf *buf, size_t base_len, const char *path)
+{
+	strbuf_setlen(buf, base_len);
+	if (buf->len && buf->buf[buf->len - 1] != '/')
+		strbuf_addch(buf, '/');
+	strbuf_addstr(buf, path);
+}
+
+/*
+ * Determine whether we can simply reuse the file in the worktree.
+ */
+static int use_wt_file(const char *workdir, const char *name,
+		       struct object_id *oid)
+{
+	struct strbuf buf = STRBUF_INIT;
+	struct stat st;
+	int use = 0;
+
+	strbuf_addstr(&buf, workdir);
+	add_path(&buf, buf.len, name);
+
+	if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) {
+		struct object_id wt_oid;
+		int fd = open(buf.buf, O_RDONLY);
+
+		if (fd >= 0 &&
+		    !index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {
+			if (is_null_oid(oid)) {
+				oidcpy(oid, &wt_oid);
+				use = 1;
+			} else if (!oidcmp(oid, &wt_oid))
+				use = 1;
+		}
+	}
+
+	strbuf_release(&buf);
+
+	return use;
+}
+
+struct working_tree_entry {
+	struct hashmap_entry entry;
+	char path[FLEX_ARRAY];
+};
+
+static int working_tree_entry_cmp(struct working_tree_entry *a,
+				  struct working_tree_entry *b, void *keydata)
+{
+	return strcmp(a->path, b->path);
+}
+
+/*
+ * The `left` and `right` entries hold paths for the symlinks hashmap,
+ * and a SHA-1 surrounded by brief text for submodules.
+ */
+struct pair_entry {
+	struct hashmap_entry entry;
+	char left[PATH_MAX], right[PATH_MAX];
+	const char path[FLEX_ARRAY];
+};
+
+static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata)
+{
+	return strcmp(a->path, b->path);
+}
+
+static void add_left_or_right(struct hashmap *map, const char *path,
+			      const char *content, int is_right)
+{
+	struct pair_entry *e, *existing;
+
+	FLEX_ALLOC_STR(e, path, path);
+	hashmap_entry_init(e, strhash(path));
+	existing = hashmap_get(map, e, NULL);
+	if (existing) {
+		free(e);
+		e = existing;
+	} else {
+		e->left[0] = e->right[0] = '\0';
+		hashmap_add(map, e);
+	}
+	strlcpy(is_right ? e->right : e->left, content, PATH_MAX);
+}
+
+struct path_entry {
+	struct hashmap_entry entry;
+	char path[FLEX_ARRAY];
+};
+
+static int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key)
+{
+	return strcmp(a->path, key ? key : b->path);
+}
+
+static void changed_files(struct hashmap *result, const char *index_path,
+			  const char *workdir)
+{
+	struct child_process update_index = CHILD_PROCESS_INIT;
+	struct child_process diff_files = CHILD_PROCESS_INIT;
+	struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT;
+	const char *git_dir = absolute_path(get_git_dir()), *env[] = {
+		NULL, NULL
+	};
+	FILE *fp;
+
+	strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path);
+	env[0] = index_env.buf;
+
+	argv_array_pushl(&update_index.args,
+			 "--git-dir", git_dir, "--work-tree", workdir,
+			 "update-index", "--really-refresh", "-q",
+			 "--unmerged", NULL);
+	update_index.no_stdin = 1;
+	update_index.no_stdout = 1;
+	update_index.no_stderr = 1;
+	update_index.git_cmd = 1;
+	update_index.use_shell = 0;
+	update_index.clean_on_exit = 1;
+	update_index.dir = workdir;
+	update_index.env = env;
+	/* Ignore any errors of update-index */
+	run_command(&update_index);
+
+	argv_array_pushl(&diff_files.args,
+			 "--git-dir", git_dir, "--work-tree", workdir,
+			 "diff-files", "--name-only", "-z", NULL);
+	diff_files.no_stdin = 1;
+	diff_files.git_cmd = 1;
+	diff_files.use_shell = 0;
+	diff_files.clean_on_exit = 1;
+	diff_files.out = -1;
+	diff_files.dir = workdir;
+	diff_files.env = env;
+	if (start_command(&diff_files))
+		die("could not obtain raw diff");
+	fp = xfdopen(diff_files.out, "r");
+	while (!strbuf_getline_nul(&buf, fp)) {
+		struct path_entry *entry;
+		FLEX_ALLOC_STR(entry, path, buf.buf);
+		hashmap_entry_init(entry, strhash(buf.buf));
+		hashmap_add(result, entry);
+	}
+	if (finish_command(&diff_files))
+		die("diff-files did not exit properly");
+	strbuf_release(&index_env);
+	strbuf_release(&buf);
+}
+
+static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)
+{
+	struct strbuf buf = STRBUF_INIT;
+	strbuf_addstr(&buf, tmpdir);
+	remove_dir_recursively(&buf, 0);
+	if (exit_code)
+		warning(_("failed: %d"), exit_code);
+	exit(exit_code);
+}
+
+static int ensure_leading_directories(char *path)
+{
+	switch (safe_create_leading_directories(path)) {
+		case SCLD_OK:
+		case SCLD_EXISTS:
+			return 0;
+		default:
+			return error(_("could not create leading directories "
+				       "of '%s'"), path);
+	}
+}
+
+static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
+			int argc, const char **argv)
+{
+	char tmpdir[PATH_MAX];
+	struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;
+	struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;
+	struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;
+	struct strbuf wtdir = STRBUF_INIT;
+	size_t ldir_len, rdir_len, wtdir_len;
+	struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1);
+	const char *workdir, *tmp;
+	int ret = 0, i;
+	FILE *fp;
+	struct hashmap working_tree_dups, submodules, symlinks2;
+	struct hashmap_iter iter;
+	struct pair_entry *entry;
+	enum object_type type;
+	unsigned long size;
+	struct index_state wtindex;
+	struct checkout lstate, rstate;
+	int rc, flags = RUN_GIT_CMD, err = 0;
+	struct child_process child = CHILD_PROCESS_INIT;
+	const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL };
+	struct hashmap wt_modified, tmp_modified;
+	int indices_loaded = 0;
+
+	workdir = get_git_work_tree();
+
+	/* Setup temp directories */
+	tmp = getenv("TMPDIR");
+	xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp");
+	if (!mkdtemp(tmpdir))
+		return error("could not create '%s'", tmpdir);
+	strbuf_addf(&ldir, "%s/left/", tmpdir);
+	strbuf_addf(&rdir, "%s/right/", tmpdir);
+	strbuf_addstr(&wtdir, workdir);
+	if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))
+		strbuf_addch(&wtdir, '/');
+	mkdir(ldir.buf, 0700);
+	mkdir(rdir.buf, 0700);
+
+	memset(&wtindex, 0, sizeof(wtindex));
+
+	memset(&lstate, 0, sizeof(lstate));
+	lstate.base_dir = ldir.buf;
+	lstate.base_dir_len = ldir.len;
+	lstate.force = 1;
+	memset(&rstate, 0, sizeof(rstate));
+	rstate.base_dir = rdir.buf;
+	rstate.base_dir_len = rdir.len;
+	rstate.force = 1;
+
+	ldir_len = ldir.len;
+	rdir_len = rdir.len;
+	wtdir_len = wtdir.len;
+
+	hashmap_init(&working_tree_dups,
+		     (hashmap_cmp_fn)working_tree_entry_cmp, 0);
+	hashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0);
+	hashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0);
+
+	child.no_stdin = 1;
+	child.git_cmd = 1;
+	child.use_shell = 0;
+	child.clean_on_exit = 1;
+	child.dir = prefix;
+	child.out = -1;
+	argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z",
+			 NULL);
+	for (i = 0; i < argc; i++)
+		argv_array_push(&child.args, argv[i]);
+	if (start_command(&child))
+		die("could not obtain raw diff");
+	fp = xfdopen(child.out, "r");
+
+	/* Build index info for left and right sides of the diff */
+	i = 0;
+	while (!strbuf_getline_nul(&info, fp)) {
+		int lmode, rmode;
+		struct object_id loid, roid;
+		char status;
+		const char *src_path, *dst_path;
+		size_t src_path_len, dst_path_len;
+
+		if (starts_with(info.buf, "::"))
+			die(N_("combined diff formats('-c' and '--cc') are "
+			       "not supported in\n"
+			       "directory diff mode('-d' and '--dir-diff')."));
+
+		if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,
+				     &status))
+			break;
+		if (strbuf_getline_nul(&lpath, fp))
+			break;
+		src_path = lpath.buf;
+		src_path_len = lpath.len;
+
+		i++;
+		if (status != 'C' && status != 'R') {
+			dst_path = src_path;
+			dst_path_len = src_path_len;
+		} else {
+			if (strbuf_getline_nul(&rpath, fp))
+				break;
+			dst_path = rpath.buf;
+			dst_path_len = rpath.len;
+		}
+
+		if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) {
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "Subproject commit %s",
+				    oid_to_hex(&loid));
+			add_left_or_right(&submodules, src_path, buf.buf, 0);
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "Subproject commit %s",
+				    oid_to_hex(&roid));
+			if (!oidcmp(&loid, &roid))
+				strbuf_addstr(&buf, "-dirty");
+			add_left_or_right(&submodules, dst_path, buf.buf, 1);
+			continue;
+		}
+
+		if (S_ISLNK(lmode)) {
+			char *content = read_sha1_file(loid.hash, &type, &size);
+			add_left_or_right(&symlinks2, src_path, content, 0);
+			free(content);
+		}
+
+		if (S_ISLNK(rmode)) {
+			char *content = read_sha1_file(roid.hash, &type, &size);
+			add_left_or_right(&symlinks2, dst_path, content, 1);
+			free(content);
+		}
+
+		if (lmode && status != 'C') {
+			ce->ce_mode = lmode;
+			oidcpy(&ce->oid, &loid);
+			strcpy(ce->name, src_path);
+			ce->ce_namelen = src_path_len;
+			if (checkout_entry(ce, &lstate, NULL))
+				return error("could not write '%s'", src_path);
+		}
+
+		if (rmode) {
+			struct working_tree_entry *entry;
+
+			/* Avoid duplicate working_tree entries */
+			FLEX_ALLOC_STR(entry, path, dst_path);
+			hashmap_entry_init(entry, strhash(dst_path));
+			if (hashmap_get(&working_tree_dups, entry, NULL)) {
+				free(entry);
+				continue;
+			}
+			hashmap_add(&working_tree_dups, entry);
+
+			if (!use_wt_file(workdir, dst_path, &roid)) {
+				ce->ce_mode = rmode;
+				oidcpy(&ce->oid, &roid);
+				strcpy(ce->name, dst_path);
+				ce->ce_namelen = dst_path_len;
+				if (checkout_entry(ce, &rstate, NULL))
+					return error("could not write '%s'",
+						     dst_path);
+			} else if (!is_null_oid(&roid)) {
+				/*
+				 * Changes in the working tree need special
+				 * treatment since they are not part of the
+				 * index.
+				 */
+				struct cache_entry *ce2 =
+					make_cache_entry(rmode, roid.hash,
+							 dst_path, 0, 0);
+
+				add_index_entry(&wtindex, ce2,
+						ADD_CACHE_JUST_APPEND);
+
+				add_path(&rdir, rdir_len, dst_path);
+				if (ensure_leading_directories(rdir.buf))
+					return error("could not create "
+						     "directory for '%s'",
+						     dst_path);
+				add_path(&wtdir, wtdir_len, dst_path);
+				if (symlinks) {
+					if (symlink(wtdir.buf, rdir.buf)) {
+						ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf);
+						goto finish;
+					}
+				} else {
+					struct stat st;
+					if (stat(wtdir.buf, &st))
+						st.st_mode = 0644;
+					if (copy_file(rdir.buf, wtdir.buf,
+						      st.st_mode)) {
+						ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf);
+						goto finish;
+					}
+				}
+			}
+		}
+	}
+
+	if (finish_command(&child)) {
+		ret = error("error occurred running diff --raw");
+		goto finish;
+	}
+
+	if (!i)
+		return 0;
+
+	/*
+	 * Changes to submodules require special treatment.This loop writes a
+	 * temporary file to both the left and right directories to show the
+	 * change in the recorded SHA1 for the submodule.
+	 */
+	hashmap_iter_init(&submodules, &iter);
+	while ((entry = hashmap_iter_next(&iter))) {
+		if (*entry->left) {
+			add_path(&ldir, ldir_len, entry->path);
+			ensure_leading_directories(ldir.buf);
+			write_file(ldir.buf, "%s", entry->left);
+		}
+		if (*entry->right) {
+			add_path(&rdir, rdir_len, entry->path);
+			ensure_leading_directories(rdir.buf);
+			write_file(rdir.buf, "%s", entry->right);
+		}
+	}
+
+	/*
+	 * Symbolic links require special treatment.The standard "git diff"
+	 * shows only the link itself, not the contents of the link target.
+	 * This loop replicates that behavior.
+	 */
+	hashmap_iter_init(&symlinks2, &iter);
+	while ((entry = hashmap_iter_next(&iter))) {
+		if (*entry->left) {
+			add_path(&ldir, ldir_len, entry->path);
+			ensure_leading_directories(ldir.buf);
+			write_file(ldir.buf, "%s", entry->left);
+		}
+		if (*entry->right) {
+			add_path(&rdir, rdir_len, entry->path);
+			ensure_leading_directories(rdir.buf);
+			write_file(rdir.buf, "%s", entry->right);
+		}
+	}
+
+	strbuf_release(&buf);
+
+	strbuf_setlen(&ldir, ldir_len);
+	helper_argv[1] = ldir.buf;
+	strbuf_setlen(&rdir, rdir_len);
+	helper_argv[2] = rdir.buf;
+
+	if (extcmd) {
+		helper_argv[0] = extcmd;
+		flags = 0;
+	} else
+		setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1);
+	rc = run_command_v_opt(helper_argv, flags);
+
+	/*
+	 * If the diff includes working copy files and those
+	 * files were modified during the diff, then the changes
+	 * should be copied back to the working tree.
+	 * Do not copy back files when symlinks are used and the
+	 * external tool did not replace the original link with a file.
+	 *
+	 * These hashes are loaded lazily since they aren't needed
+	 * in the common case of --symlinks and the difftool updating
+	 * files through the symlink.
+	 */
+	hashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp,
+		     wtindex.cache_nr);
+	hashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp,
+		     wtindex.cache_nr);
+
+	for (i = 0; i < wtindex.cache_nr; i++) {
+		struct hashmap_entry dummy;
+		const char *name = wtindex.cache[i]->name;
+		struct stat st;
+
+		add_path(&rdir, rdir_len, name);
+		if (lstat(rdir.buf, &st))
+			continue;
+
+		if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode))
+			continue;
+
+		if (!indices_loaded) {
+			static struct lock_file lock;
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "%s/wtindex", tmpdir);
+			if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
+			    write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {
+				ret = error("could not write %s", buf.buf);
+				rollback_lock_file(&lock);
+				goto finish;
+			}
+			changed_files(&wt_modified, buf.buf, workdir);
+			strbuf_setlen(&rdir, rdir_len);
+			changed_files(&tmp_modified, buf.buf, rdir.buf);
+			add_path(&rdir, rdir_len, name);
+			indices_loaded = 1;
+		}
+
+		hashmap_entry_init(&dummy, strhash(name));
+		if (hashmap_get(&tmp_modified, &dummy, name)) {
+			add_path(&wtdir, wtdir_len, name);
+			if (hashmap_get(&wt_modified, &dummy, name)) {
+				warning(_("both files modified: '%s' and '%s'."),
+					wtdir.buf, rdir.buf);
+				warning(_("working tree file has been left."));
+				warning("");
+				err = 1;
+			} else if (unlink(wtdir.buf) ||
+				   copy_file(wtdir.buf, rdir.buf, st.st_mode))
+				warning_errno(_("could not copy '%s' to '%s'"),
+					      rdir.buf, wtdir.buf);
+		}
+	}
+
+	if (err) {
+		warning(_("temporary files exist in '%s'."), tmpdir);
+		warning(_("you may want to cleanup or recover these."));
+		exit(1);
+	} else
+		exit_cleanup(tmpdir, rc);
+
+finish:
+	free(ce);
+	strbuf_release(&ldir);
+	strbuf_release(&rdir);
+	strbuf_release(&wtdir);
+	strbuf_release(&buf);
+
+	return ret;
+}
+
+static int run_file_diff(int prompt, const char *prefix,
+			 int argc, const char **argv)
+{
+	struct argv_array args = ARGV_ARRAY_INIT;
+	const char *env[] = {
+		"GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL,
+		NULL
+	};
+	int ret = 0, i;
+
+	if (prompt > 0)
+		env[2] = "GIT_DIFFTOOL_PROMPT=true";
+	else if (!prompt)
+		env[2] = "GIT_DIFFTOOL_NO_PROMPT=true";
+
+
+	argv_array_push(&args, "diff");
+	for (i = 0; i < argc; i++)
+		argv_array_push(&args, argv[i]);
+	ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env);
+	exit(ret);
+}
 
 /*
  * NEEDSWORK: this function can go once the legacy-difftool Perl script is
@@ -41,6 +642,35 @@ static int use_builtin_difftool(void) {
 
 int cmd_difftool(int argc, const char **argv, const char *prefix)
 {
+	int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,
+	    tool_help = 0;
+	static char *difftool_cmd = NULL, *extcmd = NULL;
+	struct option builtin_difftool_options[] = {
+		OPT_BOOL('g', "gui", &use_gui_tool,
+			 N_("use `diff.guitool` instead of `diff.tool`")),
+		OPT_BOOL('d', "dir-diff", &dir_diff,
+			 N_("perform a full-directory diff")),
+		{ OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL,
+			N_("do not prompt before launching a diff tool"),
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
+		{ OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL,
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
+			NULL, 1 },
+		OPT_BOOL(0, "symlinks", &symlinks,
+			 N_("use symlinks in dir-diff mode")),
+		OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"),
+			   N_("use the specified diff tool")),
+		OPT_BOOL(0, "tool-help", &tool_help,
+			 N_("print a list of diff tools that may be used with "
+			    "`--tool`")),
+		OPT_BOOL(0, "trust-exit-code", &trust_exit_code,
+			 N_("make 'git-difftool' exit when an invoked diff "
+			    "tool returns a non - zero exit code")),
+		OPT_STRING('x', "extcmd", &extcmd, N_("<command>"),
+			   N_("specify a custom command for viewing diffs")),
+		OPT_END()
+	};
+
 	/*
 	 * NEEDSWORK: Once the builtin difftool has been tested enough
 	 * and git-legacy-difftool.perl is retired to contrib/, this preamble
@@ -58,6 +688,46 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)
 	prefix = setup_git_directory();
 	trace_repo_setup(prefix);
 	setup_work_tree();
+	/* NEEDSWORK: once we no longer spawn anything, remove this */
+	setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);
+	setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);
+
+	git_config(difftool_config, NULL);
+	symlinks = has_symlinks;
+
+	argc = parse_options(argc, argv, prefix, builtin_difftool_options,
+			     builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |
+			     PARSE_OPT_KEEP_DASHDASH);
 
-	die("TODO");
+	if (tool_help)
+		return print_tool_help();
+
+	if (use_gui_tool && diff_gui_tool && *diff_gui_tool)
+		setenv("GIT_DIFF_TOOL", diff_gui_tool, 1);
+	else if (difftool_cmd) {
+		if (*difftool_cmd)
+			setenv("GIT_DIFF_TOOL", difftool_cmd, 1);
+		else
+			die(_("no <tool> given for --tool=<tool>"));
+	}
+
+	if (extcmd) {
+		if (*extcmd)
+			setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1);
+		else
+			die(_("no <cmd> given for --extcmd=<cmd>"));
+	}
+
+	setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE",
+	       trust_exit_code ? "true" : "false", 1);
+
+	/*
+	 * In directory diff mode, 'git-difftool--helper' is called once
+	 * to compare the a / b directories. In file diff mode, 'git diff'
+	 * will invoke a separate instance of 'git-difftool--helper' for
+	 * each file that changed.
+	 */
+	if (dir_diff)
+		return run_dir_diff(extcmd, symlinks, prefix, argc, argv);
+	return run_file_diff(prompt, prefix, argc, argv);
 }
-- 
2.11.0.windows.3



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

* [PATCH v5 3/3] Retire the scripted difftool
  2017-01-17 15:54       ` [PATCH v5 0/3] Turn the difftool into a builtin Johannes Schindelin
  2017-01-17 15:54         ` [PATCH v5 1/3] difftool: add a skeleton for the upcoming builtin Johannes Schindelin
  2017-01-17 15:55         ` [PATCH v5 2/3] difftool: implement the functionality in the builtin Johannes Schindelin
@ 2017-01-17 15:55         ` Johannes Schindelin
  2017-01-17 21:46           ` Junio C Hamano
  2017-01-17 21:31         ` [PATCH v5 0/3] Turn the difftool into a builtin Junio C Hamano
  2017-01-19 20:30         ` [PATCH v6 " Johannes Schindelin
  4 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2017-01-17 15:55 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

It served its purpose, but now we have a builtin difftool. Time for the
Perl script to enjoy Florida.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitignore                                         |  1 -
 Makefile                                           |  1 -
 builtin/difftool.c                                 | 41 ----------
 .../examples/git-difftool.perl                     |  0
 git.c                                              |  7 +-
 t/t7800-difftool.sh                                | 94 +++++++++++-----------
 6 files changed, 47 insertions(+), 97 deletions(-)
 rename git-legacy-difftool.perl => contrib/examples/git-difftool.perl (100%)

diff --git a/.gitignore b/.gitignore
index 5555ae025b..6722f78f9a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -76,7 +76,6 @@
 /git-init-db
 /git-interpret-trailers
 /git-instaweb
-/git-legacy-difftool
 /git-log
 /git-ls-files
 /git-ls-remote
diff --git a/Makefile b/Makefile
index 8cf5bef034..e9aa6ae57c 100644
--- a/Makefile
+++ b/Makefile
@@ -522,7 +522,6 @@ SCRIPT_LIB += git-sh-setup
 SCRIPT_LIB += git-sh-i18n
 
 SCRIPT_PERL += git-add--interactive.perl
-SCRIPT_PERL += git-legacy-difftool.perl
 SCRIPT_PERL += git-archimport.perl
 SCRIPT_PERL += git-cvsexportcommit.perl
 SCRIPT_PERL += git-cvsimport.perl
diff --git a/builtin/difftool.c b/builtin/difftool.c
index 2115e548a5..42ad9e804a 100644
--- a/builtin/difftool.c
+++ b/builtin/difftool.c
@@ -616,30 +616,6 @@ static int run_file_diff(int prompt, const char *prefix,
 	exit(ret);
 }
 
-/*
- * NEEDSWORK: this function can go once the legacy-difftool Perl script is
- * retired.
- *
- * We intentionally avoid reading the config directly here, to avoid messing up
- * the GIT_* environment variables when we need to fall back to exec()ing the
- * Perl script.
- */
-static int use_builtin_difftool(void) {
-	struct child_process cp = CHILD_PROCESS_INIT;
-	struct strbuf out = STRBUF_INIT;
-	int ret;
-
-	argv_array_pushl(&cp.args,
-			 "config", "--bool", "difftool.usebuiltin", NULL);
-	cp.git_cmd = 1;
-	if (capture_command(&cp, &out, 6))
-		return 0;
-	strbuf_trim(&out);
-	ret = !strcmp("true", out.buf);
-	strbuf_release(&out);
-	return ret;
-}
-
 int cmd_difftool(int argc, const char **argv, const char *prefix)
 {
 	int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,
@@ -671,23 +647,6 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 
-	/*
-	 * NEEDSWORK: Once the builtin difftool has been tested enough
-	 * and git-legacy-difftool.perl is retired to contrib/, this preamble
-	 * can be removed.
-	 */
-	if (!use_builtin_difftool()) {
-		const char *path = mkpath("%s/git-legacy-difftool",
-					  git_exec_path());
-
-		if (sane_execvp(path, (char **)argv) < 0)
-			die_errno("could not exec %s", path);
-
-		return 0;
-	}
-	prefix = setup_git_directory();
-	trace_repo_setup(prefix);
-	setup_work_tree();
 	/* NEEDSWORK: once we no longer spawn anything, remove this */
 	setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);
 	setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);
diff --git a/git-legacy-difftool.perl b/contrib/examples/git-difftool.perl
similarity index 100%
rename from git-legacy-difftool.perl
rename to contrib/examples/git-difftool.perl
diff --git a/git.c b/git.c
index c58181e5ef..bd4d668a21 100644
--- a/git.c
+++ b/git.c
@@ -424,12 +424,7 @@ static struct cmd_struct commands[] = {
 	{ "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
 	{ "diff-index", cmd_diff_index, RUN_SETUP },
 	{ "diff-tree", cmd_diff_tree, RUN_SETUP },
-	/*
-	 * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in
-	 * builtin/difftool.c has been removed, this entry should be changed to
-	 * RUN_SETUP | NEED_WORK_TREE
-	 */
-	{ "difftool", cmd_difftool },
+	{ "difftool", cmd_difftool, RUN_SETUP | NEED_WORK_TREE },
 	{ "fast-export", cmd_fast_export, RUN_SETUP },
 	{ "fetch", cmd_fetch, RUN_SETUP },
 	{ "fetch-pack", cmd_fetch_pack, RUN_SETUP },
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index e94910c563..aa0ef02597 100755
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
@@ -23,10 +23,8 @@ prompt_given ()
 	test "$prompt" = "Launch 'test-tool' [Y/n]? branch"
 }
 
-# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.
-
 # Create a file on master and change it on branch
-test_expect_success PERL 'setup' '
+test_expect_success 'setup' '
 	echo master >file &&
 	git add file &&
 	git commit -m "added file" &&
@@ -38,7 +36,7 @@ test_expect_success PERL 'setup' '
 '
 
 # Configure a custom difftool.<tool>.cmd and use it
-test_expect_success PERL 'custom commands' '
+test_expect_success 'custom commands' '
 	difftool_test_setup &&
 	test_config difftool.test-tool.cmd "cat \"\$REMOTE\"" &&
 	echo master >expect &&
@@ -51,21 +49,21 @@ test_expect_success PERL 'custom commands' '
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'custom tool commands override built-ins' '
+test_expect_success 'custom tool commands override built-ins' '
 	test_config difftool.vimdiff.cmd "cat \"\$REMOTE\"" &&
 	echo master >expect &&
 	git difftool --tool vimdiff --no-prompt branch >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool ignores bad --tool values' '
+test_expect_success 'difftool ignores bad --tool values' '
 	: >expect &&
 	test_must_fail \
 		git difftool --no-prompt --tool=bad-tool branch >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool forwards arguments to diff' '
+test_expect_success 'difftool forwards arguments to diff' '
 	difftool_test_setup &&
 	>for-diff &&
 	git add for-diff &&
@@ -78,40 +76,40 @@ test_expect_success PERL 'difftool forwards arguments to diff' '
 	rm for-diff
 '
 
-test_expect_success PERL 'difftool ignores exit code' '
+test_expect_success 'difftool ignores exit code' '
 	test_config difftool.error.cmd false &&
 	git difftool -y -t error branch
 '
 
-test_expect_success PERL 'difftool forwards exit code with --trust-exit-code' '
+test_expect_success 'difftool forwards exit code with --trust-exit-code' '
 	test_config difftool.error.cmd false &&
 	test_must_fail git difftool -y --trust-exit-code -t error branch
 '
 
-test_expect_success PERL 'difftool forwards exit code with --trust-exit-code for built-ins' '
+test_expect_success 'difftool forwards exit code with --trust-exit-code for built-ins' '
 	test_config difftool.vimdiff.path false &&
 	test_must_fail git difftool -y --trust-exit-code -t vimdiff branch
 '
 
-test_expect_success PERL 'difftool honors difftool.trustExitCode = true' '
+test_expect_success 'difftool honors difftool.trustExitCode = true' '
 	test_config difftool.error.cmd false &&
 	test_config difftool.trustExitCode true &&
 	test_must_fail git difftool -y -t error branch
 '
 
-test_expect_success PERL 'difftool honors difftool.trustExitCode = false' '
+test_expect_success 'difftool honors difftool.trustExitCode = false' '
 	test_config difftool.error.cmd false &&
 	test_config difftool.trustExitCode false &&
 	git difftool -y -t error branch
 '
 
-test_expect_success PERL 'difftool ignores exit code with --no-trust-exit-code' '
+test_expect_success 'difftool ignores exit code with --no-trust-exit-code' '
 	test_config difftool.error.cmd false &&
 	test_config difftool.trustExitCode true &&
 	git difftool -y --no-trust-exit-code -t error branch
 '
 
-test_expect_success PERL 'difftool stops on error with --trust-exit-code' '
+test_expect_success 'difftool stops on error with --trust-exit-code' '
 	test_when_finished "rm -f for-diff .git/fail-right-file" &&
 	test_when_finished "git reset -- for-diff" &&
 	write_script .git/fail-right-file <<-\EOF &&
@@ -126,13 +124,13 @@ test_expect_success PERL 'difftool stops on error with --trust-exit-code' '
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool honors exit status if command not found' '
+test_expect_success 'difftool honors exit status if command not found' '
 	test_config difftool.nonexistent.cmd i-dont-exist &&
 	test_config difftool.trustExitCode false &&
 	test_must_fail git difftool -y -t nonexistent branch
 '
 
-test_expect_success PERL 'difftool honors --gui' '
+test_expect_success 'difftool honors --gui' '
 	difftool_test_setup &&
 	test_config merge.tool bogus-tool &&
 	test_config diff.tool bogus-tool &&
@@ -143,7 +141,7 @@ test_expect_success PERL 'difftool honors --gui' '
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool --gui last setting wins' '
+test_expect_success 'difftool --gui last setting wins' '
 	difftool_test_setup &&
 	: >expect &&
 	git difftool --no-prompt --gui --no-gui >actual &&
@@ -157,7 +155,7 @@ test_expect_success PERL 'difftool --gui last setting wins' '
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool --gui works without configured diff.guitool' '
+test_expect_success 'difftool --gui works without configured diff.guitool' '
 	difftool_test_setup &&
 	echo branch >expect &&
 	git difftool --no-prompt --gui branch >actual &&
@@ -165,7 +163,7 @@ test_expect_success PERL 'difftool --gui works without configured diff.guitool'
 '
 
 # Specify the diff tool using $GIT_DIFF_TOOL
-test_expect_success PERL 'GIT_DIFF_TOOL variable' '
+test_expect_success 'GIT_DIFF_TOOL variable' '
 	difftool_test_setup &&
 	git config --unset diff.tool &&
 	echo branch >expect &&
@@ -175,7 +173,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL variable' '
 
 # Test the $GIT_*_TOOL variables and ensure
 # that $GIT_DIFF_TOOL always wins unless --tool is specified
-test_expect_success PERL 'GIT_DIFF_TOOL overrides' '
+test_expect_success 'GIT_DIFF_TOOL overrides' '
 	difftool_test_setup &&
 	test_config diff.tool bogus-tool &&
 	test_config merge.tool bogus-tool &&
@@ -193,7 +191,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL overrides' '
 
 # Test that we don't have to pass --no-prompt to difftool
 # when $GIT_DIFFTOOL_NO_PROMPT is true
-test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' '
+test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' '
 	difftool_test_setup &&
 	echo branch >expect &&
 	GIT_DIFFTOOL_NO_PROMPT=true git difftool branch >actual &&
@@ -202,7 +200,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' '
 
 # git-difftool supports the difftool.prompt variable.
 # Test that GIT_DIFFTOOL_PROMPT can override difftool.prompt = false
-test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' '
+test_expect_success 'GIT_DIFFTOOL_PROMPT variable' '
 	difftool_test_setup &&
 	test_config difftool.prompt false &&
 	echo >input &&
@@ -212,7 +210,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' '
 '
 
 # Test that we don't have to pass --no-prompt when difftool.prompt is false
-test_expect_success PERL 'difftool.prompt config variable is false' '
+test_expect_success 'difftool.prompt config variable is false' '
 	difftool_test_setup &&
 	test_config difftool.prompt false &&
 	echo branch >expect &&
@@ -221,7 +219,7 @@ test_expect_success PERL 'difftool.prompt config variable is false' '
 '
 
 # Test that we don't have to pass --no-prompt when mergetool.prompt is false
-test_expect_success PERL 'difftool merge.prompt = false' '
+test_expect_success 'difftool merge.prompt = false' '
 	difftool_test_setup &&
 	test_might_fail git config --unset difftool.prompt &&
 	test_config mergetool.prompt false &&
@@ -231,7 +229,7 @@ test_expect_success PERL 'difftool merge.prompt = false' '
 '
 
 # Test that the -y flag can override difftool.prompt = true
-test_expect_success PERL 'difftool.prompt can overridden with -y' '
+test_expect_success 'difftool.prompt can overridden with -y' '
 	difftool_test_setup &&
 	test_config difftool.prompt true &&
 	echo branch >expect &&
@@ -240,7 +238,7 @@ test_expect_success PERL 'difftool.prompt can overridden with -y' '
 '
 
 # Test that the --prompt flag can override difftool.prompt = false
-test_expect_success PERL 'difftool.prompt can overridden with --prompt' '
+test_expect_success 'difftool.prompt can overridden with --prompt' '
 	difftool_test_setup &&
 	test_config difftool.prompt false &&
 	echo >input &&
@@ -250,7 +248,7 @@ test_expect_success PERL 'difftool.prompt can overridden with --prompt' '
 '
 
 # Test that the last flag passed on the command-line wins
-test_expect_success PERL 'difftool last flag wins' '
+test_expect_success 'difftool last flag wins' '
 	difftool_test_setup &&
 	echo branch >expect &&
 	git difftool --prompt --no-prompt branch >actual &&
@@ -263,7 +261,7 @@ test_expect_success PERL 'difftool last flag wins' '
 
 # git-difftool falls back to git-mergetool config variables
 # so test that behavior here
-test_expect_success PERL 'difftool + mergetool config variables' '
+test_expect_success 'difftool + mergetool config variables' '
 	test_config merge.tool test-tool &&
 	test_config mergetool.test-tool.cmd "cat \$LOCAL" &&
 	echo branch >expect &&
@@ -277,49 +275,49 @@ test_expect_success PERL 'difftool + mergetool config variables' '
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool.<tool>.path' '
+test_expect_success 'difftool.<tool>.path' '
 	test_config difftool.tkdiff.path echo &&
 	git difftool --tool=tkdiff --no-prompt branch >output &&
 	lines=$(grep file output | wc -l) &&
 	test "$lines" -eq 1
 '
 
-test_expect_success PERL 'difftool --extcmd=cat' '
+test_expect_success 'difftool --extcmd=cat' '
 	echo branch >expect &&
 	echo master >>expect &&
 	git difftool --no-prompt --extcmd=cat branch >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool --extcmd cat' '
+test_expect_success 'difftool --extcmd cat' '
 	echo branch >expect &&
 	echo master >>expect &&
 	git difftool --no-prompt --extcmd=cat branch >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool -x cat' '
+test_expect_success 'difftool -x cat' '
 	echo branch >expect &&
 	echo master >>expect &&
 	git difftool --no-prompt -x cat branch >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool --extcmd echo arg1' '
+test_expect_success 'difftool --extcmd echo arg1' '
 	echo file >expect &&
 	git difftool --no-prompt \
 		--extcmd sh\ -c\ \"echo\ \$1\" branch >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool --extcmd cat arg1' '
+test_expect_success 'difftool --extcmd cat arg1' '
 	echo master >expect &&
 	git difftool --no-prompt \
 		--extcmd sh\ -c\ \"cat\ \$1\" branch >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool --extcmd cat arg2' '
+test_expect_success 'difftool --extcmd cat arg2' '
 	echo branch >expect &&
 	git difftool --no-prompt \
 		--extcmd sh\ -c\ \"cat\ \$2\" branch >actual &&
@@ -327,7 +325,7 @@ test_expect_success PERL 'difftool --extcmd cat arg2' '
 '
 
 # Create a second file on master and a different version on branch
-test_expect_success PERL 'setup with 2 files different' '
+test_expect_success 'setup with 2 files different' '
 	echo m2 >file2 &&
 	git add file2 &&
 	git commit -m "added file2" &&
@@ -339,7 +337,7 @@ test_expect_success PERL 'setup with 2 files different' '
 	git checkout master
 '
 
-test_expect_success PERL 'say no to the first file' '
+test_expect_success 'say no to the first file' '
 	(echo n && echo) >input &&
 	git difftool -x cat branch <input >output &&
 	grep m2 output &&
@@ -348,7 +346,7 @@ test_expect_success PERL 'say no to the first file' '
 	! grep branch output
 '
 
-test_expect_success PERL 'say no to the second file' '
+test_expect_success 'say no to the second file' '
 	(echo && echo n) >input &&
 	git difftool -x cat branch <input >output &&
 	grep master output &&
@@ -357,7 +355,7 @@ test_expect_success PERL 'say no to the second file' '
 	! grep br2 output
 '
 
-test_expect_success PERL 'ending prompt input with EOF' '
+test_expect_success 'ending prompt input with EOF' '
 	git difftool -x cat branch </dev/null >output &&
 	! grep master output &&
 	! grep branch output &&
@@ -365,12 +363,12 @@ test_expect_success PERL 'ending prompt input with EOF' '
 	! grep br2 output
 '
 
-test_expect_success PERL 'difftool --tool-help' '
+test_expect_success 'difftool --tool-help' '
 	git difftool --tool-help >output &&
 	grep tool output
 '
 
-test_expect_success PERL 'setup change in subdirectory' '
+test_expect_success 'setup change in subdirectory' '
 	git checkout master &&
 	mkdir sub &&
 	echo master >sub/sub &&
@@ -384,11 +382,11 @@ test_expect_success PERL 'setup change in subdirectory' '
 '
 
 run_dir_diff_test () {
-	test_expect_success PERL "$1 --no-symlinks" "
+	test_expect_success "$1 --no-symlinks" "
 		symlinks=--no-symlinks &&
 		$2
 	"
-	test_expect_success PERL,SYMLINKS "$1 --symlinks" "
+	test_expect_success SYMLINKS "$1 --symlinks" "
 		symlinks=--symlinks &&
 		$2
 	"
@@ -510,7 +508,7 @@ do
 done >actual
 EOF
 
-test_expect_success PERL,SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' '
+test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' '
 	cat >expect <<-EOF &&
 	file
 	$PWD/file
@@ -547,7 +545,7 @@ write_script modify-file <<\EOF
 echo "new content" >file
 EOF
 
-test_expect_success PERL 'difftool --no-symlinks does not overwrite working tree file ' '
+test_expect_success 'difftool --no-symlinks does not overwrite working tree file ' '
 	echo "orig content" >file &&
 	git difftool --dir-diff --no-symlinks --extcmd "$PWD/modify-file" branch &&
 	echo "new content" >expect &&
@@ -560,7 +558,7 @@ echo "tmp content" >"$2/file" &&
 echo "$2" >tmpdir
 EOF
 
-test_expect_success PERL 'difftool --no-symlinks detects conflict ' '
+test_expect_success 'difftool --no-symlinks detects conflict ' '
 	(
 		TMPDIR=$TRASH_DIRECTORY &&
 		export TMPDIR &&
@@ -573,7 +571,7 @@ test_expect_success PERL 'difftool --no-symlinks detects conflict ' '
 	)
 '
 
-test_expect_success PERL 'difftool properly honors gitlink and core.worktree' '
+test_expect_success 'difftool properly honors gitlink and core.worktree' '
 	git submodule add ./. submod/ule &&
 	test_config -C submod/ule diff.tool checktrees &&
 	test_config -C submod/ule difftool.checktrees.cmd '\''
@@ -587,7 +585,7 @@ test_expect_success PERL 'difftool properly honors gitlink and core.worktree' '
 	)
 '
 
-test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' '
+test_expect_success SYMLINKS 'difftool --dir-diff symlinked directories' '
 	git init dirlinks &&
 	(
 		cd dirlinks &&
-- 
2.11.0.windows.3

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

* Re: [PATCH v5 0/3] Turn the difftool into a builtin
  2017-01-17 15:54       ` [PATCH v5 0/3] Turn the difftool into a builtin Johannes Schindelin
                           ` (2 preceding siblings ...)
  2017-01-17 15:55         ` [PATCH v5 3/3] Retire the scripted difftool Johannes Schindelin
@ 2017-01-17 21:31         ` Junio C Hamano
  2017-01-19 20:30         ` [PATCH v6 " Johannes Schindelin
  4 siblings, 0 replies; 86+ messages in thread
From: Junio C Hamano @ 2017-01-17 21:31 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

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

> - replaced the cross-validation with the Perl script by a patch that
>   retires the Perl script instead.

Yup.  That makes things a lot simpler.  While we try to be careful
during major rewrite, we usually do not go extra careful to leave
two competing implementations in-tree.

Will replace js/difftool-builtin topic.  Thanks.

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

* Re: [PATCH v5 3/3] Retire the scripted difftool
  2017-01-17 15:55         ` [PATCH v5 3/3] Retire the scripted difftool Johannes Schindelin
@ 2017-01-17 21:46           ` Junio C Hamano
  2017-01-18 12:33             ` Johannes Schindelin
  0 siblings, 1 reply; 86+ messages in thread
From: Junio C Hamano @ 2017-01-17 21:46 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

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

> It served its purpose, but now we have a builtin difftool. Time for the
> Perl script to enjoy Florida.
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---

The endgame makes a lot of sense.  Both in the cover letter and in
the previous patch you talk about having both in the released
version, so do you want this step to proceed slower than the other
two?  I.e. merge all three to 'next' but graduate only the first two
to 'master' and after a while make this last step graduate?


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

* Re: [PATCH v5 3/3] Retire the scripted difftool
  2017-01-17 21:46           ` Junio C Hamano
@ 2017-01-18 12:33             ` Johannes Schindelin
  2017-01-18 19:15               ` Junio C Hamano
  0 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2017-01-18 12:33 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

Hi Junio,

On Tue, 17 Jan 2017, Junio C Hamano wrote:

> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> 
> > It served its purpose, but now we have a builtin difftool. Time for the
> > Perl script to enjoy Florida.
> >
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> 
> The endgame makes a lot of sense.  Both in the cover letter and in
> the previous patch you talk about having both in the released
> version, so do you want this step to proceed slower than the other
> two?

I did proceed that slowly. Already three Git for Windows versions have
been released with both.

But I submitted this iteration with this patch, so my intent is clearly to
retire the Perl script.

Ciao,
Johannes

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

* Re: [PATCH v5 3/3] Retire the scripted difftool
  2017-01-18 12:33             ` Johannes Schindelin
@ 2017-01-18 19:15               ` Junio C Hamano
  2017-01-19 16:30                 ` Johannes Schindelin
  0 siblings, 1 reply; 86+ messages in thread
From: Junio C Hamano @ 2017-01-18 19:15 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

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

> Hi Junio,
>
> On Tue, 17 Jan 2017, Junio C Hamano wrote:
>
>> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
>> 
>> > It served its purpose, but now we have a builtin difftool. Time for the
>> > Perl script to enjoy Florida.
>> >
>> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
>> > ---
>> 
>> The endgame makes a lot of sense.  Both in the cover letter and in
>> the previous patch you talk about having both in the released
>> version, so do you want this step to proceed slower than the other
>> two?
>
> I did proceed that slowly. Already three Git for Windows versions have
> been released with both.
>
> But I submitted this iteration with this patch, so my intent is clearly to
> retire the Perl script.

Ok, I was mostly reacting to 2/3 while I am reading it:

    The reason: this new, experimental, builtin difftool will be shipped as
    part of Git for Windows v2.11.0, to allow for easier large-scale
    testing, but of course as an opt-in feature.

as there is no longer an opportunity to participate in this opt-in
testing, unless 3/3 is special cased and delayed.



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

* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin
  2016-12-06 18:35                                                         ` Jeff King
@ 2017-01-18 22:38                                                           ` Brandon Williams
  0 siblings, 0 replies; 86+ messages in thread
From: Brandon Williams @ 2017-01-18 22:38 UTC (permalink / raw)
  To: Jeff King
  Cc: Stefan Beller, Johannes Schindelin, Junio C Hamano,
	git@vger.kernel.org, David Aguilar, Dennis Kaarsemaker

On 12/06, Jeff King wrote:
> On Tue, Dec 06, 2016 at 10:22:21AM -0800, Stefan Beller wrote:
> 
> > >> Maybe even go a step further and say that the config code needs a context
> > >> "object".
> > >
> > > If I were writing git from scratch, I'd consider making a "struct
> > > repository" object. I'm not sure how painful it would be to retro-fit it
> > > at this point.
> > 
> > Would it be possible to introduce "the repo" struct similar to "the index"
> > in cache.h?
> > 
> > From a submodule perspective I would very much welcome this
> > object oriented approach to repositories.
> 
> I think it may be more complicated, because there's some implicit global
> state in "the repo", like where files are relative to our cwd. All of
> those low-level functions would have to start caring about which repo
> we're talking about so they can prefix the appropriate working tree
> path, etc.
> 
> For some operations that would be fine, but there are things that would
> subtly fail for submodules. I'm thinking we'd end up with some code
> state like:
> 
>   /* finding a repo does not modify global state; good */
>   struct repository *repo = repo_discover(".");
> 
>   /* obvious repo-level operations like looking up refs can be done with
>    * a repository object; good */
>   repo_for_each_ref(repo, callback, NULL);
> 
>   /*
>    * "enter" the repo so that we are at the top-level of the working
>    * tree, etc. After this you can actually look at the index without
>    * things breaking.
>    */
>   repo_enter(repo);
> 
> That would be enough to implement a lot of submodule-level stuff, but it
> would break pretty subtly as soon as you asked the submodule about its
> working tree. The solution is to make everything that accesses the
> working tree aware of the idea of a working tree root besides the cwd.
> But that's a pretty invasive change.
> 
> -Peff

Some other challenges would be how to address people setting environment
variables like GIT_DIR that indicate the location of a repositories git
directory, which wouldn't work if you have multiple repos open.

I do agree that having a repo object of some sort would aid in
simplifying submodule operations but may require too many invasive
changes to basic low-level functions.

-- 
Brandon Williams

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

* Re: [PATCH v5 3/3] Retire the scripted difftool
  2017-01-18 19:15               ` Junio C Hamano
@ 2017-01-19 16:30                 ` Johannes Schindelin
  2017-01-19 17:56                   ` Junio C Hamano
  0 siblings, 1 reply; 86+ messages in thread
From: Johannes Schindelin @ 2017-01-19 16:30 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

Hi Junio,

On Wed, 18 Jan 2017, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > Hi Junio,
> >
> > On Tue, 17 Jan 2017, Junio C Hamano wrote:
> >
> >> Johannes Schindelin <johannes.schindelin@gmx.de> writes:
> >> 
> >> > It served its purpose, but now we have a builtin difftool. Time for the
> >> > Perl script to enjoy Florida.
> >> >
> >> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> >> > ---
> >> 
> >> The endgame makes a lot of sense.  Both in the cover letter and in
> >> the previous patch you talk about having both in the released
> >> version, so do you want this step to proceed slower than the other
> >> two?
> >
> > I did proceed that slowly. Already three Git for Windows versions have
> > been released with both.
> >
> > But I submitted this iteration with this patch, so my intent is clearly to
> > retire the Perl script.
> 
> Ok, I was mostly reacting to 2/3 while I am reading it:
> 
>     The reason: this new, experimental, builtin difftool will be shipped as
>     part of Git for Windows v2.11.0, to allow for easier large-scale
>     testing, but of course as an opt-in feature.
> 
> as there is no longer an opportunity to participate in this opt-in
> testing, unless 3/3 is special cased and delayed.

Yep, as Git for Windows v2.11.0 is yesteryear's news, it was probably
obvious to you that I simply failed to spot and fix that oversight.

Ciao,
Johannes

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

* Re: [PATCH v5 3/3] Retire the scripted difftool
  2017-01-19 16:30                 ` Johannes Schindelin
@ 2017-01-19 17:56                   ` Junio C Hamano
  2017-01-19 20:32                     ` Johannes Schindelin
  0 siblings, 1 reply; 86+ messages in thread
From: Junio C Hamano @ 2017-01-19 17:56 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

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

>> Ok, I was mostly reacting to 2/3 while I am reading it:
>> 
>>     The reason: this new, experimental, builtin difftool will be shipped as
>>     part of Git for Windows v2.11.0, to allow for easier large-scale
>>     testing, but of course as an opt-in feature.
>> 
>> as there is no longer an opportunity to participate in this opt-in
>> testing, unless 3/3 is special cased and delayed.
>
> Yep, as Git for Windows v2.11.0 is yesteryear's news, it was probably
> obvious to you that I simply failed to spot and fix that oversight.

OK, if you want to tweak log message of either 2/3 or 3/3 to
correct, there is still time, as they are still outside 'next'.  

I am hoping we can merge it and others to 'next' by the end of the
week, though.



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

* [PATCH v6 0/3] Turn the difftool into a builtin
  2017-01-17 15:54       ` [PATCH v5 0/3] Turn the difftool into a builtin Johannes Schindelin
                           ` (3 preceding siblings ...)
  2017-01-17 21:31         ` [PATCH v5 0/3] Turn the difftool into a builtin Junio C Hamano
@ 2017-01-19 20:30         ` Johannes Schindelin
  2017-01-19 20:30           ` [PATCH v6 1/3] difftool: add a skeleton for the upcoming builtin Johannes Schindelin
                             ` (2 more replies)
  4 siblings, 3 replies; 86+ messages in thread
From: Johannes Schindelin @ 2017-01-19 20:30 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

This patch series converts the difftool from a Perl script into a
builtin, for three reasons:

1. Perl is really not native on Windows. Not only is there a performance
   penalty to be paid just for running Perl scripts, we also have to deal
   with the fact that users may have different Perl installations, with
   different options, and some other Perl installation may decide to set
   PERL5LIB globally, wreaking havoc with Git for Windows' Perl (which we
   have to use because almost all other Perl distributions lack the
   Subversion bindings we need for `git svn`).

2. As the Perl script uses Unix-y paths that are not native to Windows,
   the Perl interpreter has to go through a POSIX emulation layer (the
   MSYS2 runtime). This means that paths have to be converted from
   Unix-y paths to Windows-y paths (and vice versa) whenever crossing
   the POSIX emulation barrier, leading to quite possibly surprising path
   translation errors.

3. Perl makes for a rather large reason that Git for Windows' installer
   weighs in with >30MB. While one Perl script less does not relieve us
   of that burden, it is one step in the right direction.

Changes since v5:

- reworded the commit message of 2/3 to account for the change in v4
  where we no longer keep both scripted and builtin difftool working
  (with the switch difftool.useBuiltin deciding which one is used).


Johannes Schindelin (3):
  difftool: add a skeleton for the upcoming builtin
  difftool: implement the functionality in the builtin
  Retire the scripted difftool

 Makefile                                           |   2 +-
 builtin.h                                          |   1 +
 builtin/difftool.c                                 | 692 +++++++++++++++++++++
 .../examples/git-difftool.perl                     |   0
 git.c                                              |   1 +
 t/t7800-difftool.sh                                |  92 +--
 6 files changed, 741 insertions(+), 47 deletions(-)
 create mode 100644 builtin/difftool.c
 rename git-difftool.perl => contrib/examples/git-difftool.perl (100%)


base-commit: ffac48d093d4b518a0cc0e8bf1b7cb53e0c3d7a2
Published-As: https://github.com/dscho/git/releases/tag/builtin-difftool-v6
Fetch-It-Via: git fetch https://github.com/dscho/git builtin-difftool-v6

-- 
2.11.0.windows.3


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

* [PATCH v6 1/3] difftool: add a skeleton for the upcoming builtin
  2017-01-19 20:30         ` [PATCH v6 " Johannes Schindelin
@ 2017-01-19 20:30           ` Johannes Schindelin
  2017-01-19 20:30           ` [PATCH v6 2/3] difftool: implement the functionality in the builtin Johannes Schindelin
  2017-01-19 20:30           ` [PATCH v6 3/3] Retire the scripted difftool Johannes Schindelin
  2 siblings, 0 replies; 86+ messages in thread
From: Johannes Schindelin @ 2017-01-19 20:30 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

This adds a builtin difftool that still falls back to the legacy Perl
version, which has been renamed to `legacy-difftool`.

The idea is that the new, experimental, builtin difftool immediately hands
off to the legacy difftool for now, unless the config variable
difftool.useBuiltin is set to true.

This feature flag will be used in the upcoming Git for Windows v2.11.0
release, to allow early testers to opt-in to use the builtin difftool and
flesh out any bugs.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitignore                                    |  1 +
 Makefile                                      |  3 +-
 builtin.h                                     |  1 +
 builtin/difftool.c                            | 63 +++++++++++++++++++++++++++
 git-difftool.perl => git-legacy-difftool.perl |  0
 git.c                                         |  6 +++
 t/t7800-difftool.sh                           |  2 +
 7 files changed, 75 insertions(+), 1 deletion(-)
 create mode 100644 builtin/difftool.c
 rename git-difftool.perl => git-legacy-difftool.perl (100%)

diff --git a/.gitignore b/.gitignore
index 6722f78f9a..5555ae025b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -76,6 +76,7 @@
 /git-init-db
 /git-interpret-trailers
 /git-instaweb
+/git-legacy-difftool
 /git-log
 /git-ls-files
 /git-ls-remote
diff --git a/Makefile b/Makefile
index d861bd9985..8cf5bef034 100644
--- a/Makefile
+++ b/Makefile
@@ -522,7 +522,7 @@ SCRIPT_LIB += git-sh-setup
 SCRIPT_LIB += git-sh-i18n
 
 SCRIPT_PERL += git-add--interactive.perl
-SCRIPT_PERL += git-difftool.perl
+SCRIPT_PERL += git-legacy-difftool.perl
 SCRIPT_PERL += git-archimport.perl
 SCRIPT_PERL += git-cvsexportcommit.perl
 SCRIPT_PERL += git-cvsimport.perl
@@ -883,6 +883,7 @@ BUILTIN_OBJS += builtin/diff-files.o
 BUILTIN_OBJS += builtin/diff-index.o
 BUILTIN_OBJS += builtin/diff-tree.o
 BUILTIN_OBJS += builtin/diff.o
+BUILTIN_OBJS += builtin/difftool.o
 BUILTIN_OBJS += builtin/fast-export.o
 BUILTIN_OBJS += builtin/fetch-pack.o
 BUILTIN_OBJS += builtin/fetch.o
diff --git a/builtin.h b/builtin.h
index b9122bc5f4..67f80519da 100644
--- a/builtin.h
+++ b/builtin.h
@@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_index(int argc, const char **argv, const char *prefix);
 extern int cmd_diff(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_difftool(int argc, const char **argv, const char *prefix);
 extern int cmd_fast_export(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);
diff --git a/builtin/difftool.c b/builtin/difftool.c
new file mode 100644
index 0000000000..53870bbaf7
--- /dev/null
+++ b/builtin/difftool.c
@@ -0,0 +1,63 @@
+/*
+ * "git difftool" builtin command
+ *
+ * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
+ * git-difftool--helper script.
+ *
+ * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.
+ * The GIT_DIFF* variables are exported for use by git-difftool--helper.
+ *
+ * Any arguments that are unknown to this script are forwarded to 'git diff'.
+ *
+ * Copyright (C) 2016 Johannes Schindelin
+ */
+#include "builtin.h"
+#include "run-command.h"
+#include "exec_cmd.h"
+
+/*
+ * NEEDSWORK: this function can go once the legacy-difftool Perl script is
+ * retired.
+ *
+ * We intentionally avoid reading the config directly here, to avoid messing up
+ * the GIT_* environment variables when we need to fall back to exec()ing the
+ * Perl script.
+ */
+static int use_builtin_difftool(void) {
+	struct child_process cp = CHILD_PROCESS_INIT;
+	struct strbuf out = STRBUF_INIT;
+	int ret;
+
+	argv_array_pushl(&cp.args,
+			 "config", "--bool", "difftool.usebuiltin", NULL);
+	cp.git_cmd = 1;
+	if (capture_command(&cp, &out, 6))
+		return 0;
+	strbuf_trim(&out);
+	ret = !strcmp("true", out.buf);
+	strbuf_release(&out);
+	return ret;
+}
+
+int cmd_difftool(int argc, const char **argv, const char *prefix)
+{
+	/*
+	 * NEEDSWORK: Once the builtin difftool has been tested enough
+	 * and git-legacy-difftool.perl is retired to contrib/, this preamble
+	 * can be removed.
+	 */
+	if (!use_builtin_difftool()) {
+		const char *path = mkpath("%s/git-legacy-difftool",
+					  git_exec_path());
+
+		if (sane_execvp(path, (char **)argv) < 0)
+			die_errno("could not exec %s", path);
+
+		return 0;
+	}
+	prefix = setup_git_directory();
+	trace_repo_setup(prefix);
+	setup_work_tree();
+
+	die("TODO");
+}
diff --git a/git-difftool.perl b/git-legacy-difftool.perl
similarity index 100%
rename from git-difftool.perl
rename to git-legacy-difftool.perl
diff --git a/git.c b/git.c
index bbaa949e9c..c58181e5ef 100644
--- a/git.c
+++ b/git.c
@@ -424,6 +424,12 @@ static struct cmd_struct commands[] = {
 	{ "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
 	{ "diff-index", cmd_diff_index, RUN_SETUP },
 	{ "diff-tree", cmd_diff_tree, RUN_SETUP },
+	/*
+	 * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in
+	 * builtin/difftool.c has been removed, this entry should be changed to
+	 * RUN_SETUP | NEED_WORK_TREE
+	 */
+	{ "difftool", cmd_difftool },
 	{ "fast-export", cmd_fast_export, RUN_SETUP },
 	{ "fetch", cmd_fetch, RUN_SETUP },
 	{ "fetch-pack", cmd_fetch_pack, RUN_SETUP },
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index 99d4123461..e94910c563 100755
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
@@ -23,6 +23,8 @@ prompt_given ()
 	test "$prompt" = "Launch 'test-tool' [Y/n]? branch"
 }
 
+# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.
+
 # Create a file on master and change it on branch
 test_expect_success PERL 'setup' '
 	echo master >file &&
-- 
2.11.0.windows.3



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

* [PATCH v6 2/3] difftool: implement the functionality in the builtin
  2017-01-19 20:30         ` [PATCH v6 " Johannes Schindelin
  2017-01-19 20:30           ` [PATCH v6 1/3] difftool: add a skeleton for the upcoming builtin Johannes Schindelin
@ 2017-01-19 20:30           ` Johannes Schindelin
  2017-01-19 20:30           ` [PATCH v6 3/3] Retire the scripted difftool Johannes Schindelin
  2 siblings, 0 replies; 86+ messages in thread
From: Johannes Schindelin @ 2017-01-19 20:30 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

This patch gives life to the skeleton added in the previous patch.

The motivation for converting the difftool is that Perl scripts are not at
all native on Windows, and that `git difftool` therefore is pretty slow on
that platform, when there is no good reason for it to be slow.

In addition, Perl does not really have access to Git's internals. That
means that any script will always have to jump through unnecessary
hoops, and it will often need to perform unnecessary work (e.g. when
reading the entire config every time `git config` is called to query a
single config value).

The current version of the builtin difftool does not, however, make full
use of the internals but instead chooses to spawn a couple of Git
processes, still, to make for an easier conversion. There remains a lot
of room for improvement, left later.

Note: to play it safe, the original difftool is still called unless the
config setting difftool.useBuiltin is set to true.

The reason: this new, experimental, builtin difftool was shipped as part
of Git for Windows v2.11.0, to allow for easier large-scale testing, but
of course as an opt-in feature.

The speedup is actually more noticable on Linux than on Windows: a quick
test shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s)
(real/user/sys) in a Linux VM, down from  (6.529s/3.112s/0.644s), while on
Windows, it is (36.064s/2.730s/7.194s), down from (47.637s/2.407s/6.863s).
The culprit is most likely the overhead incurred from *still* having to
shell out to mergetool-lib.sh and difftool--helper.sh.

Still, it is an improvement.

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

diff --git a/builtin/difftool.c b/builtin/difftool.c
index 53870bbaf7..2115e548a5 100644
--- a/builtin/difftool.c
+++ b/builtin/difftool.c
@@ -11,9 +11,610 @@
  *
  * Copyright (C) 2016 Johannes Schindelin
  */
+#include "cache.h"
 #include "builtin.h"
 #include "run-command.h"
 #include "exec_cmd.h"
+#include "parse-options.h"
+#include "argv-array.h"
+#include "strbuf.h"
+#include "lockfile.h"
+#include "dir.h"
+
+static char *diff_gui_tool;
+static int trust_exit_code;
+
+static const char *const builtin_difftool_usage[] = {
+	N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"),
+	NULL
+};
+
+static int difftool_config(const char *var, const char *value, void *cb)
+{
+	if (!strcmp(var, "diff.guitool")) {
+		diff_gui_tool = xstrdup(value);
+		return 0;
+	}
+
+	if (!strcmp(var, "difftool.trustexitcode")) {
+		trust_exit_code = git_config_bool(var, value);
+		return 0;
+	}
+
+	return git_default_config(var, value, cb);
+}
+
+static int print_tool_help(void)
+{
+	const char *argv[] = { "mergetool", "--tool-help=diff", NULL };
+	return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
+static int parse_index_info(char *p, int *mode1, int *mode2,
+			    struct object_id *oid1, struct object_id *oid2,
+			    char *status)
+{
+	if (*p != ':')
+		return error("expected ':', got '%c'", *p);
+	*mode1 = (int)strtol(p + 1, &p, 8);
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	*mode2 = (int)strtol(p + 1, &p, 8);
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	if (get_oid_hex(++p, oid1))
+		return error("expected object ID, got '%s'", p + 1);
+	p += GIT_SHA1_HEXSZ;
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	if (get_oid_hex(++p, oid2))
+		return error("expected object ID, got '%s'", p + 1);
+	p += GIT_SHA1_HEXSZ;
+	if (*p != ' ')
+		return error("expected ' ', got '%c'", *p);
+	*status = *++p;
+	if (!*status)
+		return error("missing status");
+	if (p[1] && !isdigit(p[1]))
+		return error("unexpected trailer: '%s'", p + 1);
+	return 0;
+}
+
+/*
+ * Remove any trailing slash from $workdir
+ * before starting to avoid double slashes in symlink targets.
+ */
+static void add_path(struct strbuf *buf, size_t base_len, const char *path)
+{
+	strbuf_setlen(buf, base_len);
+	if (buf->len && buf->buf[buf->len - 1] != '/')
+		strbuf_addch(buf, '/');
+	strbuf_addstr(buf, path);
+}
+
+/*
+ * Determine whether we can simply reuse the file in the worktree.
+ */
+static int use_wt_file(const char *workdir, const char *name,
+		       struct object_id *oid)
+{
+	struct strbuf buf = STRBUF_INIT;
+	struct stat st;
+	int use = 0;
+
+	strbuf_addstr(&buf, workdir);
+	add_path(&buf, buf.len, name);
+
+	if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) {
+		struct object_id wt_oid;
+		int fd = open(buf.buf, O_RDONLY);
+
+		if (fd >= 0 &&
+		    !index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {
+			if (is_null_oid(oid)) {
+				oidcpy(oid, &wt_oid);
+				use = 1;
+			} else if (!oidcmp(oid, &wt_oid))
+				use = 1;
+		}
+	}
+
+	strbuf_release(&buf);
+
+	return use;
+}
+
+struct working_tree_entry {
+	struct hashmap_entry entry;
+	char path[FLEX_ARRAY];
+};
+
+static int working_tree_entry_cmp(struct working_tree_entry *a,
+				  struct working_tree_entry *b, void *keydata)
+{
+	return strcmp(a->path, b->path);
+}
+
+/*
+ * The `left` and `right` entries hold paths for the symlinks hashmap,
+ * and a SHA-1 surrounded by brief text for submodules.
+ */
+struct pair_entry {
+	struct hashmap_entry entry;
+	char left[PATH_MAX], right[PATH_MAX];
+	const char path[FLEX_ARRAY];
+};
+
+static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata)
+{
+	return strcmp(a->path, b->path);
+}
+
+static void add_left_or_right(struct hashmap *map, const char *path,
+			      const char *content, int is_right)
+{
+	struct pair_entry *e, *existing;
+
+	FLEX_ALLOC_STR(e, path, path);
+	hashmap_entry_init(e, strhash(path));
+	existing = hashmap_get(map, e, NULL);
+	if (existing) {
+		free(e);
+		e = existing;
+	} else {
+		e->left[0] = e->right[0] = '\0';
+		hashmap_add(map, e);
+	}
+	strlcpy(is_right ? e->right : e->left, content, PATH_MAX);
+}
+
+struct path_entry {
+	struct hashmap_entry entry;
+	char path[FLEX_ARRAY];
+};
+
+static int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key)
+{
+	return strcmp(a->path, key ? key : b->path);
+}
+
+static void changed_files(struct hashmap *result, const char *index_path,
+			  const char *workdir)
+{
+	struct child_process update_index = CHILD_PROCESS_INIT;
+	struct child_process diff_files = CHILD_PROCESS_INIT;
+	struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT;
+	const char *git_dir = absolute_path(get_git_dir()), *env[] = {
+		NULL, NULL
+	};
+	FILE *fp;
+
+	strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path);
+	env[0] = index_env.buf;
+
+	argv_array_pushl(&update_index.args,
+			 "--git-dir", git_dir, "--work-tree", workdir,
+			 "update-index", "--really-refresh", "-q",
+			 "--unmerged", NULL);
+	update_index.no_stdin = 1;
+	update_index.no_stdout = 1;
+	update_index.no_stderr = 1;
+	update_index.git_cmd = 1;
+	update_index.use_shell = 0;
+	update_index.clean_on_exit = 1;
+	update_index.dir = workdir;
+	update_index.env = env;
+	/* Ignore any errors of update-index */
+	run_command(&update_index);
+
+	argv_array_pushl(&diff_files.args,
+			 "--git-dir", git_dir, "--work-tree", workdir,
+			 "diff-files", "--name-only", "-z", NULL);
+	diff_files.no_stdin = 1;
+	diff_files.git_cmd = 1;
+	diff_files.use_shell = 0;
+	diff_files.clean_on_exit = 1;
+	diff_files.out = -1;
+	diff_files.dir = workdir;
+	diff_files.env = env;
+	if (start_command(&diff_files))
+		die("could not obtain raw diff");
+	fp = xfdopen(diff_files.out, "r");
+	while (!strbuf_getline_nul(&buf, fp)) {
+		struct path_entry *entry;
+		FLEX_ALLOC_STR(entry, path, buf.buf);
+		hashmap_entry_init(entry, strhash(buf.buf));
+		hashmap_add(result, entry);
+	}
+	if (finish_command(&diff_files))
+		die("diff-files did not exit properly");
+	strbuf_release(&index_env);
+	strbuf_release(&buf);
+}
+
+static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)
+{
+	struct strbuf buf = STRBUF_INIT;
+	strbuf_addstr(&buf, tmpdir);
+	remove_dir_recursively(&buf, 0);
+	if (exit_code)
+		warning(_("failed: %d"), exit_code);
+	exit(exit_code);
+}
+
+static int ensure_leading_directories(char *path)
+{
+	switch (safe_create_leading_directories(path)) {
+		case SCLD_OK:
+		case SCLD_EXISTS:
+			return 0;
+		default:
+			return error(_("could not create leading directories "
+				       "of '%s'"), path);
+	}
+}
+
+static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
+			int argc, const char **argv)
+{
+	char tmpdir[PATH_MAX];
+	struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;
+	struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;
+	struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;
+	struct strbuf wtdir = STRBUF_INIT;
+	size_t ldir_len, rdir_len, wtdir_len;
+	struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1);
+	const char *workdir, *tmp;
+	int ret = 0, i;
+	FILE *fp;
+	struct hashmap working_tree_dups, submodules, symlinks2;
+	struct hashmap_iter iter;
+	struct pair_entry *entry;
+	enum object_type type;
+	unsigned long size;
+	struct index_state wtindex;
+	struct checkout lstate, rstate;
+	int rc, flags = RUN_GIT_CMD, err = 0;
+	struct child_process child = CHILD_PROCESS_INIT;
+	const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL };
+	struct hashmap wt_modified, tmp_modified;
+	int indices_loaded = 0;
+
+	workdir = get_git_work_tree();
+
+	/* Setup temp directories */
+	tmp = getenv("TMPDIR");
+	xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp");
+	if (!mkdtemp(tmpdir))
+		return error("could not create '%s'", tmpdir);
+	strbuf_addf(&ldir, "%s/left/", tmpdir);
+	strbuf_addf(&rdir, "%s/right/", tmpdir);
+	strbuf_addstr(&wtdir, workdir);
+	if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))
+		strbuf_addch(&wtdir, '/');
+	mkdir(ldir.buf, 0700);
+	mkdir(rdir.buf, 0700);
+
+	memset(&wtindex, 0, sizeof(wtindex));
+
+	memset(&lstate, 0, sizeof(lstate));
+	lstate.base_dir = ldir.buf;
+	lstate.base_dir_len = ldir.len;
+	lstate.force = 1;
+	memset(&rstate, 0, sizeof(rstate));
+	rstate.base_dir = rdir.buf;
+	rstate.base_dir_len = rdir.len;
+	rstate.force = 1;
+
+	ldir_len = ldir.len;
+	rdir_len = rdir.len;
+	wtdir_len = wtdir.len;
+
+	hashmap_init(&working_tree_dups,
+		     (hashmap_cmp_fn)working_tree_entry_cmp, 0);
+	hashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0);
+	hashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0);
+
+	child.no_stdin = 1;
+	child.git_cmd = 1;
+	child.use_shell = 0;
+	child.clean_on_exit = 1;
+	child.dir = prefix;
+	child.out = -1;
+	argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z",
+			 NULL);
+	for (i = 0; i < argc; i++)
+		argv_array_push(&child.args, argv[i]);
+	if (start_command(&child))
+		die("could not obtain raw diff");
+	fp = xfdopen(child.out, "r");
+
+	/* Build index info for left and right sides of the diff */
+	i = 0;
+	while (!strbuf_getline_nul(&info, fp)) {
+		int lmode, rmode;
+		struct object_id loid, roid;
+		char status;
+		const char *src_path, *dst_path;
+		size_t src_path_len, dst_path_len;
+
+		if (starts_with(info.buf, "::"))
+			die(N_("combined diff formats('-c' and '--cc') are "
+			       "not supported in\n"
+			       "directory diff mode('-d' and '--dir-diff')."));
+
+		if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,
+				     &status))
+			break;
+		if (strbuf_getline_nul(&lpath, fp))
+			break;
+		src_path = lpath.buf;
+		src_path_len = lpath.len;
+
+		i++;
+		if (status != 'C' && status != 'R') {
+			dst_path = src_path;
+			dst_path_len = src_path_len;
+		} else {
+			if (strbuf_getline_nul(&rpath, fp))
+				break;
+			dst_path = rpath.buf;
+			dst_path_len = rpath.len;
+		}
+
+		if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) {
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "Subproject commit %s",
+				    oid_to_hex(&loid));
+			add_left_or_right(&submodules, src_path, buf.buf, 0);
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "Subproject commit %s",
+				    oid_to_hex(&roid));
+			if (!oidcmp(&loid, &roid))
+				strbuf_addstr(&buf, "-dirty");
+			add_left_or_right(&submodules, dst_path, buf.buf, 1);
+			continue;
+		}
+
+		if (S_ISLNK(lmode)) {
+			char *content = read_sha1_file(loid.hash, &type, &size);
+			add_left_or_right(&symlinks2, src_path, content, 0);
+			free(content);
+		}
+
+		if (S_ISLNK(rmode)) {
+			char *content = read_sha1_file(roid.hash, &type, &size);
+			add_left_or_right(&symlinks2, dst_path, content, 1);
+			free(content);
+		}
+
+		if (lmode && status != 'C') {
+			ce->ce_mode = lmode;
+			oidcpy(&ce->oid, &loid);
+			strcpy(ce->name, src_path);
+			ce->ce_namelen = src_path_len;
+			if (checkout_entry(ce, &lstate, NULL))
+				return error("could not write '%s'", src_path);
+		}
+
+		if (rmode) {
+			struct working_tree_entry *entry;
+
+			/* Avoid duplicate working_tree entries */
+			FLEX_ALLOC_STR(entry, path, dst_path);
+			hashmap_entry_init(entry, strhash(dst_path));
+			if (hashmap_get(&working_tree_dups, entry, NULL)) {
+				free(entry);
+				continue;
+			}
+			hashmap_add(&working_tree_dups, entry);
+
+			if (!use_wt_file(workdir, dst_path, &roid)) {
+				ce->ce_mode = rmode;
+				oidcpy(&ce->oid, &roid);
+				strcpy(ce->name, dst_path);
+				ce->ce_namelen = dst_path_len;
+				if (checkout_entry(ce, &rstate, NULL))
+					return error("could not write '%s'",
+						     dst_path);
+			} else if (!is_null_oid(&roid)) {
+				/*
+				 * Changes in the working tree need special
+				 * treatment since they are not part of the
+				 * index.
+				 */
+				struct cache_entry *ce2 =
+					make_cache_entry(rmode, roid.hash,
+							 dst_path, 0, 0);
+
+				add_index_entry(&wtindex, ce2,
+						ADD_CACHE_JUST_APPEND);
+
+				add_path(&rdir, rdir_len, dst_path);
+				if (ensure_leading_directories(rdir.buf))
+					return error("could not create "
+						     "directory for '%s'",
+						     dst_path);
+				add_path(&wtdir, wtdir_len, dst_path);
+				if (symlinks) {
+					if (symlink(wtdir.buf, rdir.buf)) {
+						ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf);
+						goto finish;
+					}
+				} else {
+					struct stat st;
+					if (stat(wtdir.buf, &st))
+						st.st_mode = 0644;
+					if (copy_file(rdir.buf, wtdir.buf,
+						      st.st_mode)) {
+						ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf);
+						goto finish;
+					}
+				}
+			}
+		}
+	}
+
+	if (finish_command(&child)) {
+		ret = error("error occurred running diff --raw");
+		goto finish;
+	}
+
+	if (!i)
+		return 0;
+
+	/*
+	 * Changes to submodules require special treatment.This loop writes a
+	 * temporary file to both the left and right directories to show the
+	 * change in the recorded SHA1 for the submodule.
+	 */
+	hashmap_iter_init(&submodules, &iter);
+	while ((entry = hashmap_iter_next(&iter))) {
+		if (*entry->left) {
+			add_path(&ldir, ldir_len, entry->path);
+			ensure_leading_directories(ldir.buf);
+			write_file(ldir.buf, "%s", entry->left);
+		}
+		if (*entry->right) {
+			add_path(&rdir, rdir_len, entry->path);
+			ensure_leading_directories(rdir.buf);
+			write_file(rdir.buf, "%s", entry->right);
+		}
+	}
+
+	/*
+	 * Symbolic links require special treatment.The standard "git diff"
+	 * shows only the link itself, not the contents of the link target.
+	 * This loop replicates that behavior.
+	 */
+	hashmap_iter_init(&symlinks2, &iter);
+	while ((entry = hashmap_iter_next(&iter))) {
+		if (*entry->left) {
+			add_path(&ldir, ldir_len, entry->path);
+			ensure_leading_directories(ldir.buf);
+			write_file(ldir.buf, "%s", entry->left);
+		}
+		if (*entry->right) {
+			add_path(&rdir, rdir_len, entry->path);
+			ensure_leading_directories(rdir.buf);
+			write_file(rdir.buf, "%s", entry->right);
+		}
+	}
+
+	strbuf_release(&buf);
+
+	strbuf_setlen(&ldir, ldir_len);
+	helper_argv[1] = ldir.buf;
+	strbuf_setlen(&rdir, rdir_len);
+	helper_argv[2] = rdir.buf;
+
+	if (extcmd) {
+		helper_argv[0] = extcmd;
+		flags = 0;
+	} else
+		setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1);
+	rc = run_command_v_opt(helper_argv, flags);
+
+	/*
+	 * If the diff includes working copy files and those
+	 * files were modified during the diff, then the changes
+	 * should be copied back to the working tree.
+	 * Do not copy back files when symlinks are used and the
+	 * external tool did not replace the original link with a file.
+	 *
+	 * These hashes are loaded lazily since they aren't needed
+	 * in the common case of --symlinks and the difftool updating
+	 * files through the symlink.
+	 */
+	hashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp,
+		     wtindex.cache_nr);
+	hashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp,
+		     wtindex.cache_nr);
+
+	for (i = 0; i < wtindex.cache_nr; i++) {
+		struct hashmap_entry dummy;
+		const char *name = wtindex.cache[i]->name;
+		struct stat st;
+
+		add_path(&rdir, rdir_len, name);
+		if (lstat(rdir.buf, &st))
+			continue;
+
+		if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode))
+			continue;
+
+		if (!indices_loaded) {
+			static struct lock_file lock;
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "%s/wtindex", tmpdir);
+			if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
+			    write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {
+				ret = error("could not write %s", buf.buf);
+				rollback_lock_file(&lock);
+				goto finish;
+			}
+			changed_files(&wt_modified, buf.buf, workdir);
+			strbuf_setlen(&rdir, rdir_len);
+			changed_files(&tmp_modified, buf.buf, rdir.buf);
+			add_path(&rdir, rdir_len, name);
+			indices_loaded = 1;
+		}
+
+		hashmap_entry_init(&dummy, strhash(name));
+		if (hashmap_get(&tmp_modified, &dummy, name)) {
+			add_path(&wtdir, wtdir_len, name);
+			if (hashmap_get(&wt_modified, &dummy, name)) {
+				warning(_("both files modified: '%s' and '%s'."),
+					wtdir.buf, rdir.buf);
+				warning(_("working tree file has been left."));
+				warning("");
+				err = 1;
+			} else if (unlink(wtdir.buf) ||
+				   copy_file(wtdir.buf, rdir.buf, st.st_mode))
+				warning_errno(_("could not copy '%s' to '%s'"),
+					      rdir.buf, wtdir.buf);
+		}
+	}
+
+	if (err) {
+		warning(_("temporary files exist in '%s'."), tmpdir);
+		warning(_("you may want to cleanup or recover these."));
+		exit(1);
+	} else
+		exit_cleanup(tmpdir, rc);
+
+finish:
+	free(ce);
+	strbuf_release(&ldir);
+	strbuf_release(&rdir);
+	strbuf_release(&wtdir);
+	strbuf_release(&buf);
+
+	return ret;
+}
+
+static int run_file_diff(int prompt, const char *prefix,
+			 int argc, const char **argv)
+{
+	struct argv_array args = ARGV_ARRAY_INIT;
+	const char *env[] = {
+		"GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL,
+		NULL
+	};
+	int ret = 0, i;
+
+	if (prompt > 0)
+		env[2] = "GIT_DIFFTOOL_PROMPT=true";
+	else if (!prompt)
+		env[2] = "GIT_DIFFTOOL_NO_PROMPT=true";
+
+
+	argv_array_push(&args, "diff");
+	for (i = 0; i < argc; i++)
+		argv_array_push(&args, argv[i]);
+	ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env);
+	exit(ret);
+}
 
 /*
  * NEEDSWORK: this function can go once the legacy-difftool Perl script is
@@ -41,6 +642,35 @@ static int use_builtin_difftool(void) {
 
 int cmd_difftool(int argc, const char **argv, const char *prefix)
 {
+	int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,
+	    tool_help = 0;
+	static char *difftool_cmd = NULL, *extcmd = NULL;
+	struct option builtin_difftool_options[] = {
+		OPT_BOOL('g', "gui", &use_gui_tool,
+			 N_("use `diff.guitool` instead of `diff.tool`")),
+		OPT_BOOL('d', "dir-diff", &dir_diff,
+			 N_("perform a full-directory diff")),
+		{ OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL,
+			N_("do not prompt before launching a diff tool"),
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
+		{ OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL,
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
+			NULL, 1 },
+		OPT_BOOL(0, "symlinks", &symlinks,
+			 N_("use symlinks in dir-diff mode")),
+		OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"),
+			   N_("use the specified diff tool")),
+		OPT_BOOL(0, "tool-help", &tool_help,
+			 N_("print a list of diff tools that may be used with "
+			    "`--tool`")),
+		OPT_BOOL(0, "trust-exit-code", &trust_exit_code,
+			 N_("make 'git-difftool' exit when an invoked diff "
+			    "tool returns a non - zero exit code")),
+		OPT_STRING('x', "extcmd", &extcmd, N_("<command>"),
+			   N_("specify a custom command for viewing diffs")),
+		OPT_END()
+	};
+
 	/*
 	 * NEEDSWORK: Once the builtin difftool has been tested enough
 	 * and git-legacy-difftool.perl is retired to contrib/, this preamble
@@ -58,6 +688,46 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)
 	prefix = setup_git_directory();
 	trace_repo_setup(prefix);
 	setup_work_tree();
+	/* NEEDSWORK: once we no longer spawn anything, remove this */
+	setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);
+	setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);
+
+	git_config(difftool_config, NULL);
+	symlinks = has_symlinks;
+
+	argc = parse_options(argc, argv, prefix, builtin_difftool_options,
+			     builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |
+			     PARSE_OPT_KEEP_DASHDASH);
 
-	die("TODO");
+	if (tool_help)
+		return print_tool_help();
+
+	if (use_gui_tool && diff_gui_tool && *diff_gui_tool)
+		setenv("GIT_DIFF_TOOL", diff_gui_tool, 1);
+	else if (difftool_cmd) {
+		if (*difftool_cmd)
+			setenv("GIT_DIFF_TOOL", difftool_cmd, 1);
+		else
+			die(_("no <tool> given for --tool=<tool>"));
+	}
+
+	if (extcmd) {
+		if (*extcmd)
+			setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1);
+		else
+			die(_("no <cmd> given for --extcmd=<cmd>"));
+	}
+
+	setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE",
+	       trust_exit_code ? "true" : "false", 1);
+
+	/*
+	 * In directory diff mode, 'git-difftool--helper' is called once
+	 * to compare the a / b directories. In file diff mode, 'git diff'
+	 * will invoke a separate instance of 'git-difftool--helper' for
+	 * each file that changed.
+	 */
+	if (dir_diff)
+		return run_dir_diff(extcmd, symlinks, prefix, argc, argv);
+	return run_file_diff(prompt, prefix, argc, argv);
 }
-- 
2.11.0.windows.3



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

* [PATCH v6 3/3] Retire the scripted difftool
  2017-01-19 20:30         ` [PATCH v6 " Johannes Schindelin
  2017-01-19 20:30           ` [PATCH v6 1/3] difftool: add a skeleton for the upcoming builtin Johannes Schindelin
  2017-01-19 20:30           ` [PATCH v6 2/3] difftool: implement the functionality in the builtin Johannes Schindelin
@ 2017-01-19 20:30           ` Johannes Schindelin
  2 siblings, 0 replies; 86+ messages in thread
From: Johannes Schindelin @ 2017-01-19 20:30 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

It served its purpose, but now we have a builtin difftool. Time for the
Perl script to enjoy Florida.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitignore                                         |  1 -
 Makefile                                           |  1 -
 builtin/difftool.c                                 | 41 ----------
 .../examples/git-difftool.perl                     |  0
 git.c                                              |  7 +-
 t/t7800-difftool.sh                                | 94 +++++++++++-----------
 6 files changed, 47 insertions(+), 97 deletions(-)
 rename git-legacy-difftool.perl => contrib/examples/git-difftool.perl (100%)

diff --git a/.gitignore b/.gitignore
index 5555ae025b..6722f78f9a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -76,7 +76,6 @@
 /git-init-db
 /git-interpret-trailers
 /git-instaweb
-/git-legacy-difftool
 /git-log
 /git-ls-files
 /git-ls-remote
diff --git a/Makefile b/Makefile
index 8cf5bef034..e9aa6ae57c 100644
--- a/Makefile
+++ b/Makefile
@@ -522,7 +522,6 @@ SCRIPT_LIB += git-sh-setup
 SCRIPT_LIB += git-sh-i18n
 
 SCRIPT_PERL += git-add--interactive.perl
-SCRIPT_PERL += git-legacy-difftool.perl
 SCRIPT_PERL += git-archimport.perl
 SCRIPT_PERL += git-cvsexportcommit.perl
 SCRIPT_PERL += git-cvsimport.perl
diff --git a/builtin/difftool.c b/builtin/difftool.c
index 2115e548a5..42ad9e804a 100644
--- a/builtin/difftool.c
+++ b/builtin/difftool.c
@@ -616,30 +616,6 @@ static int run_file_diff(int prompt, const char *prefix,
 	exit(ret);
 }
 
-/*
- * NEEDSWORK: this function can go once the legacy-difftool Perl script is
- * retired.
- *
- * We intentionally avoid reading the config directly here, to avoid messing up
- * the GIT_* environment variables when we need to fall back to exec()ing the
- * Perl script.
- */
-static int use_builtin_difftool(void) {
-	struct child_process cp = CHILD_PROCESS_INIT;
-	struct strbuf out = STRBUF_INIT;
-	int ret;
-
-	argv_array_pushl(&cp.args,
-			 "config", "--bool", "difftool.usebuiltin", NULL);
-	cp.git_cmd = 1;
-	if (capture_command(&cp, &out, 6))
-		return 0;
-	strbuf_trim(&out);
-	ret = !strcmp("true", out.buf);
-	strbuf_release(&out);
-	return ret;
-}
-
 int cmd_difftool(int argc, const char **argv, const char *prefix)
 {
 	int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,
@@ -671,23 +647,6 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 
-	/*
-	 * NEEDSWORK: Once the builtin difftool has been tested enough
-	 * and git-legacy-difftool.perl is retired to contrib/, this preamble
-	 * can be removed.
-	 */
-	if (!use_builtin_difftool()) {
-		const char *path = mkpath("%s/git-legacy-difftool",
-					  git_exec_path());
-
-		if (sane_execvp(path, (char **)argv) < 0)
-			die_errno("could not exec %s", path);
-
-		return 0;
-	}
-	prefix = setup_git_directory();
-	trace_repo_setup(prefix);
-	setup_work_tree();
 	/* NEEDSWORK: once we no longer spawn anything, remove this */
 	setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);
 	setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);
diff --git a/git-legacy-difftool.perl b/contrib/examples/git-difftool.perl
similarity index 100%
rename from git-legacy-difftool.perl
rename to contrib/examples/git-difftool.perl
diff --git a/git.c b/git.c
index c58181e5ef..bd4d668a21 100644
--- a/git.c
+++ b/git.c
@@ -424,12 +424,7 @@ static struct cmd_struct commands[] = {
 	{ "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
 	{ "diff-index", cmd_diff_index, RUN_SETUP },
 	{ "diff-tree", cmd_diff_tree, RUN_SETUP },
-	/*
-	 * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in
-	 * builtin/difftool.c has been removed, this entry should be changed to
-	 * RUN_SETUP | NEED_WORK_TREE
-	 */
-	{ "difftool", cmd_difftool },
+	{ "difftool", cmd_difftool, RUN_SETUP | NEED_WORK_TREE },
 	{ "fast-export", cmd_fast_export, RUN_SETUP },
 	{ "fetch", cmd_fetch, RUN_SETUP },
 	{ "fetch-pack", cmd_fetch_pack, RUN_SETUP },
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index e94910c563..aa0ef02597 100755
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
@@ -23,10 +23,8 @@ prompt_given ()
 	test "$prompt" = "Launch 'test-tool' [Y/n]? branch"
 }
 
-# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.
-
 # Create a file on master and change it on branch
-test_expect_success PERL 'setup' '
+test_expect_success 'setup' '
 	echo master >file &&
 	git add file &&
 	git commit -m "added file" &&
@@ -38,7 +36,7 @@ test_expect_success PERL 'setup' '
 '
 
 # Configure a custom difftool.<tool>.cmd and use it
-test_expect_success PERL 'custom commands' '
+test_expect_success 'custom commands' '
 	difftool_test_setup &&
 	test_config difftool.test-tool.cmd "cat \"\$REMOTE\"" &&
 	echo master >expect &&
@@ -51,21 +49,21 @@ test_expect_success PERL 'custom commands' '
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'custom tool commands override built-ins' '
+test_expect_success 'custom tool commands override built-ins' '
 	test_config difftool.vimdiff.cmd "cat \"\$REMOTE\"" &&
 	echo master >expect &&
 	git difftool --tool vimdiff --no-prompt branch >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool ignores bad --tool values' '
+test_expect_success 'difftool ignores bad --tool values' '
 	: >expect &&
 	test_must_fail \
 		git difftool --no-prompt --tool=bad-tool branch >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool forwards arguments to diff' '
+test_expect_success 'difftool forwards arguments to diff' '
 	difftool_test_setup &&
 	>for-diff &&
 	git add for-diff &&
@@ -78,40 +76,40 @@ test_expect_success PERL 'difftool forwards arguments to diff' '
 	rm for-diff
 '
 
-test_expect_success PERL 'difftool ignores exit code' '
+test_expect_success 'difftool ignores exit code' '
 	test_config difftool.error.cmd false &&
 	git difftool -y -t error branch
 '
 
-test_expect_success PERL 'difftool forwards exit code with --trust-exit-code' '
+test_expect_success 'difftool forwards exit code with --trust-exit-code' '
 	test_config difftool.error.cmd false &&
 	test_must_fail git difftool -y --trust-exit-code -t error branch
 '
 
-test_expect_success PERL 'difftool forwards exit code with --trust-exit-code for built-ins' '
+test_expect_success 'difftool forwards exit code with --trust-exit-code for built-ins' '
 	test_config difftool.vimdiff.path false &&
 	test_must_fail git difftool -y --trust-exit-code -t vimdiff branch
 '
 
-test_expect_success PERL 'difftool honors difftool.trustExitCode = true' '
+test_expect_success 'difftool honors difftool.trustExitCode = true' '
 	test_config difftool.error.cmd false &&
 	test_config difftool.trustExitCode true &&
 	test_must_fail git difftool -y -t error branch
 '
 
-test_expect_success PERL 'difftool honors difftool.trustExitCode = false' '
+test_expect_success 'difftool honors difftool.trustExitCode = false' '
 	test_config difftool.error.cmd false &&
 	test_config difftool.trustExitCode false &&
 	git difftool -y -t error branch
 '
 
-test_expect_success PERL 'difftool ignores exit code with --no-trust-exit-code' '
+test_expect_success 'difftool ignores exit code with --no-trust-exit-code' '
 	test_config difftool.error.cmd false &&
 	test_config difftool.trustExitCode true &&
 	git difftool -y --no-trust-exit-code -t error branch
 '
 
-test_expect_success PERL 'difftool stops on error with --trust-exit-code' '
+test_expect_success 'difftool stops on error with --trust-exit-code' '
 	test_when_finished "rm -f for-diff .git/fail-right-file" &&
 	test_when_finished "git reset -- for-diff" &&
 	write_script .git/fail-right-file <<-\EOF &&
@@ -126,13 +124,13 @@ test_expect_success PERL 'difftool stops on error with --trust-exit-code' '
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool honors exit status if command not found' '
+test_expect_success 'difftool honors exit status if command not found' '
 	test_config difftool.nonexistent.cmd i-dont-exist &&
 	test_config difftool.trustExitCode false &&
 	test_must_fail git difftool -y -t nonexistent branch
 '
 
-test_expect_success PERL 'difftool honors --gui' '
+test_expect_success 'difftool honors --gui' '
 	difftool_test_setup &&
 	test_config merge.tool bogus-tool &&
 	test_config diff.tool bogus-tool &&
@@ -143,7 +141,7 @@ test_expect_success PERL 'difftool honors --gui' '
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool --gui last setting wins' '
+test_expect_success 'difftool --gui last setting wins' '
 	difftool_test_setup &&
 	: >expect &&
 	git difftool --no-prompt --gui --no-gui >actual &&
@@ -157,7 +155,7 @@ test_expect_success PERL 'difftool --gui last setting wins' '
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool --gui works without configured diff.guitool' '
+test_expect_success 'difftool --gui works without configured diff.guitool' '
 	difftool_test_setup &&
 	echo branch >expect &&
 	git difftool --no-prompt --gui branch >actual &&
@@ -165,7 +163,7 @@ test_expect_success PERL 'difftool --gui works without configured diff.guitool'
 '
 
 # Specify the diff tool using $GIT_DIFF_TOOL
-test_expect_success PERL 'GIT_DIFF_TOOL variable' '
+test_expect_success 'GIT_DIFF_TOOL variable' '
 	difftool_test_setup &&
 	git config --unset diff.tool &&
 	echo branch >expect &&
@@ -175,7 +173,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL variable' '
 
 # Test the $GIT_*_TOOL variables and ensure
 # that $GIT_DIFF_TOOL always wins unless --tool is specified
-test_expect_success PERL 'GIT_DIFF_TOOL overrides' '
+test_expect_success 'GIT_DIFF_TOOL overrides' '
 	difftool_test_setup &&
 	test_config diff.tool bogus-tool &&
 	test_config merge.tool bogus-tool &&
@@ -193,7 +191,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL overrides' '
 
 # Test that we don't have to pass --no-prompt to difftool
 # when $GIT_DIFFTOOL_NO_PROMPT is true
-test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' '
+test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' '
 	difftool_test_setup &&
 	echo branch >expect &&
 	GIT_DIFFTOOL_NO_PROMPT=true git difftool branch >actual &&
@@ -202,7 +200,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' '
 
 # git-difftool supports the difftool.prompt variable.
 # Test that GIT_DIFFTOOL_PROMPT can override difftool.prompt = false
-test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' '
+test_expect_success 'GIT_DIFFTOOL_PROMPT variable' '
 	difftool_test_setup &&
 	test_config difftool.prompt false &&
 	echo >input &&
@@ -212,7 +210,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' '
 '
 
 # Test that we don't have to pass --no-prompt when difftool.prompt is false
-test_expect_success PERL 'difftool.prompt config variable is false' '
+test_expect_success 'difftool.prompt config variable is false' '
 	difftool_test_setup &&
 	test_config difftool.prompt false &&
 	echo branch >expect &&
@@ -221,7 +219,7 @@ test_expect_success PERL 'difftool.prompt config variable is false' '
 '
 
 # Test that we don't have to pass --no-prompt when mergetool.prompt is false
-test_expect_success PERL 'difftool merge.prompt = false' '
+test_expect_success 'difftool merge.prompt = false' '
 	difftool_test_setup &&
 	test_might_fail git config --unset difftool.prompt &&
 	test_config mergetool.prompt false &&
@@ -231,7 +229,7 @@ test_expect_success PERL 'difftool merge.prompt = false' '
 '
 
 # Test that the -y flag can override difftool.prompt = true
-test_expect_success PERL 'difftool.prompt can overridden with -y' '
+test_expect_success 'difftool.prompt can overridden with -y' '
 	difftool_test_setup &&
 	test_config difftool.prompt true &&
 	echo branch >expect &&
@@ -240,7 +238,7 @@ test_expect_success PERL 'difftool.prompt can overridden with -y' '
 '
 
 # Test that the --prompt flag can override difftool.prompt = false
-test_expect_success PERL 'difftool.prompt can overridden with --prompt' '
+test_expect_success 'difftool.prompt can overridden with --prompt' '
 	difftool_test_setup &&
 	test_config difftool.prompt false &&
 	echo >input &&
@@ -250,7 +248,7 @@ test_expect_success PERL 'difftool.prompt can overridden with --prompt' '
 '
 
 # Test that the last flag passed on the command-line wins
-test_expect_success PERL 'difftool last flag wins' '
+test_expect_success 'difftool last flag wins' '
 	difftool_test_setup &&
 	echo branch >expect &&
 	git difftool --prompt --no-prompt branch >actual &&
@@ -263,7 +261,7 @@ test_expect_success PERL 'difftool last flag wins' '
 
 # git-difftool falls back to git-mergetool config variables
 # so test that behavior here
-test_expect_success PERL 'difftool + mergetool config variables' '
+test_expect_success 'difftool + mergetool config variables' '
 	test_config merge.tool test-tool &&
 	test_config mergetool.test-tool.cmd "cat \$LOCAL" &&
 	echo branch >expect &&
@@ -277,49 +275,49 @@ test_expect_success PERL 'difftool + mergetool config variables' '
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool.<tool>.path' '
+test_expect_success 'difftool.<tool>.path' '
 	test_config difftool.tkdiff.path echo &&
 	git difftool --tool=tkdiff --no-prompt branch >output &&
 	lines=$(grep file output | wc -l) &&
 	test "$lines" -eq 1
 '
 
-test_expect_success PERL 'difftool --extcmd=cat' '
+test_expect_success 'difftool --extcmd=cat' '
 	echo branch >expect &&
 	echo master >>expect &&
 	git difftool --no-prompt --extcmd=cat branch >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool --extcmd cat' '
+test_expect_success 'difftool --extcmd cat' '
 	echo branch >expect &&
 	echo master >>expect &&
 	git difftool --no-prompt --extcmd=cat branch >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool -x cat' '
+test_expect_success 'difftool -x cat' '
 	echo branch >expect &&
 	echo master >>expect &&
 	git difftool --no-prompt -x cat branch >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool --extcmd echo arg1' '
+test_expect_success 'difftool --extcmd echo arg1' '
 	echo file >expect &&
 	git difftool --no-prompt \
 		--extcmd sh\ -c\ \"echo\ \$1\" branch >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool --extcmd cat arg1' '
+test_expect_success 'difftool --extcmd cat arg1' '
 	echo master >expect &&
 	git difftool --no-prompt \
 		--extcmd sh\ -c\ \"cat\ \$1\" branch >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success PERL 'difftool --extcmd cat arg2' '
+test_expect_success 'difftool --extcmd cat arg2' '
 	echo branch >expect &&
 	git difftool --no-prompt \
 		--extcmd sh\ -c\ \"cat\ \$2\" branch >actual &&
@@ -327,7 +325,7 @@ test_expect_success PERL 'difftool --extcmd cat arg2' '
 '
 
 # Create a second file on master and a different version on branch
-test_expect_success PERL 'setup with 2 files different' '
+test_expect_success 'setup with 2 files different' '
 	echo m2 >file2 &&
 	git add file2 &&
 	git commit -m "added file2" &&
@@ -339,7 +337,7 @@ test_expect_success PERL 'setup with 2 files different' '
 	git checkout master
 '
 
-test_expect_success PERL 'say no to the first file' '
+test_expect_success 'say no to the first file' '
 	(echo n && echo) >input &&
 	git difftool -x cat branch <input >output &&
 	grep m2 output &&
@@ -348,7 +346,7 @@ test_expect_success PERL 'say no to the first file' '
 	! grep branch output
 '
 
-test_expect_success PERL 'say no to the second file' '
+test_expect_success 'say no to the second file' '
 	(echo && echo n) >input &&
 	git difftool -x cat branch <input >output &&
 	grep master output &&
@@ -357,7 +355,7 @@ test_expect_success PERL 'say no to the second file' '
 	! grep br2 output
 '
 
-test_expect_success PERL 'ending prompt input with EOF' '
+test_expect_success 'ending prompt input with EOF' '
 	git difftool -x cat branch </dev/null >output &&
 	! grep master output &&
 	! grep branch output &&
@@ -365,12 +363,12 @@ test_expect_success PERL 'ending prompt input with EOF' '
 	! grep br2 output
 '
 
-test_expect_success PERL 'difftool --tool-help' '
+test_expect_success 'difftool --tool-help' '
 	git difftool --tool-help >output &&
 	grep tool output
 '
 
-test_expect_success PERL 'setup change in subdirectory' '
+test_expect_success 'setup change in subdirectory' '
 	git checkout master &&
 	mkdir sub &&
 	echo master >sub/sub &&
@@ -384,11 +382,11 @@ test_expect_success PERL 'setup change in subdirectory' '
 '
 
 run_dir_diff_test () {
-	test_expect_success PERL "$1 --no-symlinks" "
+	test_expect_success "$1 --no-symlinks" "
 		symlinks=--no-symlinks &&
 		$2
 	"
-	test_expect_success PERL,SYMLINKS "$1 --symlinks" "
+	test_expect_success SYMLINKS "$1 --symlinks" "
 		symlinks=--symlinks &&
 		$2
 	"
@@ -510,7 +508,7 @@ do
 done >actual
 EOF
 
-test_expect_success PERL,SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' '
+test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' '
 	cat >expect <<-EOF &&
 	file
 	$PWD/file
@@ -547,7 +545,7 @@ write_script modify-file <<\EOF
 echo "new content" >file
 EOF
 
-test_expect_success PERL 'difftool --no-symlinks does not overwrite working tree file ' '
+test_expect_success 'difftool --no-symlinks does not overwrite working tree file ' '
 	echo "orig content" >file &&
 	git difftool --dir-diff --no-symlinks --extcmd "$PWD/modify-file" branch &&
 	echo "new content" >expect &&
@@ -560,7 +558,7 @@ echo "tmp content" >"$2/file" &&
 echo "$2" >tmpdir
 EOF
 
-test_expect_success PERL 'difftool --no-symlinks detects conflict ' '
+test_expect_success 'difftool --no-symlinks detects conflict ' '
 	(
 		TMPDIR=$TRASH_DIRECTORY &&
 		export TMPDIR &&
@@ -573,7 +571,7 @@ test_expect_success PERL 'difftool --no-symlinks detects conflict ' '
 	)
 '
 
-test_expect_success PERL 'difftool properly honors gitlink and core.worktree' '
+test_expect_success 'difftool properly honors gitlink and core.worktree' '
 	git submodule add ./. submod/ule &&
 	test_config -C submod/ule diff.tool checktrees &&
 	test_config -C submod/ule difftool.checktrees.cmd '\''
@@ -587,7 +585,7 @@ test_expect_success PERL 'difftool properly honors gitlink and core.worktree' '
 	)
 '
 
-test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' '
+test_expect_success SYMLINKS 'difftool --dir-diff symlinked directories' '
 	git init dirlinks &&
 	(
 		cd dirlinks &&
-- 
2.11.0.windows.3

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

* Re: [PATCH v5 3/3] Retire the scripted difftool
  2017-01-19 17:56                   ` Junio C Hamano
@ 2017-01-19 20:32                     ` Johannes Schindelin
  0 siblings, 0 replies; 86+ messages in thread
From: Johannes Schindelin @ 2017-01-19 20:32 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra

Hi Junio,

On Thu, 19 Jan 2017, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > Yep, as Git for Windows v2.11.0 is yesteryear's news, it was probably
> > obvious to you that I simply failed to spot and fix that oversight.
> 
> OK, if you want to tweak log message of either 2/3 or 3/3 to correct,
> there is still time, as they are still outside 'next'.  

Sent out v6 with the commit message reworded.

Ciao,
Johannes

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

end of thread, other threads:[~2017-01-19 20:40 UTC | newest]

Thread overview: 86+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-11-22 17:01 [PATCH 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin
2016-11-22 17:01 ` [PATCH 1/2] difftool: add the builtin Johannes Schindelin
2016-11-23  8:08   ` David Aguilar
2016-11-23 11:34     ` Johannes Schindelin
2016-11-22 17:01 ` [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version Johannes Schindelin
2016-11-23 14:51   ` Dennis Kaarsemaker
2016-11-23 17:29     ` Johannes Schindelin
2016-11-23 17:40       ` Junio C Hamano
2016-11-23 18:18         ` Junio C Hamano
2016-11-23 19:55           ` Johannes Schindelin
2016-11-23 20:04             ` Junio C Hamano
2016-11-23 22:01       ` Johannes Schindelin
2016-11-23 22:03 ` [PATCH v2 0/1] Show Git Mailing List: a builtin difftool Johannes Schindelin
2016-11-23 22:03   ` [PATCH v2 1/1] difftool: add the builtin Johannes Schindelin
2016-11-23 22:25     ` Junio C Hamano
2016-11-23 22:30       ` Junio C Hamano
2016-11-24 10:38         ` Johannes Schindelin
2016-11-24 20:55   ` [PATCH v3 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin
2016-11-24 20:55     ` [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin Johannes Schindelin
2016-11-24 21:08       ` Jeff King
2016-11-24 21:56         ` Johannes Schindelin
2016-11-25  3:18           ` Jeff King
2016-11-25 11:05             ` Johannes Schindelin
2016-11-25 17:19               ` Jeff King
2016-11-25 17:41                 ` Johannes Schindelin
2016-11-25 17:47                   ` Jeff King
2016-11-26 12:22                     ` Johannes Schindelin
2016-11-26 16:19                       ` Jeff King
2016-11-26 13:01                         ` Johannes Schindelin
2016-11-27 16:50                           ` Jeff King
2016-11-28 17:06                             ` Junio C Hamano
2016-11-28 17:34                               ` Johannes Schindelin
2016-11-28 19:27                                 ` Junio C Hamano
2016-11-29 20:36                                   ` Johannes Schindelin
2016-11-29 20:49                                     ` Jeff King
2016-11-30 12:30                                       ` Johannes Schindelin
2016-11-30 12:35                                         ` Jeff King
2016-11-29 20:55                                     ` Junio C Hamano
2016-11-30 12:30                                       ` Johannes Schindelin
2016-12-01 23:33                                         ` Junio C Hamano
2016-12-05 10:36                                           ` Johannes Schindelin
2016-12-05 18:37                                             ` Junio C Hamano
2016-12-06 13:16                                               ` Johannes Schindelin
2016-12-06 13:36                                                 ` Jeff King
2016-12-06 14:48                                                   ` Johannes Schindelin
2016-12-06 15:09                                                     ` Jeff King
2016-12-06 18:22                                                       ` Stefan Beller
2016-12-06 18:35                                                         ` Jeff King
2017-01-18 22:38                                                           ` Brandon Williams
2016-11-30 16:02                                 ` Jakub Narębski
2016-11-30 18:39                                   ` Junio C Hamano
2016-11-24 20:55     ` [PATCH v3 2/2] difftool: implement the functionality in the builtin Johannes Schindelin
2016-11-25 21:24       ` Jakub Narębski
2016-11-27 11:10         ` Johannes Schindelin
2016-11-27 11:20           ` Jakub Narębski
2017-01-02 16:16     ` [PATCH v4 0/4] Show Git Mailing List: a builtin difftool Johannes Schindelin
2017-01-02 16:22       ` [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path() Johannes Schindelin
2017-01-03 20:11         ` Stefan Beller
2017-01-03 21:33           ` Johannes Schindelin
2017-01-04 18:09             ` Stefan Beller
2017-01-04  1:13           ` Jeff King
2017-01-09  1:25           ` Junio C Hamano
2017-01-09  6:00             ` Jeff King
2017-01-09  7:49             ` Johannes Schindelin
2017-01-09 19:21             ` Stefan Beller
2017-01-02 16:22       ` [PATCH v4 2/4] difftool: add a skeleton for the upcoming builtin Johannes Schindelin
2017-01-02 16:22       ` [PATCH v4 3/4] difftool: implement the functionality in the builtin Johannes Schindelin
2017-01-02 16:24       ` [PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now Johannes Schindelin
2017-01-09  1:38         ` Junio C Hamano
2017-01-09  7:56           ` Johannes Schindelin
2017-01-09  9:46             ` Junio C Hamano
2017-01-17 15:54       ` [PATCH v5 0/3] Turn the difftool into a builtin Johannes Schindelin
2017-01-17 15:54         ` [PATCH v5 1/3] difftool: add a skeleton for the upcoming builtin Johannes Schindelin
2017-01-17 15:55         ` [PATCH v5 2/3] difftool: implement the functionality in the builtin Johannes Schindelin
2017-01-17 15:55         ` [PATCH v5 3/3] Retire the scripted difftool Johannes Schindelin
2017-01-17 21:46           ` Junio C Hamano
2017-01-18 12:33             ` Johannes Schindelin
2017-01-18 19:15               ` Junio C Hamano
2017-01-19 16:30                 ` Johannes Schindelin
2017-01-19 17:56                   ` Junio C Hamano
2017-01-19 20:32                     ` Johannes Schindelin
2017-01-17 21:31         ` [PATCH v5 0/3] Turn the difftool into a builtin Junio C Hamano
2017-01-19 20:30         ` [PATCH v6 " Johannes Schindelin
2017-01-19 20:30           ` [PATCH v6 1/3] difftool: add a skeleton for the upcoming builtin Johannes Schindelin
2017-01-19 20:30           ` [PATCH v6 2/3] difftool: implement the functionality in the builtin Johannes Schindelin
2017-01-19 20:30           ` [PATCH v6 3/3] Retire the scripted difftool Johannes Schindelin

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).