{"thread":{"id":"44532","subject":"[PATCH 0/2] Show Git Mailing List: a builtin difftool","startedAt":"2016-11-22T17:01:42Z","lastAt":"2017-01-19T20:40:51Z","messageCount":86,"participants":["Johannes Schindelin","David Aguilar","Dennis Kaarsemaker","Junio C Hamano","Jeff King","Jakub Narębski","Stefan Beller","Brandon Williams"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"306309","messageId":"cover.1479834051.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":null,"subject":"[PATCH 0/2] Show Git Mailing List: a builtin difftool","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-22T17:01:03Z","receivedAt":"2016-11-22T17:01:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"I have been working on the builtin difftool for a little over a week,\nfor two reasons:\n\n1. Perl is really not native on Windows. Not only is there a performance\n   penalty to be paid just for running Perl scripts, we also have to deal\n   with the fact that users may have different Perl installations, with\n   different options, and some other Perl installation may decide to set\n   PERL5LIB globally, wreaking havoc with Git for Windows' Perl (which we\n   have to use because almost all other Perl distributions lack the\n   Subversion bindings we need for `git svn`).\n\n2. Perl makes for a rather large reason that Git for Windows' installer\n   weighs in with >30MB. While one Perl script less does not relieve us\n   of that burden, it is one step in the right direction.\n\nThis pair of patches serves two purposes: to ask for reviews, and to\nshow what I plan to release as part of Git for Windows v2.11.0 (which is\ndue this Thursday, if Git v2.11.0 is released tomorrow, as planned).\n\nThe second patch really only explains how I will make sure that the\nbuiltin difftool will only affect users who want to opt in to testing.\n\n\nJohannes Schindelin (2):\n  difftool: add the builtin\n  difftool: add a feature flag for the builtin vs scripted version\n\n .gitignore                 |   2 +\n Makefile                   |   1 +\n builtin.h                  |   1 +\n builtin/builtin-difftool.c | 680 +++++++++++++++++++++++++++++++++++++++++++++\n git-difftool.perl          |   7 +\n git.c                      |  21 ++\n 6 files changed, 712 insertions(+)\n create mode 100644 builtin/builtin-difftool.c\n\n\nbase-commit: 1310affe024fba407bff55dbe65cd6d670c8a32d\nPublished-As: https://github.com/dscho/git/releases/tag/builtin-difftool-v1\nFetch-It-Via: git fetch https://github.com/dscho/git builtin-difftool-v1\n\n-- \n2.10.1.583.g721a9e0\n\n"},{"id":"306310","messageId":"7aec571e1b0773ca80ea25453d2650af6a18e095.1479834051.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1479834051.git.johannes.schindelin@gmx.de","subject":"[PATCH 1/2] difftool: add the builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-22T17:01:23Z","receivedAt":"2016-11-22T17:01:46Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This adds a builtin difftool that represents a conversion of the current\nPerl script version of the difftool.\n\nThe motivation is that Perl scripts are not at all native on Windows,\nand that `git difftool` therefore is pretty slow on that platform, when\nthere is no good reason for it to be slow.\n\nIn addition, Perl does not really have access to Git's internals. That\nmeans that any script will always have to jump through unnecessary\nhoops.\n\nThe current version of the builtin difftool does not, however, make full\nuse of the internals but instead chooses to spawn a couple of Git\nprocesses, still, to make for an easier conversion. There remains a lot\nof room for improvement, left for a later date.\n\nNote: the original difftool is still called by `git difftool`. To get the\nnew, experimental version, call `git builtin-difftool`. The reason: this\nnew, experimental, builtin difftool will be shipped as part of Git for\nWindows v2.11.0, to allow for easier large-scale testing.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n .gitignore                 |   1 +\n Makefile                   |   1 +\n builtin.h                  |   1 +\n builtin/builtin-difftool.c | 680 +++++++++++++++++++++++++++++++++++++++++++++\n git.c                      |   1 +\n 5 files changed, 684 insertions(+)\n create mode 100644 builtin/builtin-difftool.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 05cb58a..4f54531 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -51,6 +51,7 @@\n /git-diff-tree\n /git-difftool\n /git-difftool--helper\n+/git-builtin-difftool\n /git-describe\n /git-fast-export\n /git-fast-import\ndiff --git a/Makefile b/Makefile\nindex f53fcc9..f764174 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -888,6 +888,7 @@ BUILTIN_OBJS += builtin/diff-files.o\n BUILTIN_OBJS += builtin/diff-index.o\n BUILTIN_OBJS += builtin/diff-tree.o\n BUILTIN_OBJS += builtin/diff.o\n+BUILTIN_OBJS += builtin/builtin-difftool.o\n BUILTIN_OBJS += builtin/fast-export.o\n BUILTIN_OBJS += builtin/fetch-pack.o\n BUILTIN_OBJS += builtin/fetch.o\ndiff --git a/builtin.h b/builtin.h\nindex b9122bc..409a61e 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix);\n extern int cmd_diff_index(int argc, const char **argv, const char *prefix);\n extern int cmd_diff(int argc, const char **argv, const char *prefix);\n extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);\n+extern int cmd_builtin_difftool(int argc, const char **argv, const char *prefix);\n extern int cmd_fast_export(int argc, const char **argv, const char *prefix);\n extern int cmd_fetch(int argc, const char **argv, const char *prefix);\n extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/builtin-difftool.c b/builtin/builtin-difftool.c\nnew file mode 100644\nindex 0000000..9feefcd\n--- /dev/null\n+++ b/builtin/builtin-difftool.c\n@@ -0,0 +1,680 @@\n+/*\n+ * \"git difftool\" builtin command\n+ *\n+ * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible\n+ * git-difftool--helper script.\n+ *\n+ * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.\n+ * The GIT_DIFF* variables are exported for use by git-difftool--helper.\n+ *\n+ * Any arguments that are unknown to this script are forwarded to 'git diff'.\n+ *\n+ * Copyright (C) 2016 Johannes Schindelin\n+ */\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"parse-options.h\"\n+#include \"run-command.h\"\n+#include \"argv-array.h\"\n+#include \"strbuf.h\"\n+#include \"lockfile.h\"\n+\n+static char *diff_gui_tool;\n+static int trust_exit_code;\n+\n+static const char * const builtin_difftool_usage[] = {\n+\tN_(\"git add [<options>] [--] <pathspec>...\"),\n+\tNULL\n+};\n+\n+static int difftool_config(const char *var, const char *value, void *cb)\n+{\n+\tif (!strcmp(var, \"diff.guitool\")) {\n+\t\tdiff_gui_tool = xstrdup(value);\n+\t\treturn 0;\n+\t}\n+\n+\tif (!strcmp(var, \"difftool.trustexitcode\")) {\n+\t\ttrust_exit_code = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n+\treturn git_default_config(var, value, cb);\n+}\n+\n+static int print_tool_help(void)\n+{\n+\tconst char *argv[] = { \"mergetool\", \"--tool-help=diff\", NULL };\n+\treturn run_command_v_opt(argv, RUN_GIT_CMD);\n+}\n+\n+static int parse_index_info(char *p, int *mode1, int *mode2,\n+\t\t\t    struct object_id *oid1, struct object_id *oid2,\n+\t\t\t    char *status)\n+{\n+\tif (*p != ':')\n+\t\treturn error(\"expected ':', got '%c'\", *p);\n+\t*mode1 = (int)strtol(p + 1, &p, 8);\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\t*mode2 = (int)strtol(p + 1, &p, 8);\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\tif (get_oid_hex(++p, oid1))\n+\t\treturn error(\"expected object ID, got '%s'\", p + 1);\n+\tp += GIT_SHA1_HEXSZ;\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\tif (get_oid_hex(++p, oid2))\n+\t\treturn error(\"expected object ID, got '%s'\", p + 1);\n+\tp += GIT_SHA1_HEXSZ;\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\t*status = *++p;\n+\tif (!status || p[1])\n+\t\treturn error(\"unexpected trailer: '%s'\", p);\n+\treturn 0;\n+}\n+\n+/*\n+ * Remove any trailing slash from $workdir\n+ * before starting to avoid double slashes in symlink targets.\n+ */\n+static void add_path(struct strbuf *buf, size_t base_len, const char *path)\n+{\n+\tstrbuf_setlen(buf, base_len);\n+\tif (buf->len && buf->buf[buf->len - 1] != '/')\n+\t\tstrbuf_addch(buf, '/');\n+\tstrbuf_addstr(buf, path);\n+}\n+\n+/*\n+ * Determine whether we can simply reuse the file in the worktree.\n+ */\n+static int use_wt_file(const char *workdir, const char *name,\n+\t\t       struct object_id *oid)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct stat st;\n+\tint use = 0;\n+\n+\tstrbuf_addstr(&buf, workdir);\n+\tadd_path(&buf, buf.len, name);\n+\n+\tif (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) {\n+\t\tstruct object_id wt_oid;\n+\t\tint fd = open(buf.buf, O_RDONLY);\n+\n+\t\tif (!index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {\n+\t\t\tif (is_null_oid(oid)) {\n+\t\t\t\toidcpy(oid, &wt_oid);\n+\t\t\t\tuse = 1;\n+\t\t\t} else if (!oidcmp(oid, &wt_oid))\n+\t\t\t\tuse = 1;\n+\t\t}\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn use;\n+}\n+\n+struct working_tree_entry {\n+\tstruct hashmap_entry entry;\n+\tchar path[FLEX_ARRAY];\n+};\n+\n+static int working_tree_entry_cmp(struct working_tree_entry *a,\n+\t\t\t\t  struct working_tree_entry *b, void *keydata)\n+{\n+\treturn strcmp(a->path, b->path);\n+}\n+\n+/*\n+ * The `left` and `right` entries hold paths for the symlinks hashmap,\n+ * and a SHA-1 surrounded by brief text for submodules.\n+ */\n+struct pair_entry {\n+\tstruct hashmap_entry entry;\n+\tchar left[PATH_MAX], right[PATH_MAX];\n+\tconst char path[FLEX_ARRAY];\n+};\n+\n+static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata)\n+{\n+\treturn strcmp(a->path, b->path);\n+}\n+\n+static void add_left_or_right(struct hashmap *map, const char *path,\n+\t\t\t      const char *content, int is_right)\n+{\n+\tstruct pair_entry *e, *existing;\n+\n+\tFLEX_ALLOC_STR(e, path, path);\n+\thashmap_entry_init(e, strhash(path));\n+\texisting = hashmap_get(map, e, NULL);\n+\tif (existing) {\n+\t\tfree(e);\n+\t\te = existing;\n+\t} else {\n+\t\te->left[0] = e->right[0] = '\\0';\n+\t\thashmap_add(map, e);\n+\t}\n+\tstrcpy(is_right ? e->right : e->left, content);\n+}\n+\n+struct path_entry {\n+\tstruct hashmap_entry entry;\n+\tchar path[FLEX_ARRAY];\n+};\n+\n+int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key)\n+{\n+\treturn strcmp(a->path, key ? key : b->path);\n+}\n+\n+static void changed_files(struct hashmap *result, const char *index_path,\n+\t\t\t  const char *workdir)\n+{\n+\tstruct child_process update_index = CHILD_PROCESS_INIT;\n+\tstruct child_process diff_files = CHILD_PROCESS_INIT;\n+\tstruct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT;\n+\tconst char *git_dir = absolute_path(get_git_dir()), *env[] = {\n+\t\tNULL, NULL\n+\t};\n+\tFILE *fp;\n+\n+\tstrbuf_addf(&index_env, \"GIT_INDEX_FILE=%s\", index_path);\n+\tenv[0] = index_env.buf;\n+\n+\targv_array_pushl(&update_index.args,\n+\t\t\t \"--git-dir\", git_dir, \"--work-tree\", workdir,\n+\t\t\t \"update-index\", \"--really-refresh\", \"-q\",\n+\t\t\t \"--unmerged\", NULL);\n+\tupdate_index.no_stdin = 1;\n+\tupdate_index.no_stdout = 1;\n+\tupdate_index.no_stderr = 1;\n+\tupdate_index.git_cmd = 1;\n+\tupdate_index.use_shell = 0;\n+\tupdate_index.clean_on_exit = 1;\n+\tupdate_index.dir = workdir;\n+\tupdate_index.env = env;\n+\t/* Ignore any errors of update-index */\n+\trun_command(&update_index);\n+\n+\targv_array_pushl(&diff_files.args,\n+\t\t\t \"--git-dir\", git_dir, \"--work-tree\", workdir,\n+\t\t\t \"diff-files\", \"--name-only\", \"-z\", NULL);\n+\tdiff_files.no_stdin = 1;\n+\tdiff_files.git_cmd = 1;\n+\tdiff_files.use_shell = 0;\n+\tdiff_files.clean_on_exit = 1;\n+\tdiff_files.out = -1;\n+\tdiff_files.dir = workdir;\n+\tdiff_files.env = env;\n+\tif (start_command(&diff_files))\n+\t\tdie(\"could not obtain raw diff\");\n+\tfp = xfdopen(diff_files.out, \"r\");\n+\twhile (!strbuf_getline_nul(&buf, fp)) {\n+\t\tstruct path_entry *entry;\n+\t\tFLEX_ALLOC_STR(entry, path, buf.buf);\n+\t\thashmap_entry_init(entry, strhash(buf.buf));\n+\t\thashmap_add(result, entry);\n+\t}\n+\tif (finish_command(&diff_files))\n+\t\tdie(\"diff-files did not exit properly\");\n+\tstrbuf_release(&index_env);\n+\tstrbuf_release(&buf);\n+}\n+\n+#include \"dir.h\"\n+\n+static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstrbuf_addstr(&buf, tmpdir);\n+\tremove_dir_recursively(&buf, 0);\n+\tif (exit_code)\n+\t\twarning(_(\"failed: %d\"), exit_code);\n+\texit(exit_code);\n+}\n+\n+static int ensure_leading_directories(char *path)\n+{\n+\tswitch (safe_create_leading_directories(path)) {\n+\t\tcase SCLD_OK:\n+\t\tcase SCLD_EXISTS:\n+\t\t\treturn 0;\n+\t\tdefault:\n+\t\t\treturn error(_(\"could not create leading directories \"\n+\t\t\t\t       \"of '%s'\"), path);\n+\t}\n+}\n+\n+static int run_dir_diff(const char *extcmd, int symlinks,\n+\t\t\tint argc, const char **argv)\n+{\n+\tchar tmpdir[PATH_MAX];\n+\tstruct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;\n+\tstruct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;\n+\tstruct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;\n+\tstruct strbuf wtdir = STRBUF_INIT;\n+\tsize_t ldir_len, rdir_len, wtdir_len;\n+\tstruct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1);\n+\tconst char *workdir, *tmp;\n+\tint ret = 0, i;\n+\tFILE *fp;\n+\tstruct hashmap working_tree_dups, submodules, symlinks2;\n+\tstruct hashmap_iter iter;\n+\tstruct pair_entry *entry;\n+\tenum object_type type;\n+\tunsigned long size;\n+\tstruct index_state wtindex;\n+\tstruct checkout lstate, rstate;\n+\tint rc, flags = RUN_GIT_CMD, err = 0;\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\tconst char *helper_argv[] = { \"difftool--helper\", NULL, NULL, NULL };\n+\tstruct hashmap wt_modified, tmp_modified;\n+\tint indices_loaded = 0;\n+\n+\tsetup_work_tree();\n+\tworkdir = get_git_work_tree();\n+\n+\t/* Setup temp directories */\n+\ttmp = getenv(\"TMPDIR\");\n+\tsprintf(tmpdir, \"%s/git-difftool.XXXXXX\", tmp ? tmp : \"/tmp\");\n+\tif (!mkdtemp(tmpdir))\n+\t\treturn error(\"could not create temporary directory\");\n+\tstrbuf_addf(&ldir, \"%s/left/\", tmpdir);\n+\tstrbuf_addf(&rdir, \"%s/right/\", tmpdir);\n+\tstrbuf_addstr(&wtdir, workdir);\n+\tif (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))\n+\t\tstrbuf_addch(&wtdir, '/');\n+\tmkdir(ldir.buf, 0777);\n+\tmkdir(rdir.buf, 0777);\n+\n+\tmemset(&wtindex, 0, sizeof(wtindex));\n+\n+\tmemset(&lstate, 0, sizeof(lstate));\n+\tlstate.base_dir = ldir.buf;\n+\tlstate.base_dir_len = ldir.len;\n+\tlstate.force = 1;\n+\tmemset(&rstate, 0, sizeof(rstate));\n+\trstate.base_dir = rdir.buf;\n+\trstate.base_dir_len = rdir.len;\n+\trstate.force = 1;\n+\n+\tldir_len = ldir.len;\n+\trdir_len = rdir.len;\n+\twtdir_len = wtdir.len;\n+\n+\thashmap_init(&working_tree_dups,\n+\t\t     (hashmap_cmp_fn)working_tree_entry_cmp, 0);\n+\thashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0);\n+\thashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0);\n+\n+\tchild.no_stdin = 1;\n+\tchild.git_cmd = 1;\n+\tchild.use_shell = 0;\n+\tchild.clean_on_exit = 1;\n+\tchild.out = -1;\n+\targv_array_pushl(&child.args, \"diff\", \"--raw\", \"--no-abbrev\", \"-z\",\n+\t\t\t NULL);\n+\tfor (i = 0; i < argc; i++)\n+\t\targv_array_push(&child.args, argv[i]);\n+\tif (start_command(&child))\n+\t\tdie(\"could not obtain raw diff\");\n+\tfp = xfdopen(child.out, \"r\");\n+\n+\t/* Build index info for left and right sides of the diff */\n+\twhile (!strbuf_getline_nul(&info, fp)) {\n+\t\tint lmode, rmode;\n+\t\tstruct object_id loid, roid;\n+\t\tchar status;\n+\t\tconst char *src_path, *dst_path;\n+\t\tsize_t src_path_len, dst_path_len;\n+\n+\t\tif (starts_with(info.buf, \"::\"))\n+\t\t\tdie(N_(\"combined diff formats('-c' and '--cc') are \"\n+\t\t\t       \"not supported in\\n\"\n+\t\t\t       \"directory diff mode('-d' and '--dir-diff').\"));\n+\n+\t\tif (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,\n+\t\t\t\t     &status))\n+\t\t\tbreak;\n+\t\tif (strbuf_getline_nul(&lpath, fp))\n+\t\t\tbreak;\n+\t\tsrc_path = lpath.buf;\n+\t\tsrc_path_len = lpath.len;\n+\n+\t\tif (status != 'C' && status != 'R') {\n+\t\t\tdst_path = src_path;\n+\t\t\tdst_path_len = src_path_len;\n+\t\t} else {\n+\t\t\tif (strbuf_getline_nul(&rpath, fp))\n+\t\t\t\tbreak;\n+\t\t\tdst_path = rpath.buf;\n+\t\t\tdst_path_len = rpath.len;\n+\t\t}\n+\n+\t\tif (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) {\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"Subproject commit %s\",\n+\t\t\t\t    oid_to_hex(&loid));\n+\t\t\tadd_left_or_right(&submodules, src_path, buf.buf, 0);\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"Subproject commit %s\",\n+\t\t\t\t    oid_to_hex(&roid));\n+\t\t\tif (!oidcmp(&loid, &roid))\n+\t\t\t\tstrbuf_addstr(&buf, \"-dirty\");\n+\t\t\tadd_left_or_right(&submodules, dst_path, buf.buf, 1);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (S_ISLNK(lmode)) {\n+\t\t\tchar *content = read_sha1_file(loid.hash, &type, &size);\n+\t\t\tadd_left_or_right(&symlinks2, src_path, content, 0);\n+\t\t\tfree(content);\n+\t\t}\n+\n+\t\tif (S_ISLNK(rmode)) {\n+\t\t\tchar *content = read_sha1_file(roid.hash, &type, &size);\n+\t\t\tadd_left_or_right(&symlinks2, dst_path, content, 1);\n+\t\t\tfree(content);\n+\t\t}\n+\n+\t\tif (lmode && status != 'C') {\n+\t\t\tce->ce_mode = lmode;\n+\t\t\toidcpy(&ce->oid, &loid);\n+\t\t\tstrcpy(ce->name, src_path);\n+\t\t\tce->ce_namelen = src_path_len;\n+\t\t\tif (checkout_entry(ce, &lstate, NULL))\n+\t\t\t\treturn error(\"could not write '%s'\", src_path);\n+\t\t}\n+\n+\t\tif (rmode) {\n+\t\t\tstruct working_tree_entry *entry;\n+\n+\t\t\t/* Avoid duplicate working_tree entries */\n+\t\t\tFLEX_ALLOC_STR(entry, path, dst_path);\n+\t\t\thashmap_entry_init(entry, strhash(dst_path));\n+\t\t\tif (hashmap_get(&working_tree_dups, entry, NULL)) {\n+\t\t\t\tfree(entry);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\thashmap_add(&working_tree_dups, entry);\n+\n+\t\t\tif (!use_wt_file(workdir, dst_path, &roid)) {\n+\t\t\t\tce->ce_mode = rmode;\n+\t\t\t\toidcpy(&ce->oid, &roid);\n+\t\t\t\tstrcpy(ce->name, dst_path);\n+\t\t\t\tce->ce_namelen = dst_path_len;\n+\t\t\t\tif (checkout_entry(ce, &rstate, NULL))\n+\t\t\t\t\treturn error(\"could not write '%s'\",\n+\t\t\t\t\t\t     dst_path);\n+\t\t\t} else if (!is_null_oid(&roid)) {\n+\t\t\t\t/*\n+\t\t\t\t * Changes in the working tree need special\n+\t\t\t\t * treatment since they are not part of the\n+\t\t\t\t * index.\n+\t\t\t\t */\n+\t\t\t\tstruct cache_entry *ce2 =\n+\t\t\t\t\tmake_cache_entry(rmode, roid.hash,\n+\t\t\t\t\t\t\t dst_path, 0, 0);\n+\t\t\t\tce_mode_from_stat(ce2, rmode);\n+\n+\t\t\t\tadd_index_entry(&wtindex, ce2,\n+\t\t\t\t\t\tADD_CACHE_JUST_APPEND);\n+\n+\t\t\t\tadd_path(&wtdir, wtdir_len, dst_path);\n+\t\t\t\tadd_path(&rdir, rdir_len, dst_path);\n+\t\t\t\tif (ensure_leading_directories(rdir.buf))\n+\t\t\t\t\treturn error(\"could not create \"\n+\t\t\t\t\t\t     \"directory for '%s'\",\n+\t\t\t\t\t\t     dst_path);\n+\t\t\t\tif (symlinks) {\n+\t\t\t\t\tif (symlink(wtdir.buf, rdir.buf)) {\n+\t\t\t\t\t\tret = error_errno(\"could not symlink '%s' to '%s'\", wtdir.buf, rdir.buf);\n+\t\t\t\t\t\tgoto finish;\n+\t\t\t\t\t}\n+\t\t\t\t} else {\n+\t\t\t\t\tstruct stat st;\n+\t\t\t\t\tif (stat(wtdir.buf, &st))\n+\t\t\t\t\t\tst.st_mode = 0644;\n+\t\t\t\t\tif (copy_file(rdir.buf, wtdir.buf,\n+\t\t\t\t\t\t      st.st_mode)) {\n+\t\t\t\t\t\tret = error(\"could not copy '%s' to '%s'\", wtdir.buf, rdir.buf);\n+\t\t\t\t\t\tgoto finish;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t}\n+\tif (finish_command(&child)) {\n+\t\tret = error(\"error occurred running diff --raw\");\n+\t\tgoto finish;\n+\t}\n+\n+\t/*\n+\t * Changes to submodules require special treatment.This loop writes a\n+\t * temporary file to both the left and right directories to show the\n+\t * change in the recorded SHA1 for the submodule.\n+\t */\n+\thashmap_iter_init(&submodules, &iter);\n+\twhile ((entry = hashmap_iter_next(&iter))) {\n+\t\tif (*entry->left) {\n+\t\t\tadd_path(&ldir, ldir_len, entry->path);\n+\t\t\tensure_leading_directories(ldir.buf);\n+\t\t\twrite_file(ldir.buf, \"%s\", entry->left);\n+\t\t}\n+\t\tif (*entry->right) {\n+\t\t\tadd_path(&rdir, rdir_len, entry->path);\n+\t\t\tensure_leading_directories(rdir.buf);\n+\t\t\twrite_file(rdir.buf, \"%s\", entry->right);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Symbolic links require special treatment.The standard \"git diff\"\n+\t * shows only the link itself, not the contents of the link target.\n+\t * This loop replicates that behavior.\n+\t */\n+\thashmap_iter_init(&symlinks2, &iter);\n+\twhile ((entry = hashmap_iter_next(&iter))) {\n+\t\tif (*entry->left) {\n+\t\t\tadd_path(&ldir, ldir_len, entry->path);\n+\t\t\tensure_leading_directories(ldir.buf);\n+\t\t\twrite_file(ldir.buf, \"%s\", entry->left);\n+\t\t}\n+\t\tif (*entry->right) {\n+\t\t\tadd_path(&rdir, rdir_len, entry->path);\n+\t\t\tensure_leading_directories(rdir.buf);\n+\t\t\twrite_file(rdir.buf, \"%s\", entry->right);\n+\t\t}\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\tstrbuf_setlen(&ldir, ldir_len);\n+\thelper_argv[1] = ldir.buf;\n+\tstrbuf_setlen(&rdir, rdir_len);\n+\thelper_argv[2] = rdir.buf;\n+\n+\tif (extcmd) {\n+\t\thelper_argv[0] = extcmd;\n+\t\tflags = 0;\n+\t} else\n+\t\tsetenv(\"GIT_DIFFTOOL_DIRDIFF\", \"true\", 1);\n+\trc = run_command_v_opt(helper_argv, flags);\n+\n+\t/*\n+\t * If the diff includes working copy files and those\n+\t * files were modified during the diff, then the changes\n+\t * should be copied back to the working tree.\n+\t * Do not copy back files when symlinks are used and the\n+\t * external tool did not replace the original link with a file.\n+\t *\n+\t * These hashes are loaded lazily since they aren't needed\n+\t * in the common case of --symlinks and the difftool updating\n+\t * files through the symlink.\n+\t */\n+\thashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp,\n+\t\t     wtindex.cache_nr);\n+\thashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp,\n+\t\t     wtindex.cache_nr);\n+\n+\tfor (i = 0; i < wtindex.cache_nr; i++) {\n+\t\tstruct hashmap_entry dummy;\n+\t\tconst char *name = wtindex.cache[i]->name;\n+\t\tstruct stat st;\n+\n+\t\tadd_path(&rdir, rdir_len, name);\n+\t\tif (lstat(rdir.buf, &st))\n+\t\t\tcontinue;\n+\n+\t\tif ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode))\n+\t\t\tcontinue;\n+\n+\t\tif (!indices_loaded) {\n+\t\t\tstatic struct lock_file lock;\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"%s/wtindex\", tmpdir);\n+\t\t\tif (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||\n+\t\t\t    write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {\n+\t\t\t\tret = error(\"could not write %s\", buf.buf);\n+\t\t\t\trollback_lock_file(&lock);\n+\t\t\t\tgoto finish;\n+\t\t\t}\n+\t\t\tchanged_files(&wt_modified, buf.buf, workdir);\n+\t\t\tstrbuf_setlen(&rdir, rdir_len);\n+\t\t\tchanged_files(&tmp_modified, buf.buf, rdir.buf);\n+\t\t\tadd_path(&rdir, rdir_len, name);\n+\t\t\tindices_loaded = 1;\n+\t\t}\n+\n+\t\thashmap_entry_init(&dummy, strhash(name));\n+\t\tif (hashmap_get(&tmp_modified, &dummy, name)) {\n+\t\t\tadd_path(&wtdir, wtdir_len, name);\n+\t\t\tif (hashmap_get(&wt_modified, &dummy, name)) {\n+\t\t\t\twarning(_(\"both files modified: '%s' and '%s'.\"),\n+\t\t\t\t\twtdir.buf, rdir.buf);\n+\t\t\t\twarning(_(\"working tree file has been left.\"));\n+\t\t\t\twarning(\"\");\n+\t\t\t\terr = 1;\n+\t\t\t} else if (unlink(wtdir.buf) ||\n+\t\t\t\t   copy_file(wtdir.buf, rdir.buf, st.st_mode))\n+\t\t\t\twarning_errno(_(\"could not copy '%s' to '%s'\"),\n+\t\t\t\t\t      rdir.buf, wtdir.buf);\n+\t\t}\n+\t}\n+\n+\tif (err) {\n+\t\twarning(_(\"temporary files exist in '%s'.\"), tmpdir);\n+\t\twarning(_(\"you may want to cleanup or recover these.\"));\n+\t\texit(1);\n+\t} else\n+\t\texit_cleanup(tmpdir, rc);\n+\n+finish:\n+\tfree(ce);\n+\tstrbuf_release(&ldir);\n+\tstrbuf_release(&rdir);\n+\tstrbuf_release(&wtdir);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+static int run_file_diff(int prompt, int argc, const char **argv)\n+{\n+\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\tconst char *env[] = {\n+\t\t\"GIT_PAGER=\", \"GIT_EXTERNAL_DIFF=git-difftool--helper\", NULL,\n+\t\tNULL\n+\t};\n+\tint ret = 0, i;\n+\n+\tif (prompt > 0)\n+\t\tenv[2] = \"GIT_DIFFTOOL_PROMPT=true\";\n+\telse if (!prompt)\n+\t\tenv[2] = \"GIT_DIFFTOOL_NO_PROMPT=true\";\n+\n+\targv_array_push(&args, \"diff\");\n+\tfor (i = 0; i < argc; i++)\n+\t\targv_array_push(&args, argv[i]);\n+\tret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, NULL, env);\n+\texit(ret);\n+}\n+\n+int cmd_builtin_difftool(int argc, const char ** argv, const char * prefix)\n+{\n+\tint use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,\n+\t    tool_help = 0;\n+\tstatic char *difftool_cmd = NULL, *extcmd = NULL;\n+\n+\tstruct option builtin_difftool_options[] = {\n+\t\tOPT_BOOL('g', \"gui\", &use_gui_tool,\n+\t\t\t N_(\"use `diff.guitool` instead of `diff.tool`\")),\n+\t\tOPT_BOOL('d', \"dir-diff\", &dir_diff,\n+\t\t\t N_(\"perform a full-directory diff\")),\n+\t\t{ OPTION_SET_INT, 'y', \"no-prompt\", &prompt, NULL,\n+\t\t\tN_(\"do not prompt before launching a diff tool\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},\n+\t\t{ OPTION_SET_INT, 0, \"prompt\", &prompt, NULL, NULL,\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n+\t\t\tNULL, 1 },\n+\t\tOPT_BOOL(0, \"symlinks\", &symlinks,\n+\t\t\t N_(\"use symlinks in dir-diff mode\")),\n+\t\tOPT_STRING('t', \"tool\", &difftool_cmd, N_(\"<tool>\"),\n+\t\t\t   N_(\"use the specified diff tool\")),\n+\t\tOPT_BOOL(0, \"tool-help\", &tool_help,\n+\t\t\t N_(\"print a list of diff tools that may be used with \"\n+\t\t\t    \"`--tool`\")),\n+\t\tOPT_BOOL(0, \"trust-exit-code\", &trust_exit_code,\n+\t\t\t N_(\"make 'git-difftool' exit when an invoked diff \"\n+\t\t\t    \"tool returns a non - zero exit code\")),\n+\t\tOPT_STRING('x', \"extcmd\", &extcmd, N_(\"<command>\"),\n+\t\t\t   N_(\"specify a custom command for viewing diffs\")),\n+\t\tOPT_END()\n+\t};\n+\n+\tsymlinks = has_symlinks;\n+\n+\tgit_config(difftool_config, NULL);\n+\n+\targc = parse_options(argc, argv, prefix, builtin_difftool_options,\n+\t\t\t     builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n+\n+\tif (tool_help)\n+\t\treturn print_tool_help();\n+\n+\tif (use_gui_tool && diff_gui_tool && *diff_gui_tool)\n+\t\tsetenv(\"GIT_DIFF_TOOL\", diff_gui_tool, 1);\n+\telse if (difftool_cmd) {\n+\t\tif (*difftool_cmd)\n+\t\t\tsetenv(\"GIT_DIFF_TOOL\", difftool_cmd, 1);\n+\t\telse\n+\t\t\tdie(_(\"no <tool> given for --tool=<tool>\"));\n+\t}\n+\n+\tif (extcmd) {\n+\t\tif (*extcmd)\n+\t\t\tsetenv(\"GIT_DIFFTOOL_EXTCMD\", extcmd, 1);\n+\t\telse\n+\t\t\tdie(_(\"no <cmd> given for --extcmd=<cmd>\"));\n+\t}\n+\n+\tsetenv(\"GIT_DIFFTOOL_TRUST_EXIT_CODE\",\n+\t       trust_exit_code ? \"true\" : \"false\", 1);\n+\n+\t/*\n+\t * In directory diff mode, 'git-difftool--helper' is called once\n+\t * to compare the a / b directories.In file diff mode, 'git diff'\n+\t * will invoke a separate instance of 'git-difftool--helper' for\n+\t * each file that changed.\n+\t */\n+\tif (dir_diff)\n+\t\treturn run_dir_diff(extcmd, symlinks, argc, argv);\n+\treturn run_file_diff(prompt, argc, argv);\n+}\ndiff --git a/git.c b/git.c\nindex efa1059..eaa0f67 100644\n--- a/git.c\n+++ b/git.c\n@@ -424,6 +424,7 @@ static struct cmd_struct commands[] = {\n \t{ \"diff-files\", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"diff-index\", cmd_diff_index, RUN_SETUP },\n \t{ \"diff-tree\", cmd_diff_tree, RUN_SETUP },\n+\t{ \"builtin-difftool\", cmd_builtin_difftool, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"fast-export\", cmd_fast_export, RUN_SETUP },\n \t{ \"fetch\", cmd_fetch, RUN_SETUP },\n \t{ \"fetch-pack\", cmd_fetch_pack, RUN_SETUP },\n-- \n2.10.1.583.g721a9e0\n\n\n"},{"id":"306311","messageId":"598dcfdbeef4e15d2d439053a0423589182e5f30.1479834051.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1479834051.git.johannes.schindelin@gmx.de","subject":"[PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-22T17:01:27Z","receivedAt":"2016-11-22T17:02:10Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The popular difftool command was just converted into a builtin, for\nbetter performance on Windows as well as to reduce the number of Perl\nscripts (so that we may, in the very long run, be able to ship Git for\nWindows without any Perl interpreter at all).\n\nHowever, it would be sloppy practice to simply switch over from the\ntried-and-tested (albeit slow) scripted version to the spanking new\nbuiltin version that has not seen a whole lot of real-world testing.\n\nSo let's add a feature flag.\n\nIf the file `use-builtin-difftool` exists in Git's exec path, Git will\nnow automagically use the builtin version of the difftool, without\nrequiring the user to call `git builtin-difftool <args>`. This comes in\nparticularly handy when the difftool command is used from within\nscripts.\n\nIf the file `use-builtin-difftool` is absent from Git's exec path, which\nis the default, Git will use the scripted version as before.\n\nThe original idea was to use an environment variable\nGIT_USE_BUILTIN_DIFFTOOL, but the test suite resets those variables, and\nwe do want to use that feature flag to run the tests with, and without,\nthe feature flag.\n\nBesides, the plan is to add an opt-in flag in Git for Windows'\ninstaller. If we implemented the feature flag as an environment\nvariable, we would have to modify the user's environment, in order to\nmake the builtin difftool the default when called from Git Bash, Git CMD\nor third-party tools.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n .gitignore        |  1 +\n git-difftool.perl |  7 +++++++\n git.c             | 20 ++++++++++++++++++++\n 3 files changed, 28 insertions(+)\n\ndiff --git a/.gitignore b/.gitignore\nindex 4f54531..91bfd09 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -1,3 +1,4 @@\n+/use-builtin-difftool\n /GIT-BUILD-OPTIONS\n /GIT-CFLAGS\n /GIT-LDFLAGS\ndiff --git a/git-difftool.perl b/git-difftool.perl\nindex a5790d0..28e47d8 100755\n--- a/git-difftool.perl\n+++ b/git-difftool.perl\n@@ -23,6 +23,13 @@ use File::Temp qw(tempdir);\n use Getopt::Long qw(:config pass_through);\n use Git;\n \n+if (-e Git::exec_path() . '/use-builtin-difftool') {\n+\tunshift(@ARGV, \"builtin-difftool\");\n+\tunshift(@ARGV, \"git\");\n+\texec(@ARGV);\n+\tdie(\"Could not execute builtin difftool\");\n+}\n+\n sub usage\n {\n \tmy $exitcode = shift;\ndiff --git a/git.c b/git.c\nindex eaa0f67..7a0df7a 100644\n--- a/git.c\n+++ b/git.c\n@@ -2,6 +2,7 @@\n #include \"exec_cmd.h\"\n #include \"help.h\"\n #include \"run-command.h\"\n+#include \"dir.h\"\n \n const char git_usage_string[] =\n \t\"git [--version] [--help] [-C <path>] [-c name=value]\\n\"\n@@ -542,6 +543,22 @@ static void strip_extension(const char **argv)\n #define strip_extension(cmd)\n #endif\n \n+static int use_builtin_difftool(void)\n+{\n+\tstatic int initialized, use;\n+\n+\tif (!initialized) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tstrbuf_addf(&buf, \"%s/%s\", git_exec_path(),\n+\t\t\t    \"use-builtin-difftool\");\n+\t\tuse = file_exists(buf.buf);\n+\t\tstrbuf_release(&buf);\n+\t\tinitialized = 1;\n+\t}\n+\n+\treturn use;\n+}\n+\n static void handle_builtin(int argc, const char **argv)\n {\n \tstruct argv_array args = ARGV_ARRAY_INIT;\n@@ -551,6 +568,9 @@ static void handle_builtin(int argc, const char **argv)\n \tstrip_extension(argv);\n \tcmd = argv[0];\n \n+\tif (!strcmp(\"difftool\", cmd) && use_builtin_difftool())\n+\t\tcmd = \"builtin-difftool\";\n+\n \t/* Turn \"git cmd --help\" into \"git help --exclude-guides cmd\" */\n \tif (argc > 1 && !strcmp(argv[1], \"--help\")) {\n \t\tint i;\n-- \n2.10.1.583.g721a9e0\n"},{"id":"306370","messageId":"20161123080850.GA23742@gmail.com","threadId":"44532","inReplyTo":"7aec571e1b0773ca80ea25453d2650af6a18e095.1479834051.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 1/2] difftool: add the builtin","fromName":"David Aguilar","fromEmail":"davvid@gmail.com","sentAt":"2016-11-23T08:08:50Z","receivedAt":"2016-11-23T08:22:56Z","isPatch":true,"sender":{"key":"davvid@gmail.com","avatar":"https://avatars.githubusercontent.com/u/13196?v=4"},"body":"On Tue, Nov 22, 2016 at 06:01:23PM +0100, Johannes Schindelin wrote:\n> This adds a builtin difftool that represents a conversion of the current\n> Perl script version of the difftool.\n> \n> The motivation is that Perl scripts are not at all native on Windows,\n> and that `git difftool` therefore is pretty slow on that platform, when\n> there is no good reason for it to be slow.\n> \n> In addition, Perl does not really have access to Git's internals. That\n> means that any script will always have to jump through unnecessary\n> hoops.\n\nNice!\n\n> The current version of the builtin difftool does not, however, make full\n> use of the internals but instead chooses to spawn a couple of Git\n> processes, still, to make for an easier conversion. There remains a lot\n> of room for improvement, left for a later date.\n> \n> Note: the original difftool is still called by `git difftool`. To get the\n> new, experimental version, call `git builtin-difftool`. The reason: this\n> new, experimental, builtin difftool will be shipped as part of Git for\n> Windows v2.11.0, to allow for easier large-scale testing.\n\nI like this plan.  I was going to ask for an environment\nvariable (to preset in git-cola) but since Git for Windows is\nhandling it then everyone benefits.\n\n> diff --git a/builtin/builtin-difftool.c b/builtin/builtin-difftool.c\n> new file mode 100644\n> index 0000000..9feefcd\n> --- /dev/null\n> +++ b/builtin/builtin-difftool.c\n> @@ -0,0 +1,680 @@\n> +/*\n> + * \"git difftool\" builtin command\n> + *\n> + * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible\n> + * git-difftool--helper script.\n> + *\n> + * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.\n> + * The GIT_DIFF* variables are exported for use by git-difftool--helper.\n> + *\n> + * Any arguments that are unknown to this script are forwarded to 'git diff'.\n> + *\n> + * Copyright (C) 2016 Johannes Schindelin\n> + */\n> +#include \"cache.h\"\n> +#include \"builtin.h\"\n> +#include \"parse-options.h\"\n> +#include \"run-command.h\"\n> +#include \"argv-array.h\"\n> +#include \"strbuf.h\"\n> +#include \"lockfile.h\"\n> +\n> +static char *diff_gui_tool;\n> +static int trust_exit_code;\n> +\n> +static const char * const builtin_difftool_usage[] = {\n> +\tN_(\"git add [<options>] [--] <pathspec>...\"),\n> +\tNULL\n> +};\n\nThe usage should probably say \"difftool\" (or \"builtin-difftool\").\n\n> [...]\n> +static void changed_files(struct hashmap *result, const char *index_path,\n> +\t\t\t  const char *workdir)\n> +{\n> +[...]\n> +}\n> +\n> +#include \"dir.h\"\n\nCan this mid-file #include go to the top of the file?\n\n> +static int run_dir_diff(const char *extcmd, int symlinks,\n> +\t\t\tint argc, const char **argv)\n> +{\n> +\tchar tmpdir[PATH_MAX];\n> +\tstruct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;\n> +\tstruct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;\n> +\tstruct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;\n> +\tstruct strbuf wtdir = STRBUF_INIT;\n> +\tsize_t ldir_len, rdir_len, wtdir_len;\n> +\tstruct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1);\n> +\tconst char *workdir, *tmp;\n> +\tint ret = 0, i;\n> +\tFILE *fp;\n> +\tstruct hashmap working_tree_dups, submodules, symlinks2;\n> +\tstruct hashmap_iter iter;\n> +\tstruct pair_entry *entry;\n> +\tenum object_type type;\n> +\tunsigned long size;\n> +\tstruct index_state wtindex;\n> +\tstruct checkout lstate, rstate;\n> +\tint rc, flags = RUN_GIT_CMD, err = 0;\n> +\tstruct child_process child = CHILD_PROCESS_INIT;\n> +\tconst char *helper_argv[] = { \"difftool--helper\", NULL, NULL, NULL };\n> +\tstruct hashmap wt_modified, tmp_modified;\n> +\tint indices_loaded = 0;\n> +\n> +\tsetup_work_tree();\n> +\tworkdir = get_git_work_tree();\n> +\n> +\t/* Setup temp directories */\n> +\ttmp = getenv(\"TMPDIR\");\n> +\tsprintf(tmpdir, \"%s/git-difftool.XXXXXX\", tmp ? tmp : \"/tmp\");\n\nMaybe snprintf instead?\n\ngetenv() won't return anything longer than PATH_MAX for most\nusers, but users are weird.\n\n> +\tif (!mkdtemp(tmpdir))\n> +\t\treturn error(\"could not create temporary directory\");\n\nMention the tmpdir here?\n\n> +\tstrbuf_addf(&ldir, \"%s/left/\", tmpdir);\n> +\tstrbuf_addf(&rdir, \"%s/right/\", tmpdir);\n> +\tstrbuf_addstr(&wtdir, workdir);\n> +\tif (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))\n> +\t\tstrbuf_addch(&wtdir, '/');\n> +\tmkdir(ldir.buf, 0777);\n> +\tmkdir(rdir.buf, 0777);\n\nSeeing the perl mkpath() default 0777 spelled out this way\nmakes me wonder whether 0700 would be safer.\n\nThe mkdtemp() above is already using 0700 so it's ok, but it\nmight be worth making it consistent (later, perhaps).\n\n> [...]\n> +int cmd_builtin_difftool(int argc, const char ** argv, const char * prefix)\n> +{\n> +\tint use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,\n> +\t    tool_help = 0;\n> +\tstatic char *difftool_cmd = NULL, *extcmd = NULL;\n> +\n> +\tstruct option builtin_difftool_options[] = {\n> +\t\tOPT_BOOL('g', \"gui\", &use_gui_tool,\n> +\t\t\t N_(\"use `diff.guitool` instead of `diff.tool`\")),\n> +\t\tOPT_BOOL('d', \"dir-diff\", &dir_diff,\n> +\t\t\t N_(\"perform a full-directory diff\")),\n> +\t\t{ OPTION_SET_INT, 'y', \"no-prompt\", &prompt, NULL,\n> +\t\t\tN_(\"do not prompt before launching a diff tool\"),\n> +\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},\n> +\t\t{ OPTION_SET_INT, 0, \"prompt\", &prompt, NULL, NULL,\n> +\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n> +\t\t\tNULL, 1 },\n> +\t\tOPT_BOOL(0, \"symlinks\", &symlinks,\n> +\t\t\t N_(\"use symlinks in dir-diff mode\")),\n> +\t\tOPT_STRING('t', \"tool\", &difftool_cmd, N_(\"<tool>\"),\n> +\t\t\t   N_(\"use the specified diff tool\")),\n> +\t\tOPT_BOOL(0, \"tool-help\", &tool_help,\n> +\t\t\t N_(\"print a list of diff tools that may be used with \"\n> +\t\t\t    \"`--tool`\")),\n> +\t\tOPT_BOOL(0, \"trust-exit-code\", &trust_exit_code,\n> +\t\t\t N_(\"make 'git-difftool' exit when an invoked diff \"\n> +\t\t\t    \"tool returns a non - zero exit code\")),\n> +\t\tOPT_STRING('x', \"extcmd\", &extcmd, N_(\"<command>\"),\n> +\t\t\t   N_(\"specify a custom command for viewing diffs\")),\n> +\t\tOPT_END()\n> +\t};\n> +\n> +\tsymlinks = has_symlinks;\n> +\n> +\tgit_config(difftool_config, NULL);\n> +\n> +\targc = parse_options(argc, argv, prefix, builtin_difftool_options,\n> +\t\t\t     builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |\n> +\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n> +\n> +\tif (tool_help)\n> +\t\treturn print_tool_help();\n> +\n> +\tif (use_gui_tool && diff_gui_tool && *diff_gui_tool)\n> +\t\tsetenv(\"GIT_DIFF_TOOL\", diff_gui_tool, 1);\n> +\telse if (difftool_cmd) {\n> +\t\tif (*difftool_cmd)\n> +\t\t\tsetenv(\"GIT_DIFF_TOOL\", difftool_cmd, 1);\n> +\t\telse\n> +\t\t\tdie(_(\"no <tool> given for --tool=<tool>\"));\n> +\t}\n> +\n> +\tif (extcmd) {\n> +\t\tif (*extcmd)\n> +\t\t\tsetenv(\"GIT_DIFFTOOL_EXTCMD\", extcmd, 1);\n> +\t\telse\n> +\t\t\tdie(_(\"no <cmd> given for --extcmd=<cmd>\"));\n> +\t}\n> +\n> +\tsetenv(\"GIT_DIFFTOOL_TRUST_EXIT_CODE\",\n> +\t       trust_exit_code ? \"true\" : \"false\", 1);\n> +\n> +\t/*\n> +\t * In directory diff mode, 'git-difftool--helper' is called once\n> +\t * to compare the a / b directories.In file diff mode, 'git diff'\n> +\t * will invoke a separate instance of 'git-difftool--helper' for\n> +\t * each file that changed.\n> +\t */\n\nMissing space after \".\" in the comment above.\n\n> +\tif (dir_diff)\n> +\t\treturn run_dir_diff(extcmd, symlinks, argc, argv);\n> +\treturn run_file_diff(prompt, argc, argv);\n> +}\n> diff --git a/git.c b/git.c\n> index efa1059..eaa0f67 100644\n> --- a/git.c\n> +++ b/git.c\n> @@ -424,6 +424,7 @@ static struct cmd_struct commands[] = {\n>  \t{ \"diff-files\", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },\n>  \t{ \"diff-index\", cmd_diff_index, RUN_SETUP },\n>  \t{ \"diff-tree\", cmd_diff_tree, RUN_SETUP },\n> +\t{ \"builtin-difftool\", cmd_builtin_difftool, RUN_SETUP | NEED_WORK_TREE },\n>  \t{ \"fast-export\", cmd_fast_export, RUN_SETUP },\n>  \t{ \"fetch\", cmd_fetch, RUN_SETUP },\n>  \t{ \"fetch-pack\", cmd_fetch_pack, RUN_SETUP },\n\nThis isn't alphabetical anymore, but it actually is if you\nconsider that the final plan is to change \"builtin-difftool\" to\n\"difftool\".\n\nIf we want to minimize that future diff we could name\ncmd_builtin_difftool() as cmd_difftool() for consistency now so\nthat the future commit only needs to tweak the string here.\n-- \nDavid\n"},{"id":"306375","messageId":"alpine.DEB.2.20.1611231210380.3746@virtualbox","threadId":"44532","inReplyTo":"20161123080850.GA23742@gmail.com","subject":"Re: [PATCH 1/2] difftool: add the builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-23T11:34:07Z","receivedAt":"2016-11-23T11:41:14Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi David,\n\nOn Wed, 23 Nov 2016, David Aguilar wrote:\n\n> On Tue, Nov 22, 2016 at 06:01:23PM +0100, Johannes Schindelin wrote:\n>\n> > +static const char * const builtin_difftool_usage[] = {\n> > +\tN_(\"git add [<options>] [--] <pathspec>...\"),\n> > +\tNULL\n> > +};\n> \n> The usage should probably say \"difftool\" (or \"builtin-difftool\").\n\nAh, my dirty secret was spilled. I copy-edited this. *pours ashes over his\nhead*\n\n> > [...]\n> > +static void changed_files(struct hashmap *result, const char *index_path,\n> > +\t\t\t  const char *workdir)\n> > +{\n> > +[...]\n> > +}\n> > +\n> > +#include \"dir.h\"\n> \n> Can this mid-file #include go to the top of the file?\n\nYep, thanks.\n\nIn case you are interested: You probably guessed it, it was left for a\nlater clean-up. I worked a bit over the last weeks on getting Git to build\nin Visual Studio, to be able to benefit from its quite nice features (I\nwas always a fan of Visual Studio, long before I started working at\nMicrosoft). I used the conversion of the difftool as an excuse to make use\nof this myself: I did the entire conversion in Visual Studio, reverting to\nthe old, tedious command-line driven workflow to fix the bugs identified\nby t7800-difftool.sh.\n\n> > +static int run_dir_diff(const char *extcmd, int symlinks,\n> > +\t\t\tint argc, const char **argv)\n> > +{\n> > +\tchar tmpdir[PATH_MAX];\n> > +\tstruct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;\n> > +\tstruct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;\n> > +\tstruct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;\n> > +\tstruct strbuf wtdir = STRBUF_INIT;\n> > +\tsize_t ldir_len, rdir_len, wtdir_len;\n> > +\tstruct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1);\n> > +\tconst char *workdir, *tmp;\n> > +\tint ret = 0, i;\n> > +\tFILE *fp;\n> > +\tstruct hashmap working_tree_dups, submodules, symlinks2;\n> > +\tstruct hashmap_iter iter;\n> > +\tstruct pair_entry *entry;\n> > +\tenum object_type type;\n> > +\tunsigned long size;\n> > +\tstruct index_state wtindex;\n> > +\tstruct checkout lstate, rstate;\n> > +\tint rc, flags = RUN_GIT_CMD, err = 0;\n> > +\tstruct child_process child = CHILD_PROCESS_INIT;\n> > +\tconst char *helper_argv[] = { \"difftool--helper\", NULL, NULL, NULL };\n> > +\tstruct hashmap wt_modified, tmp_modified;\n> > +\tint indices_loaded = 0;\n> > +\n> > +\tsetup_work_tree();\n> > +\tworkdir = get_git_work_tree();\n> > +\n> > +\t/* Setup temp directories */\n> > +\ttmp = getenv(\"TMPDIR\");\n> > +\tsprintf(tmpdir, \"%s/git-difftool.XXXXXX\", tmp ? tmp : \"/tmp\");\n> \n> Maybe snprintf instead?\n> \n> getenv() won't return anything longer than PATH_MAX for most\n> users, but users are weird.\n\nTrue.\n\n> > +\tif (!mkdtemp(tmpdir))\n> > +\t\treturn error(\"could not create temporary directory\");\n> \n> Mention the tmpdir here?\n\nSure thing.\n\n> > +\tstrbuf_addf(&ldir, \"%s/left/\", tmpdir);\n> > +\tstrbuf_addf(&rdir, \"%s/right/\", tmpdir);\n> > +\tstrbuf_addstr(&wtdir, workdir);\n> > +\tif (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))\n> > +\t\tstrbuf_addch(&wtdir, '/');\n> > +\tmkdir(ldir.buf, 0777);\n> > +\tmkdir(rdir.buf, 0777);\n> \n> Seeing the perl mkpath() default 0777 spelled out this way\n> makes me wonder whether 0700 would be safer.\n> \n> The mkdtemp() above is already using 0700 so it's ok, but it\n> might be worth making it consistent (later, perhaps).\n\nAh, of course! I stupidly imitated other `mkdir()` calls elsewhere, but\nthey refer to directories within the Git worktree...\n\n> > +\t/*\n> > +\t * In directory diff mode, 'git-difftool--helper' is called once\n> > +\t * to compare the a / b directories.In file diff mode, 'git diff'\n> > +\t * will invoke a separate instance of 'git-difftool--helper' for\n> > +\t * each file that changed.\n> > +\t */\n> \n> Missing space after \".\" in the comment above.\n\nYep. It was two spaces and I deleted one too many (we are so way past\nactual print, where the two spaces may have made sense...).\n\n> > +\tif (dir_diff)\n> > +\t\treturn run_dir_diff(extcmd, symlinks, argc, argv);\n> > +\treturn run_file_diff(prompt, argc, argv);\n> > +}\n> > diff --git a/git.c b/git.c\n> > index efa1059..eaa0f67 100644\n> > --- a/git.c\n> > +++ b/git.c\n> > @@ -424,6 +424,7 @@ static struct cmd_struct commands[] = {\n> >  \t{ \"diff-files\", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },\n> >  \t{ \"diff-index\", cmd_diff_index, RUN_SETUP },\n> >  \t{ \"diff-tree\", cmd_diff_tree, RUN_SETUP },\n> > +\t{ \"builtin-difftool\", cmd_builtin_difftool, RUN_SETUP | NEED_WORK_TREE },\n> >  \t{ \"fast-export\", cmd_fast_export, RUN_SETUP },\n> >  \t{ \"fetch\", cmd_fetch, RUN_SETUP },\n> >  \t{ \"fetch-pack\", cmd_fetch_pack, RUN_SETUP },\n> \n> This isn't alphabetical anymore, but it actually is if you\n> consider that the final plan is to change \"builtin-difftool\" to\n> \"difftool\".\n\nExactly, that was my thinking.\n\n> If we want to minimize that future diff we could name\n> cmd_builtin_difftool() as cmd_difftool() for consistency now so\n> that the future commit only needs to tweak the string here.\n\nYes!\n\nFor the record, this is a left-over from an impatient attempt at avoiding\nproblems with `make` overwriting the Perl version of `git difftool` by the\nbuiltin version; I had originally assumed that a list of builtins was\ngenerated from parsing git.c or builtin.h, but it turns out that the\nBUILTIN_OBJS are actually responsible, i.e. the file name.\n\nFixed.\n\nThank you for your review!\nDscho\n"},{"id":"306377","messageId":"1479912693.5181.27.camel@kaarsemaker.net","threadId":"44532","inReplyTo":"598dcfdbeef4e15d2d439053a0423589182e5f30.1479834051.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version","fromName":"Dennis Kaarsemaker","fromEmail":"dennis@kaarsemaker.net","sentAt":"2016-11-23T14:51:33Z","receivedAt":"2016-11-23T14:52:01Z","isPatch":true,"sender":{"key":"dennis@kaarsemaker.net","avatar":"https://avatars.githubusercontent.com/u/200649?v=4"},"body":"On Tue, 2016-11-22 at 18:01 +0100, Johannes Schindelin wrote:\n> The original idea was to use an environment variable\n> GIT_USE_BUILTIN_DIFFTOOL, but the test suite resets those variables, and\n> we do want to use that feature flag to run the tests with, and without,\n> the feature flag.\n> \n> Besides, the plan is to add an opt-in flag in Git for Windows'\n> installer. If we implemented the feature flag as an environment\n> variable, we would have to modify the user's environment, in order to\n> make the builtin difftool the default when called from Git Bash, Git CMD\n> or third-party tools.\n\nHi Johannes,\n\nWhy is this not a normal configuration variable (as in git config\ndifftool.builtin true or something)? It doesn't make much sense to me\nto introduce a way of configuring git by introducing magic files, when\na normal configuration variable would do just fine, and the GfW\ninstaller can also set such variables, like it does for the crlf config\nI believe.\n\n-- \nDennis Kaarsemaker\nhttp://www.kaarsemaker.net\n"},{"id":"306385","messageId":"alpine.DEB.2.20.1611231824530.3746@virtualbox","threadId":"44532","inReplyTo":"1479912693.5181.27.camel@kaarsemaker.net","subject":"Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-23T17:29:43Z","receivedAt":"2016-11-23T17:30:24Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Dennis,\n\nOn Wed, 23 Nov 2016, Dennis Kaarsemaker wrote:\n\n> On Tue, 2016-11-22 at 18:01 +0100, Johannes Schindelin wrote:\n> > The original idea was to use an environment variable\n> > GIT_USE_BUILTIN_DIFFTOOL, but the test suite resets those variables, and\n> > we do want to use that feature flag to run the tests with, and without,\n> > the feature flag.\n> > \n> > Besides, the plan is to add an opt-in flag in Git for Windows'\n> > installer. If we implemented the feature flag as an environment\n> > variable, we would have to modify the user's environment, in order to\n> > make the builtin difftool the default when called from Git Bash, Git CMD\n> > or third-party tools.\n> \n> Why is this not a normal configuration variable (as in git config\n> difftool.builtin true or something)? It doesn't make much sense to me\n> to introduce a way of configuring git by introducing magic files, when\n> a normal configuration variable would do just fine, and the GfW\n> installer can also set such variables, like it does for the crlf config\n> I believe.\n\nI considered that. Adding a config setting would mean we simply test for\nit in git-difftool.perl and call the builtin if the setting is active,\nright?\n\nThe downside is that we actually *do* go through Perl to do that. Only to\ngo back to a builtin. Which is exactly the thing I intended to avoid.\n\nIf we do not go through Perl, we have to set up the git directory and\nparse the config in git.c *just* to figure out whether we want to\nmagically forward difftool to builtin-difftool. That is not only ugly, but\nhas potential side effects I was not willing to risk.\n\nIn any case, this feature flag will be there only for one or two Git for\nWindows releases, to give early adopters a chance to send me bug reports\nabout any regressions.\n\nTo be crystal-clear: I never expected this patch to enter git.git.\n\nIn that light, I am okay with taking the heat for introducing a temporary,\nGit for Windows-only feature flag that is implemented as a \"does the file\n<xyz> exist?\" test.\n\nCiao,\nDscho\n"},{"id":"306387","messageId":"xmqqh96ym6x6.fsf@gitster.mtv.corp.google.com","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1611231824530.3746@virtualbox","subject":"Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-11-23T17:40:05Z","receivedAt":"2016-11-23T17:47:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> The downside is that we actually *do* go through Perl to do that. Only to\n> go back to a builtin. Which is exactly the thing I intended to avoid.\n>\n> If we do not go through Perl, we have to set up the git directory and\n> parse the config in git.c *just* to figure out whether we want to\n> magically forward difftool to builtin-difftool. That is not only ugly, but\n> has potential side effects I was not willing to risk.\n\nI won't accept the latter anyway, so do not worry ;-)\n\n> In any case, this feature flag will be there only for one or two Git for\n> Windows releases, to give early adopters a chance to send me bug reports\n> about any regressions.\n\nI think that is sensible.  I suspect that for early detection of\nbreakages, you do not need to invent and force people to use a\ncompletely new \"mechanism\" to switch between two implementations.\n\nCan't you route the control upon seeing \"git difftool\" to your\nexperimental \"C\" difftool and check the configuration there?  Then\nyou can decide to run_command() a non-builtin one depending what the\nconfiguration says---that way, you would incur cost of spawning Perl\nonly when you need it, no?\n\n"},{"id":"306388","messageId":"xmqqd1hmm54f.fsf@gitster.mtv.corp.google.com","threadId":"44532","inReplyTo":"xmqqh96ym6x6.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-11-23T18:18:56Z","receivedAt":"2016-11-23T18:19:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Can't you route the control upon seeing \"git difftool\" to your\n> experimental \"C\" difftool and check the configuration there?  Then\n> you can decide to run_command() a non-builtin one depending what the\n> configuration says---that way, you would incur cost of spawning Perl\n> only when you need it, no?\n\nFWIW, the approach taken by 73c2779f42 (\"builtin-am: implement\nskeletal builtin am\", 2015-08-04) is what I had in mind when I wrote\nthe above.\n"},{"id":"306390","messageId":"alpine.DEB.2.20.1611232051420.3746@virtualbox","threadId":"44532","inReplyTo":"xmqqd1hmm54f.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-23T19:55:25Z","receivedAt":"2016-11-23T19:56:46Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Wed, 23 Nov 2016, Junio C Hamano wrote:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> > Can't you route the control upon seeing \"git difftool\" to your\n> > experimental \"C\" difftool and check the configuration there?  Then\n> > you can decide to run_command() a non-builtin one depending what the\n> > configuration says---that way, you would incur cost of spawning Perl\n> > only when you need it, no?\n> \n> FWIW, the approach taken by 73c2779f42 (\"builtin-am: implement\n> skeletal builtin am\", 2015-08-04) is what I had in mind when I wrote\n> the above.\n\nMaybe that worked back then. But I doubt it, because checking out that\nrevision, I get this \"warning\":\n\nMakefile:1732: warning: overriding recipe for target 'git-am'\nMakefile:1696: warning: ignoring old recipe for target 'git-am'\n\nSeems like a matter of luck whether the `make` executable you happen to\nuse guesses what we want: to munge git-am.sh into git-am, as opposed to\nhard-linking git to git-am.\n\nCiao,\nDscho\n"},{"id":"306391","messageId":"xmqq8tsam08n.fsf@gitster.mtv.corp.google.com","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1611232051420.3746@virtualbox","subject":"Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-11-23T20:04:24Z","receivedAt":"2016-11-23T20:04:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Hi Junio,\n>\n> On Wed, 23 Nov 2016, Junio C Hamano wrote:\n>\n>> Junio C Hamano <gitster@pobox.com> writes:\n>> \n>> > Can't you route the control upon seeing \"git difftool\" to your\n>> > experimental \"C\" difftool and check the configuration there?  Then\n>> > you can decide to run_command() a non-builtin one depending what the\n>> > configuration says---that way, you would incur cost of spawning Perl\n>> > only when you need it, no?\n>> \n>> FWIW, the approach taken by 73c2779f42 (\"builtin-am: implement\n>> skeletal builtin am\", 2015-08-04) is what I had in mind when I wrote\n>> the above.\n>\n> Maybe that worked back then. But I doubt it, because checking out that\n> revision, I get this \"warning\":\n>\n> Makefile:1732: warning: overriding recipe for target 'git-am'\n> Makefile:1696: warning: ignoring old recipe for target 'git-am'\n>\n> Seems like a matter of luck whether the `make` executable you happen to\n> use guesses what we want: to munge git-am.sh into git-am, as opposed to\n> hard-linking git to git-am.\n\nYou do not need to keep two copies of \"git-cmd\", though.  commands[]\ntable can have an entry \"difftool\" that points at cmd_difftool(),\nwhich switches between run_command(\"difftool-scripted\") or makes a\nfunction call to a static difftool_builtin() that you wrote in 1/2.\n"},{"id":"306393","messageId":"alpine.DEB.2.20.1611232259090.68520@virtualbox","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1611231824530.3746@virtualbox","subject":"Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-23T22:01:28Z","receivedAt":"2016-11-23T22:01:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Dennis,\n\nOn Wed, 23 Nov 2016, Johannes Schindelin wrote:\n\n> On Wed, 23 Nov 2016, Dennis Kaarsemaker wrote:\n> \n> > On Tue, 2016-11-22 at 18:01 +0100, Johannes Schindelin wrote:\n> > > The original idea was to use an environment variable\n> > > GIT_USE_BUILTIN_DIFFTOOL, but the test suite resets those variables, and\n> > > we do want to use that feature flag to run the tests with, and without,\n> > > the feature flag.\n> > > \n> > > Besides, the plan is to add an opt-in flag in Git for Windows'\n> > > installer. If we implemented the feature flag as an environment\n> > > variable, we would have to modify the user's environment, in order to\n> > > make the builtin difftool the default when called from Git Bash, Git CMD\n> > > or third-party tools.\n> > \n> > Why is this not a normal configuration variable (as in git config\n> > difftool.builtin true or something)? It doesn't make much sense to me\n> > to introduce a way of configuring git by introducing magic files, when\n> > a normal configuration variable would do just fine, and the GfW\n> > installer can also set such variables, like it does for the crlf config\n> > I believe.\n> \n> I considered that. Adding a config setting would mean we simply test for\n> it in git-difftool.perl and call the builtin if the setting is active,\n> right?\n> \n> The downside is that we actually *do* go through Perl to do that. Only to\n> go back to a builtin. Which is exactly the thing I intended to avoid.\n\nOkay, I reconsidered. Junio's comment about how git-am did it made me\nrethink the issue: I need not keep the name \"difftool\" for the script. So\nwhat I do now is rename the Perl script to git-legacy-difftool and always\nread the config in the builtin difftool, handing off to the legacy\ndifftool unless core.useBuiltinDifftool=true.\n\nThis is an easy way to do it, and a portable and clean blueprint for\nsimilar feature-flags in the future.\n\nCiao,\nJohannes\n"},{"id":"306394","messageId":"cover.1479938494.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1479834051.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 0/1] Show Git Mailing List: a builtin difftool","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-23T22:03:10Z","receivedAt":"2016-11-23T22:03:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"I have been working on the builtin difftool for almost two weeks,\nfor two reasons:\n\n1. Perl is really not native on Windows. Not only is there a performance\n   penalty to be paid just for running Perl scripts, we also have to deal\n   with the fact that users may have different Perl installations, with\n   different options, and some other Perl installation may decide to set\n   PERL5LIB globally, wreaking havoc with Git for Windows' Perl (which we\n   have to use because almost all other Perl distributions lack the\n   Subversion bindings we need for `git svn`).\n\n2. Perl makes for a rather large reason that Git for Windows' installer\n   weighs in with >30MB. While one Perl script less does not relieve us\n   of that burden, it is one step in the right direction.\n\nThis patch serves two purposes: to ask for reviews, and to show what I\nplan to release as part of Git for Windows v2.11.0 (which is due this\ncoming Wednesday, if Git v2.11.0 is released on Tuesday, as planned).\n\nChanges since v1:\n\n- fixed the usage (pointed out by David Aguilar)\n\n- moved the stray #include \"dir.h\" to cuddle with the other #include's\n  (also pointed out by David Aguilar)\n\n- changed the `sprintf()` call to a much safer `xsnprintf()` call\n  (again, pointed out by David Aguilar)\n\n- changed an error message to include the offending path (need I say,\n  pointed out by David Aguilar?)\n\n- used more restrictive permissions for the temporary directories (once\n  again, David Aguilar's suggestion)\n\n- fixed a comment that lacked a space after a period (another fix thanks\n  to David Aguilar).\n\n- switched the opt-in feature flag triggering the use of the builtin\n  difftool to a config variable (this suggestion came from Junio\n  Hamano).\n\n- made difftool respect core.symlinks by moving the usage of\n  has_symlinks after the config was parsed.\n\n\nJohannes Schindelin (1):\n  difftool: add the builtin\n\n .gitignore                                    |   1 +\n Makefile                                      |   3 +-\n builtin.h                                     |   1 +\n builtin/difftool.c                            | 692 ++++++++++++++++++++++++++\n git-difftool.perl => git-legacy-difftool.perl |   0\n git.c                                         |   1 +\n 6 files changed, 697 insertions(+), 1 deletion(-)\n create mode 100644 builtin/difftool.c\n rename git-difftool.perl => git-legacy-difftool.perl (100%)\n\n\nbase-commit: 1e37181391e305a7ab0c382ca3c3b2de998d4138\nPublished-As: https://github.com/dscho/git/releases/tag/builtin-difftool-v2\nFetch-It-Via: git fetch https://github.com/dscho/git builtin-difftool-v2\n\nInterdiff vs v1:\n\n diff --git a/.gitignore b/.gitignore\n index 91bfd09..f96e50e 100644\n --- a/.gitignore\n +++ b/.gitignore\n @@ -1,4 +1,3 @@\n -/use-builtin-difftool\n  /GIT-BUILD-OPTIONS\n  /GIT-CFLAGS\n  /GIT-LDFLAGS\n @@ -52,7 +51,6 @@\n  /git-diff-tree\n  /git-difftool\n  /git-difftool--helper\n -/git-builtin-difftool\n  /git-describe\n  /git-fast-export\n  /git-fast-import\n @@ -78,6 +76,7 @@\n  /git-init-db\n  /git-interpret-trailers\n  /git-instaweb\n +/git-legacy-difftool\n  /git-log\n  /git-ls-files\n  /git-ls-remote\n diff --git a/Makefile b/Makefile\n index f764174..7863bc2 100644\n --- a/Makefile\n +++ b/Makefile\n @@ -527,7 +527,7 @@ SCRIPT_LIB += git-sh-setup\n  SCRIPT_LIB += git-sh-i18n\n  \n  SCRIPT_PERL += git-add--interactive.perl\n -SCRIPT_PERL += git-difftool.perl\n +SCRIPT_PERL += git-legacy-difftool.perl\n  SCRIPT_PERL += git-archimport.perl\n  SCRIPT_PERL += git-cvsexportcommit.perl\n  SCRIPT_PERL += git-cvsimport.perl\n @@ -888,7 +888,7 @@ BUILTIN_OBJS += builtin/diff-files.o\n  BUILTIN_OBJS += builtin/diff-index.o\n  BUILTIN_OBJS += builtin/diff-tree.o\n  BUILTIN_OBJS += builtin/diff.o\n -BUILTIN_OBJS += builtin/builtin-difftool.o\n +BUILTIN_OBJS += builtin/difftool.o\n  BUILTIN_OBJS += builtin/fast-export.o\n  BUILTIN_OBJS += builtin/fetch-pack.o\n  BUILTIN_OBJS += builtin/fetch.o\n diff --git a/builtin.h b/builtin.h\n index 409a61e..67f8051 100644\n --- a/builtin.h\n +++ b/builtin.h\n @@ -60,7 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix);\n  extern int cmd_diff_index(int argc, const char **argv, const char *prefix);\n  extern int cmd_diff(int argc, const char **argv, const char *prefix);\n  extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);\n -extern int cmd_builtin_difftool(int argc, const char **argv, const char *prefix);\n +extern int cmd_difftool(int argc, const char **argv, const char *prefix);\n  extern int cmd_fast_export(int argc, const char **argv, const char *prefix);\n  extern int cmd_fetch(int argc, const char **argv, const char *prefix);\n  extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);\n diff --git a/builtin/builtin-difftool.c b/builtin/difftool.c\n similarity index 95%\n rename from builtin/builtin-difftool.c\n rename to builtin/difftool.c\n index 9feefcd..f845879 100644\n --- a/builtin/builtin-difftool.c\n +++ b/builtin/difftool.c\n @@ -18,12 +18,15 @@\n  #include \"argv-array.h\"\n  #include \"strbuf.h\"\n  #include \"lockfile.h\"\n +#include \"dir.h\"\n +#include \"exec_cmd.h\"\n  \n  static char *diff_gui_tool;\n  static int trust_exit_code;\n +static int use_builtin_difftool;\n  \n -static const char * const builtin_difftool_usage[] = {\n -\tN_(\"git add [<options>] [--] <pathspec>...\"),\n +static const char *const builtin_difftool_usage[] = {\n +\tN_(\"git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]\"),\n  \tNULL\n  };\n  \n @@ -39,6 +42,11 @@ static int difftool_config(const char *var, const char *value, void *cb)\n  \t\treturn 0;\n  \t}\n  \n +\tif (!strcmp(var, \"core.usebuiltindifftool\")) {\n +\t\tuse_builtin_difftool = git_config_bool(var, value);\n +\t\treturn 0;\n +\t}\n +\n  \treturn git_default_config(var, value, cb);\n  }\n  \n @@ -227,8 +235,6 @@ static void changed_files(struct hashmap *result, const char *index_path,\n  \tstrbuf_release(&buf);\n  }\n  \n -#include \"dir.h\"\n -\n  static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)\n  {\n  \tstruct strbuf buf = STRBUF_INIT;\n @@ -282,16 +288,16 @@ static int run_dir_diff(const char *extcmd, int symlinks,\n  \n  \t/* Setup temp directories */\n  \ttmp = getenv(\"TMPDIR\");\n -\tsprintf(tmpdir, \"%s/git-difftool.XXXXXX\", tmp ? tmp : \"/tmp\");\n +\txsnprintf(tmpdir, sizeof(tmpdir), \"%s/git-difftool.XXXXXX\", tmp ? tmp : \"/tmp\");\n  \tif (!mkdtemp(tmpdir))\n -\t\treturn error(\"could not create temporary directory\");\n +\t\treturn error(\"could not create '%s'\", tmpdir);\n  \tstrbuf_addf(&ldir, \"%s/left/\", tmpdir);\n  \tstrbuf_addf(&rdir, \"%s/right/\", tmpdir);\n  \tstrbuf_addstr(&wtdir, workdir);\n  \tif (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))\n  \t\tstrbuf_addch(&wtdir, '/');\n -\tmkdir(ldir.buf, 0777);\n -\tmkdir(rdir.buf, 0777);\n +\tmkdir(ldir.buf, 0700);\n +\tmkdir(rdir.buf, 0700);\n  \n  \tmemset(&wtindex, 0, sizeof(wtindex));\n  \n @@ -606,12 +612,11 @@ static int run_file_diff(int prompt, int argc, const char **argv)\n  \texit(ret);\n  }\n  \n -int cmd_builtin_difftool(int argc, const char ** argv, const char * prefix)\n +int cmd_difftool(int argc, const char ** argv, const char * prefix)\n  {\n  \tint use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,\n  \t    tool_help = 0;\n  \tstatic char *difftool_cmd = NULL, *extcmd = NULL;\n -\n  \tstruct option builtin_difftool_options[] = {\n  \t\tOPT_BOOL('g', \"gui\", &use_gui_tool,\n  \t\t\t N_(\"use `diff.guitool` instead of `diff.tool`\")),\n @@ -638,9 +643,16 @@ int cmd_builtin_difftool(int argc, const char ** argv, const char * prefix)\n  \t\tOPT_END()\n  \t};\n  \n +\tgit_config(difftool_config, NULL);\n  \tsymlinks = has_symlinks;\n +\tif (!use_builtin_difftool) {\n +\t\tconst char *path = mkpath(\"%s/git-legacy-difftool\", git_exec_path());\n  \n -\tgit_config(difftool_config, NULL);\n +\t\tif (sane_execvp(path, (char **)argv) < 0)\n +\t\t\tdie_errno(\"could not exec %s\", path);\n +\n +\t\treturn 0;\n +\t}\n  \n  \targc = parse_options(argc, argv, prefix, builtin_difftool_options,\n  \t\t\t     builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |\n @@ -670,7 +682,7 @@ int cmd_builtin_difftool(int argc, const char ** argv, const char * prefix)\n  \n  \t/*\n  \t * In directory diff mode, 'git-difftool--helper' is called once\n -\t * to compare the a / b directories.In file diff mode, 'git diff'\n +\t * to compare the a / b directories. In file diff mode, 'git diff'\n  \t * will invoke a separate instance of 'git-difftool--helper' for\n  \t * each file that changed.\n  \t */\n diff --git a/git-difftool.perl b/git-legacy-difftool.perl\n similarity index 98%\n rename from git-difftool.perl\n rename to git-legacy-difftool.perl\n index 28e47d8..a5790d0 100755\n --- a/git-difftool.perl\n +++ b/git-legacy-difftool.perl\n @@ -23,13 +23,6 @@ use File::Temp qw(tempdir);\n  use Getopt::Long qw(:config pass_through);\n  use Git;\n  \n -if (-e Git::exec_path() . '/use-builtin-difftool') {\n -\tunshift(@ARGV, \"builtin-difftool\");\n -\tunshift(@ARGV, \"git\");\n -\texec(@ARGV);\n -\tdie(\"Could not execute builtin difftool\");\n -}\n -\n  sub usage\n  {\n  \tmy $exitcode = shift;\n diff --git a/git.c b/git.c\n index 7a0df7a..0e6bbee 100644\n --- a/git.c\n +++ b/git.c\n @@ -2,7 +2,6 @@\n  #include \"exec_cmd.h\"\n  #include \"help.h\"\n  #include \"run-command.h\"\n -#include \"dir.h\"\n  \n  const char git_usage_string[] =\n  \t\"git [--version] [--help] [-C <path>] [-c name=value]\\n\"\n @@ -425,7 +424,7 @@ static struct cmd_struct commands[] = {\n  \t{ \"diff-files\", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },\n  \t{ \"diff-index\", cmd_diff_index, RUN_SETUP },\n  \t{ \"diff-tree\", cmd_diff_tree, RUN_SETUP },\n -\t{ \"builtin-difftool\", cmd_builtin_difftool, RUN_SETUP | NEED_WORK_TREE },\n +\t{ \"difftool\", cmd_difftool, RUN_SETUP | NEED_WORK_TREE },\n  \t{ \"fast-export\", cmd_fast_export, RUN_SETUP },\n  \t{ \"fetch\", cmd_fetch, RUN_SETUP },\n  \t{ \"fetch-pack\", cmd_fetch_pack, RUN_SETUP },\n @@ -543,22 +542,6 @@ static void strip_extension(const char **argv)\n  #define strip_extension(cmd)\n  #endif\n  \n -static int use_builtin_difftool(void)\n -{\n -\tstatic int initialized, use;\n -\n -\tif (!initialized) {\n -\t\tstruct strbuf buf = STRBUF_INIT;\n -\t\tstrbuf_addf(&buf, \"%s/%s\", git_exec_path(),\n -\t\t\t    \"use-builtin-difftool\");\n -\t\tuse = file_exists(buf.buf);\n -\t\tstrbuf_release(&buf);\n -\t\tinitialized = 1;\n -\t}\n -\n -\treturn use;\n -}\n -\n  static void handle_builtin(int argc, const char **argv)\n  {\n  \tstruct argv_array args = ARGV_ARRAY_INIT;\n @@ -568,9 +551,6 @@ static void handle_builtin(int argc, const char **argv)\n  \tstrip_extension(argv);\n  \tcmd = argv[0];\n  \n -\tif (!strcmp(\"difftool\", cmd) && use_builtin_difftool())\n -\t\tcmd = \"builtin-difftool\";\n -\n  \t/* Turn \"git cmd --help\" into \"git help --exclude-guides cmd\" */\n  \tif (argc > 1 && !strcmp(argv[1], \"--help\")) {\n  \t\tint i;\n\n-- \n2.10.1.583.g721a9e0\n\n"},{"id":"306395","messageId":"3e9a2f31c779476c7524849a2e5307f2d3396fe8.1479938494.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1479938494.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 1/1] difftool: add the builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-23T22:03:15Z","receivedAt":"2016-11-23T22:03:46Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This adds a builtin difftool that represents a conversion of the current\nPerl script version of the difftool.\n\nThe motivation is that Perl scripts are not at all native on Windows,\nand that `git difftool` therefore is pretty slow on that platform, when\nthere is no good reason for it to be slow.\n\nIn addition, Perl does not really have access to Git's internals. That\nmeans that any script will always have to jump through unnecessary\nhoops.\n\nThe current version of the builtin difftool does not, however, make full\nuse of the internals but instead chooses to spawn a couple of Git\nprocesses, still, to make for an easier conversion. There remains a lot\nof room for improvement, left for a later date.\n\nNote: the original difftool is now called `git legacy-difftool`, but to\nplay it safe, it is still called by difftool unless the config setting\ncore.useBuiltinDifftool=true.\n\nThe reason: this new, experimental, builtin difftool will be shipped as\npart of Git for Windows v2.11.0, to allow for easier large-scale\ntesting, but of course as an opt-in feature.\n\nSadly, the speedup is more noticable on Linux than on Windows: a quick\ntest shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s)\n(real/user/sys) in a Linux VM, down from  (6.529s/3.112s/0.644s), while\non Windows, it is (36.064s/2.730s/7.194s), down from\n(47.637s/2.407s/6.863s). The culprit is most likely the overhead\nincurred from *still* having to shell out to mergetool-lib.sh and\ndifftool--helper.sh.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n .gitignore                                    |   1 +\n Makefile                                      |   3 +-\n builtin.h                                     |   1 +\n builtin/difftool.c                            | 692 ++++++++++++++++++++++++++\n git-difftool.perl => git-legacy-difftool.perl |   0\n git.c                                         |   1 +\n 6 files changed, 697 insertions(+), 1 deletion(-)\n create mode 100644 builtin/difftool.c\n rename git-difftool.perl => git-legacy-difftool.perl (100%)\n\ndiff --git a/.gitignore b/.gitignore\nindex 05cb58a..f96e50e 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -76,6 +76,7 @@\n /git-init-db\n /git-interpret-trailers\n /git-instaweb\n+/git-legacy-difftool\n /git-log\n /git-ls-files\n /git-ls-remote\ndiff --git a/Makefile b/Makefile\nindex f53fcc9..7863bc2 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -527,7 +527,7 @@ SCRIPT_LIB += git-sh-setup\n SCRIPT_LIB += git-sh-i18n\n \n SCRIPT_PERL += git-add--interactive.perl\n-SCRIPT_PERL += git-difftool.perl\n+SCRIPT_PERL += git-legacy-difftool.perl\n SCRIPT_PERL += git-archimport.perl\n SCRIPT_PERL += git-cvsexportcommit.perl\n SCRIPT_PERL += git-cvsimport.perl\n@@ -888,6 +888,7 @@ BUILTIN_OBJS += builtin/diff-files.o\n BUILTIN_OBJS += builtin/diff-index.o\n BUILTIN_OBJS += builtin/diff-tree.o\n BUILTIN_OBJS += builtin/diff.o\n+BUILTIN_OBJS += builtin/difftool.o\n BUILTIN_OBJS += builtin/fast-export.o\n BUILTIN_OBJS += builtin/fetch-pack.o\n BUILTIN_OBJS += builtin/fetch.o\ndiff --git a/builtin.h b/builtin.h\nindex b9122bc..67f8051 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix);\n extern int cmd_diff_index(int argc, const char **argv, const char *prefix);\n extern int cmd_diff(int argc, const char **argv, const char *prefix);\n extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);\n+extern int cmd_difftool(int argc, const char **argv, const char *prefix);\n extern int cmd_fast_export(int argc, const char **argv, const char *prefix);\n extern int cmd_fetch(int argc, const char **argv, const char *prefix);\n extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nnew file mode 100644\nindex 0000000..f845879\n--- /dev/null\n+++ b/builtin/difftool.c\n@@ -0,0 +1,692 @@\n+/*\n+ * \"git difftool\" builtin command\n+ *\n+ * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible\n+ * git-difftool--helper script.\n+ *\n+ * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.\n+ * The GIT_DIFF* variables are exported for use by git-difftool--helper.\n+ *\n+ * Any arguments that are unknown to this script are forwarded to 'git diff'.\n+ *\n+ * Copyright (C) 2016 Johannes Schindelin\n+ */\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"parse-options.h\"\n+#include \"run-command.h\"\n+#include \"argv-array.h\"\n+#include \"strbuf.h\"\n+#include \"lockfile.h\"\n+#include \"dir.h\"\n+#include \"exec_cmd.h\"\n+\n+static char *diff_gui_tool;\n+static int trust_exit_code;\n+static int use_builtin_difftool;\n+\n+static const char *const builtin_difftool_usage[] = {\n+\tN_(\"git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]\"),\n+\tNULL\n+};\n+\n+static int difftool_config(const char *var, const char *value, void *cb)\n+{\n+\tif (!strcmp(var, \"diff.guitool\")) {\n+\t\tdiff_gui_tool = xstrdup(value);\n+\t\treturn 0;\n+\t}\n+\n+\tif (!strcmp(var, \"difftool.trustexitcode\")) {\n+\t\ttrust_exit_code = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n+\tif (!strcmp(var, \"core.usebuiltindifftool\")) {\n+\t\tuse_builtin_difftool = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n+\treturn git_default_config(var, value, cb);\n+}\n+\n+static int print_tool_help(void)\n+{\n+\tconst char *argv[] = { \"mergetool\", \"--tool-help=diff\", NULL };\n+\treturn run_command_v_opt(argv, RUN_GIT_CMD);\n+}\n+\n+static int parse_index_info(char *p, int *mode1, int *mode2,\n+\t\t\t    struct object_id *oid1, struct object_id *oid2,\n+\t\t\t    char *status)\n+{\n+\tif (*p != ':')\n+\t\treturn error(\"expected ':', got '%c'\", *p);\n+\t*mode1 = (int)strtol(p + 1, &p, 8);\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\t*mode2 = (int)strtol(p + 1, &p, 8);\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\tif (get_oid_hex(++p, oid1))\n+\t\treturn error(\"expected object ID, got '%s'\", p + 1);\n+\tp += GIT_SHA1_HEXSZ;\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\tif (get_oid_hex(++p, oid2))\n+\t\treturn error(\"expected object ID, got '%s'\", p + 1);\n+\tp += GIT_SHA1_HEXSZ;\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\t*status = *++p;\n+\tif (!status || p[1])\n+\t\treturn error(\"unexpected trailer: '%s'\", p);\n+\treturn 0;\n+}\n+\n+/*\n+ * Remove any trailing slash from $workdir\n+ * before starting to avoid double slashes in symlink targets.\n+ */\n+static void add_path(struct strbuf *buf, size_t base_len, const char *path)\n+{\n+\tstrbuf_setlen(buf, base_len);\n+\tif (buf->len && buf->buf[buf->len - 1] != '/')\n+\t\tstrbuf_addch(buf, '/');\n+\tstrbuf_addstr(buf, path);\n+}\n+\n+/*\n+ * Determine whether we can simply reuse the file in the worktree.\n+ */\n+static int use_wt_file(const char *workdir, const char *name,\n+\t\t       struct object_id *oid)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct stat st;\n+\tint use = 0;\n+\n+\tstrbuf_addstr(&buf, workdir);\n+\tadd_path(&buf, buf.len, name);\n+\n+\tif (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) {\n+\t\tstruct object_id wt_oid;\n+\t\tint fd = open(buf.buf, O_RDONLY);\n+\n+\t\tif (!index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {\n+\t\t\tif (is_null_oid(oid)) {\n+\t\t\t\toidcpy(oid, &wt_oid);\n+\t\t\t\tuse = 1;\n+\t\t\t} else if (!oidcmp(oid, &wt_oid))\n+\t\t\t\tuse = 1;\n+\t\t}\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn use;\n+}\n+\n+struct working_tree_entry {\n+\tstruct hashmap_entry entry;\n+\tchar path[FLEX_ARRAY];\n+};\n+\n+static int working_tree_entry_cmp(struct working_tree_entry *a,\n+\t\t\t\t  struct working_tree_entry *b, void *keydata)\n+{\n+\treturn strcmp(a->path, b->path);\n+}\n+\n+/*\n+ * The `left` and `right` entries hold paths for the symlinks hashmap,\n+ * and a SHA-1 surrounded by brief text for submodules.\n+ */\n+struct pair_entry {\n+\tstruct hashmap_entry entry;\n+\tchar left[PATH_MAX], right[PATH_MAX];\n+\tconst char path[FLEX_ARRAY];\n+};\n+\n+static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata)\n+{\n+\treturn strcmp(a->path, b->path);\n+}\n+\n+static void add_left_or_right(struct hashmap *map, const char *path,\n+\t\t\t      const char *content, int is_right)\n+{\n+\tstruct pair_entry *e, *existing;\n+\n+\tFLEX_ALLOC_STR(e, path, path);\n+\thashmap_entry_init(e, strhash(path));\n+\texisting = hashmap_get(map, e, NULL);\n+\tif (existing) {\n+\t\tfree(e);\n+\t\te = existing;\n+\t} else {\n+\t\te->left[0] = e->right[0] = '\\0';\n+\t\thashmap_add(map, e);\n+\t}\n+\tstrcpy(is_right ? e->right : e->left, content);\n+}\n+\n+struct path_entry {\n+\tstruct hashmap_entry entry;\n+\tchar path[FLEX_ARRAY];\n+};\n+\n+int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key)\n+{\n+\treturn strcmp(a->path, key ? key : b->path);\n+}\n+\n+static void changed_files(struct hashmap *result, const char *index_path,\n+\t\t\t  const char *workdir)\n+{\n+\tstruct child_process update_index = CHILD_PROCESS_INIT;\n+\tstruct child_process diff_files = CHILD_PROCESS_INIT;\n+\tstruct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT;\n+\tconst char *git_dir = absolute_path(get_git_dir()), *env[] = {\n+\t\tNULL, NULL\n+\t};\n+\tFILE *fp;\n+\n+\tstrbuf_addf(&index_env, \"GIT_INDEX_FILE=%s\", index_path);\n+\tenv[0] = index_env.buf;\n+\n+\targv_array_pushl(&update_index.args,\n+\t\t\t \"--git-dir\", git_dir, \"--work-tree\", workdir,\n+\t\t\t \"update-index\", \"--really-refresh\", \"-q\",\n+\t\t\t \"--unmerged\", NULL);\n+\tupdate_index.no_stdin = 1;\n+\tupdate_index.no_stdout = 1;\n+\tupdate_index.no_stderr = 1;\n+\tupdate_index.git_cmd = 1;\n+\tupdate_index.use_shell = 0;\n+\tupdate_index.clean_on_exit = 1;\n+\tupdate_index.dir = workdir;\n+\tupdate_index.env = env;\n+\t/* Ignore any errors of update-index */\n+\trun_command(&update_index);\n+\n+\targv_array_pushl(&diff_files.args,\n+\t\t\t \"--git-dir\", git_dir, \"--work-tree\", workdir,\n+\t\t\t \"diff-files\", \"--name-only\", \"-z\", NULL);\n+\tdiff_files.no_stdin = 1;\n+\tdiff_files.git_cmd = 1;\n+\tdiff_files.use_shell = 0;\n+\tdiff_files.clean_on_exit = 1;\n+\tdiff_files.out = -1;\n+\tdiff_files.dir = workdir;\n+\tdiff_files.env = env;\n+\tif (start_command(&diff_files))\n+\t\tdie(\"could not obtain raw diff\");\n+\tfp = xfdopen(diff_files.out, \"r\");\n+\twhile (!strbuf_getline_nul(&buf, fp)) {\n+\t\tstruct path_entry *entry;\n+\t\tFLEX_ALLOC_STR(entry, path, buf.buf);\n+\t\thashmap_entry_init(entry, strhash(buf.buf));\n+\t\thashmap_add(result, entry);\n+\t}\n+\tif (finish_command(&diff_files))\n+\t\tdie(\"diff-files did not exit properly\");\n+\tstrbuf_release(&index_env);\n+\tstrbuf_release(&buf);\n+}\n+\n+static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstrbuf_addstr(&buf, tmpdir);\n+\tremove_dir_recursively(&buf, 0);\n+\tif (exit_code)\n+\t\twarning(_(\"failed: %d\"), exit_code);\n+\texit(exit_code);\n+}\n+\n+static int ensure_leading_directories(char *path)\n+{\n+\tswitch (safe_create_leading_directories(path)) {\n+\t\tcase SCLD_OK:\n+\t\tcase SCLD_EXISTS:\n+\t\t\treturn 0;\n+\t\tdefault:\n+\t\t\treturn error(_(\"could not create leading directories \"\n+\t\t\t\t       \"of '%s'\"), path);\n+\t}\n+}\n+\n+static int run_dir_diff(const char *extcmd, int symlinks,\n+\t\t\tint argc, const char **argv)\n+{\n+\tchar tmpdir[PATH_MAX];\n+\tstruct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;\n+\tstruct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;\n+\tstruct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;\n+\tstruct strbuf wtdir = STRBUF_INIT;\n+\tsize_t ldir_len, rdir_len, wtdir_len;\n+\tstruct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1);\n+\tconst char *workdir, *tmp;\n+\tint ret = 0, i;\n+\tFILE *fp;\n+\tstruct hashmap working_tree_dups, submodules, symlinks2;\n+\tstruct hashmap_iter iter;\n+\tstruct pair_entry *entry;\n+\tenum object_type type;\n+\tunsigned long size;\n+\tstruct index_state wtindex;\n+\tstruct checkout lstate, rstate;\n+\tint rc, flags = RUN_GIT_CMD, err = 0;\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\tconst char *helper_argv[] = { \"difftool--helper\", NULL, NULL, NULL };\n+\tstruct hashmap wt_modified, tmp_modified;\n+\tint indices_loaded = 0;\n+\n+\tsetup_work_tree();\n+\tworkdir = get_git_work_tree();\n+\n+\t/* Setup temp directories */\n+\ttmp = getenv(\"TMPDIR\");\n+\txsnprintf(tmpdir, sizeof(tmpdir), \"%s/git-difftool.XXXXXX\", tmp ? tmp : \"/tmp\");\n+\tif (!mkdtemp(tmpdir))\n+\t\treturn error(\"could not create '%s'\", tmpdir);\n+\tstrbuf_addf(&ldir, \"%s/left/\", tmpdir);\n+\tstrbuf_addf(&rdir, \"%s/right/\", tmpdir);\n+\tstrbuf_addstr(&wtdir, workdir);\n+\tif (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))\n+\t\tstrbuf_addch(&wtdir, '/');\n+\tmkdir(ldir.buf, 0700);\n+\tmkdir(rdir.buf, 0700);\n+\n+\tmemset(&wtindex, 0, sizeof(wtindex));\n+\n+\tmemset(&lstate, 0, sizeof(lstate));\n+\tlstate.base_dir = ldir.buf;\n+\tlstate.base_dir_len = ldir.len;\n+\tlstate.force = 1;\n+\tmemset(&rstate, 0, sizeof(rstate));\n+\trstate.base_dir = rdir.buf;\n+\trstate.base_dir_len = rdir.len;\n+\trstate.force = 1;\n+\n+\tldir_len = ldir.len;\n+\trdir_len = rdir.len;\n+\twtdir_len = wtdir.len;\n+\n+\thashmap_init(&working_tree_dups,\n+\t\t     (hashmap_cmp_fn)working_tree_entry_cmp, 0);\n+\thashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0);\n+\thashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0);\n+\n+\tchild.no_stdin = 1;\n+\tchild.git_cmd = 1;\n+\tchild.use_shell = 0;\n+\tchild.clean_on_exit = 1;\n+\tchild.out = -1;\n+\targv_array_pushl(&child.args, \"diff\", \"--raw\", \"--no-abbrev\", \"-z\",\n+\t\t\t NULL);\n+\tfor (i = 0; i < argc; i++)\n+\t\targv_array_push(&child.args, argv[i]);\n+\tif (start_command(&child))\n+\t\tdie(\"could not obtain raw diff\");\n+\tfp = xfdopen(child.out, \"r\");\n+\n+\t/* Build index info for left and right sides of the diff */\n+\twhile (!strbuf_getline_nul(&info, fp)) {\n+\t\tint lmode, rmode;\n+\t\tstruct object_id loid, roid;\n+\t\tchar status;\n+\t\tconst char *src_path, *dst_path;\n+\t\tsize_t src_path_len, dst_path_len;\n+\n+\t\tif (starts_with(info.buf, \"::\"))\n+\t\t\tdie(N_(\"combined diff formats('-c' and '--cc') are \"\n+\t\t\t       \"not supported in\\n\"\n+\t\t\t       \"directory diff mode('-d' and '--dir-diff').\"));\n+\n+\t\tif (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,\n+\t\t\t\t     &status))\n+\t\t\tbreak;\n+\t\tif (strbuf_getline_nul(&lpath, fp))\n+\t\t\tbreak;\n+\t\tsrc_path = lpath.buf;\n+\t\tsrc_path_len = lpath.len;\n+\n+\t\tif (status != 'C' && status != 'R') {\n+\t\t\tdst_path = src_path;\n+\t\t\tdst_path_len = src_path_len;\n+\t\t} else {\n+\t\t\tif (strbuf_getline_nul(&rpath, fp))\n+\t\t\t\tbreak;\n+\t\t\tdst_path = rpath.buf;\n+\t\t\tdst_path_len = rpath.len;\n+\t\t}\n+\n+\t\tif (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) {\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"Subproject commit %s\",\n+\t\t\t\t    oid_to_hex(&loid));\n+\t\t\tadd_left_or_right(&submodules, src_path, buf.buf, 0);\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"Subproject commit %s\",\n+\t\t\t\t    oid_to_hex(&roid));\n+\t\t\tif (!oidcmp(&loid, &roid))\n+\t\t\t\tstrbuf_addstr(&buf, \"-dirty\");\n+\t\t\tadd_left_or_right(&submodules, dst_path, buf.buf, 1);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (S_ISLNK(lmode)) {\n+\t\t\tchar *content = read_sha1_file(loid.hash, &type, &size);\n+\t\t\tadd_left_or_right(&symlinks2, src_path, content, 0);\n+\t\t\tfree(content);\n+\t\t}\n+\n+\t\tif (S_ISLNK(rmode)) {\n+\t\t\tchar *content = read_sha1_file(roid.hash, &type, &size);\n+\t\t\tadd_left_or_right(&symlinks2, dst_path, content, 1);\n+\t\t\tfree(content);\n+\t\t}\n+\n+\t\tif (lmode && status != 'C') {\n+\t\t\tce->ce_mode = lmode;\n+\t\t\toidcpy(&ce->oid, &loid);\n+\t\t\tstrcpy(ce->name, src_path);\n+\t\t\tce->ce_namelen = src_path_len;\n+\t\t\tif (checkout_entry(ce, &lstate, NULL))\n+\t\t\t\treturn error(\"could not write '%s'\", src_path);\n+\t\t}\n+\n+\t\tif (rmode) {\n+\t\t\tstruct working_tree_entry *entry;\n+\n+\t\t\t/* Avoid duplicate working_tree entries */\n+\t\t\tFLEX_ALLOC_STR(entry, path, dst_path);\n+\t\t\thashmap_entry_init(entry, strhash(dst_path));\n+\t\t\tif (hashmap_get(&working_tree_dups, entry, NULL)) {\n+\t\t\t\tfree(entry);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\thashmap_add(&working_tree_dups, entry);\n+\n+\t\t\tif (!use_wt_file(workdir, dst_path, &roid)) {\n+\t\t\t\tce->ce_mode = rmode;\n+\t\t\t\toidcpy(&ce->oid, &roid);\n+\t\t\t\tstrcpy(ce->name, dst_path);\n+\t\t\t\tce->ce_namelen = dst_path_len;\n+\t\t\t\tif (checkout_entry(ce, &rstate, NULL))\n+\t\t\t\t\treturn error(\"could not write '%s'\",\n+\t\t\t\t\t\t     dst_path);\n+\t\t\t} else if (!is_null_oid(&roid)) {\n+\t\t\t\t/*\n+\t\t\t\t * Changes in the working tree need special\n+\t\t\t\t * treatment since they are not part of the\n+\t\t\t\t * index.\n+\t\t\t\t */\n+\t\t\t\tstruct cache_entry *ce2 =\n+\t\t\t\t\tmake_cache_entry(rmode, roid.hash,\n+\t\t\t\t\t\t\t dst_path, 0, 0);\n+\t\t\t\tce_mode_from_stat(ce2, rmode);\n+\n+\t\t\t\tadd_index_entry(&wtindex, ce2,\n+\t\t\t\t\t\tADD_CACHE_JUST_APPEND);\n+\n+\t\t\t\tadd_path(&wtdir, wtdir_len, dst_path);\n+\t\t\t\tadd_path(&rdir, rdir_len, dst_path);\n+\t\t\t\tif (ensure_leading_directories(rdir.buf))\n+\t\t\t\t\treturn error(\"could not create \"\n+\t\t\t\t\t\t     \"directory for '%s'\",\n+\t\t\t\t\t\t     dst_path);\n+\t\t\t\tif (symlinks) {\n+\t\t\t\t\tif (symlink(wtdir.buf, rdir.buf)) {\n+\t\t\t\t\t\tret = error_errno(\"could not symlink '%s' to '%s'\", wtdir.buf, rdir.buf);\n+\t\t\t\t\t\tgoto finish;\n+\t\t\t\t\t}\n+\t\t\t\t} else {\n+\t\t\t\t\tstruct stat st;\n+\t\t\t\t\tif (stat(wtdir.buf, &st))\n+\t\t\t\t\t\tst.st_mode = 0644;\n+\t\t\t\t\tif (copy_file(rdir.buf, wtdir.buf,\n+\t\t\t\t\t\t      st.st_mode)) {\n+\t\t\t\t\t\tret = error(\"could not copy '%s' to '%s'\", wtdir.buf, rdir.buf);\n+\t\t\t\t\t\tgoto finish;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t}\n+\tif (finish_command(&child)) {\n+\t\tret = error(\"error occurred running diff --raw\");\n+\t\tgoto finish;\n+\t}\n+\n+\t/*\n+\t * Changes to submodules require special treatment.This loop writes a\n+\t * temporary file to both the left and right directories to show the\n+\t * change in the recorded SHA1 for the submodule.\n+\t */\n+\thashmap_iter_init(&submodules, &iter);\n+\twhile ((entry = hashmap_iter_next(&iter))) {\n+\t\tif (*entry->left) {\n+\t\t\tadd_path(&ldir, ldir_len, entry->path);\n+\t\t\tensure_leading_directories(ldir.buf);\n+\t\t\twrite_file(ldir.buf, \"%s\", entry->left);\n+\t\t}\n+\t\tif (*entry->right) {\n+\t\t\tadd_path(&rdir, rdir_len, entry->path);\n+\t\t\tensure_leading_directories(rdir.buf);\n+\t\t\twrite_file(rdir.buf, \"%s\", entry->right);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Symbolic links require special treatment.The standard \"git diff\"\n+\t * shows only the link itself, not the contents of the link target.\n+\t * This loop replicates that behavior.\n+\t */\n+\thashmap_iter_init(&symlinks2, &iter);\n+\twhile ((entry = hashmap_iter_next(&iter))) {\n+\t\tif (*entry->left) {\n+\t\t\tadd_path(&ldir, ldir_len, entry->path);\n+\t\t\tensure_leading_directories(ldir.buf);\n+\t\t\twrite_file(ldir.buf, \"%s\", entry->left);\n+\t\t}\n+\t\tif (*entry->right) {\n+\t\t\tadd_path(&rdir, rdir_len, entry->path);\n+\t\t\tensure_leading_directories(rdir.buf);\n+\t\t\twrite_file(rdir.buf, \"%s\", entry->right);\n+\t\t}\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\tstrbuf_setlen(&ldir, ldir_len);\n+\thelper_argv[1] = ldir.buf;\n+\tstrbuf_setlen(&rdir, rdir_len);\n+\thelper_argv[2] = rdir.buf;\n+\n+\tif (extcmd) {\n+\t\thelper_argv[0] = extcmd;\n+\t\tflags = 0;\n+\t} else\n+\t\tsetenv(\"GIT_DIFFTOOL_DIRDIFF\", \"true\", 1);\n+\trc = run_command_v_opt(helper_argv, flags);\n+\n+\t/*\n+\t * If the diff includes working copy files and those\n+\t * files were modified during the diff, then the changes\n+\t * should be copied back to the working tree.\n+\t * Do not copy back files when symlinks are used and the\n+\t * external tool did not replace the original link with a file.\n+\t *\n+\t * These hashes are loaded lazily since they aren't needed\n+\t * in the common case of --symlinks and the difftool updating\n+\t * files through the symlink.\n+\t */\n+\thashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp,\n+\t\t     wtindex.cache_nr);\n+\thashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp,\n+\t\t     wtindex.cache_nr);\n+\n+\tfor (i = 0; i < wtindex.cache_nr; i++) {\n+\t\tstruct hashmap_entry dummy;\n+\t\tconst char *name = wtindex.cache[i]->name;\n+\t\tstruct stat st;\n+\n+\t\tadd_path(&rdir, rdir_len, name);\n+\t\tif (lstat(rdir.buf, &st))\n+\t\t\tcontinue;\n+\n+\t\tif ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode))\n+\t\t\tcontinue;\n+\n+\t\tif (!indices_loaded) {\n+\t\t\tstatic struct lock_file lock;\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"%s/wtindex\", tmpdir);\n+\t\t\tif (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||\n+\t\t\t    write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {\n+\t\t\t\tret = error(\"could not write %s\", buf.buf);\n+\t\t\t\trollback_lock_file(&lock);\n+\t\t\t\tgoto finish;\n+\t\t\t}\n+\t\t\tchanged_files(&wt_modified, buf.buf, workdir);\n+\t\t\tstrbuf_setlen(&rdir, rdir_len);\n+\t\t\tchanged_files(&tmp_modified, buf.buf, rdir.buf);\n+\t\t\tadd_path(&rdir, rdir_len, name);\n+\t\t\tindices_loaded = 1;\n+\t\t}\n+\n+\t\thashmap_entry_init(&dummy, strhash(name));\n+\t\tif (hashmap_get(&tmp_modified, &dummy, name)) {\n+\t\t\tadd_path(&wtdir, wtdir_len, name);\n+\t\t\tif (hashmap_get(&wt_modified, &dummy, name)) {\n+\t\t\t\twarning(_(\"both files modified: '%s' and '%s'.\"),\n+\t\t\t\t\twtdir.buf, rdir.buf);\n+\t\t\t\twarning(_(\"working tree file has been left.\"));\n+\t\t\t\twarning(\"\");\n+\t\t\t\terr = 1;\n+\t\t\t} else if (unlink(wtdir.buf) ||\n+\t\t\t\t   copy_file(wtdir.buf, rdir.buf, st.st_mode))\n+\t\t\t\twarning_errno(_(\"could not copy '%s' to '%s'\"),\n+\t\t\t\t\t      rdir.buf, wtdir.buf);\n+\t\t}\n+\t}\n+\n+\tif (err) {\n+\t\twarning(_(\"temporary files exist in '%s'.\"), tmpdir);\n+\t\twarning(_(\"you may want to cleanup or recover these.\"));\n+\t\texit(1);\n+\t} else\n+\t\texit_cleanup(tmpdir, rc);\n+\n+finish:\n+\tfree(ce);\n+\tstrbuf_release(&ldir);\n+\tstrbuf_release(&rdir);\n+\tstrbuf_release(&wtdir);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+static int run_file_diff(int prompt, int argc, const char **argv)\n+{\n+\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\tconst char *env[] = {\n+\t\t\"GIT_PAGER=\", \"GIT_EXTERNAL_DIFF=git-difftool--helper\", NULL,\n+\t\tNULL\n+\t};\n+\tint ret = 0, i;\n+\n+\tif (prompt > 0)\n+\t\tenv[2] = \"GIT_DIFFTOOL_PROMPT=true\";\n+\telse if (!prompt)\n+\t\tenv[2] = \"GIT_DIFFTOOL_NO_PROMPT=true\";\n+\n+\targv_array_push(&args, \"diff\");\n+\tfor (i = 0; i < argc; i++)\n+\t\targv_array_push(&args, argv[i]);\n+\tret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, NULL, env);\n+\texit(ret);\n+}\n+\n+int cmd_difftool(int argc, const char ** argv, const char * prefix)\n+{\n+\tint use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,\n+\t    tool_help = 0;\n+\tstatic char *difftool_cmd = NULL, *extcmd = NULL;\n+\tstruct option builtin_difftool_options[] = {\n+\t\tOPT_BOOL('g', \"gui\", &use_gui_tool,\n+\t\t\t N_(\"use `diff.guitool` instead of `diff.tool`\")),\n+\t\tOPT_BOOL('d', \"dir-diff\", &dir_diff,\n+\t\t\t N_(\"perform a full-directory diff\")),\n+\t\t{ OPTION_SET_INT, 'y', \"no-prompt\", &prompt, NULL,\n+\t\t\tN_(\"do not prompt before launching a diff tool\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},\n+\t\t{ OPTION_SET_INT, 0, \"prompt\", &prompt, NULL, NULL,\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n+\t\t\tNULL, 1 },\n+\t\tOPT_BOOL(0, \"symlinks\", &symlinks,\n+\t\t\t N_(\"use symlinks in dir-diff mode\")),\n+\t\tOPT_STRING('t', \"tool\", &difftool_cmd, N_(\"<tool>\"),\n+\t\t\t   N_(\"use the specified diff tool\")),\n+\t\tOPT_BOOL(0, \"tool-help\", &tool_help,\n+\t\t\t N_(\"print a list of diff tools that may be used with \"\n+\t\t\t    \"`--tool`\")),\n+\t\tOPT_BOOL(0, \"trust-exit-code\", &trust_exit_code,\n+\t\t\t N_(\"make 'git-difftool' exit when an invoked diff \"\n+\t\t\t    \"tool returns a non - zero exit code\")),\n+\t\tOPT_STRING('x', \"extcmd\", &extcmd, N_(\"<command>\"),\n+\t\t\t   N_(\"specify a custom command for viewing diffs\")),\n+\t\tOPT_END()\n+\t};\n+\n+\tgit_config(difftool_config, NULL);\n+\tsymlinks = has_symlinks;\n+\tif (!use_builtin_difftool) {\n+\t\tconst char *path = mkpath(\"%s/git-legacy-difftool\", git_exec_path());\n+\n+\t\tif (sane_execvp(path, (char **)argv) < 0)\n+\t\t\tdie_errno(\"could not exec %s\", path);\n+\n+\t\treturn 0;\n+\t}\n+\n+\targc = parse_options(argc, argv, prefix, builtin_difftool_options,\n+\t\t\t     builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n+\n+\tif (tool_help)\n+\t\treturn print_tool_help();\n+\n+\tif (use_gui_tool && diff_gui_tool && *diff_gui_tool)\n+\t\tsetenv(\"GIT_DIFF_TOOL\", diff_gui_tool, 1);\n+\telse if (difftool_cmd) {\n+\t\tif (*difftool_cmd)\n+\t\t\tsetenv(\"GIT_DIFF_TOOL\", difftool_cmd, 1);\n+\t\telse\n+\t\t\tdie(_(\"no <tool> given for --tool=<tool>\"));\n+\t}\n+\n+\tif (extcmd) {\n+\t\tif (*extcmd)\n+\t\t\tsetenv(\"GIT_DIFFTOOL_EXTCMD\", extcmd, 1);\n+\t\telse\n+\t\t\tdie(_(\"no <cmd> given for --extcmd=<cmd>\"));\n+\t}\n+\n+\tsetenv(\"GIT_DIFFTOOL_TRUST_EXIT_CODE\",\n+\t       trust_exit_code ? \"true\" : \"false\", 1);\n+\n+\t/*\n+\t * In directory diff mode, 'git-difftool--helper' is called once\n+\t * to compare the a / b directories. In file diff mode, 'git diff'\n+\t * will invoke a separate instance of 'git-difftool--helper' for\n+\t * each file that changed.\n+\t */\n+\tif (dir_diff)\n+\t\treturn run_dir_diff(extcmd, symlinks, argc, argv);\n+\treturn run_file_diff(prompt, argc, argv);\n+}\ndiff --git a/git-difftool.perl b/git-legacy-difftool.perl\nsimilarity index 100%\nrename from git-difftool.perl\nrename to git-legacy-difftool.perl\ndiff --git a/git.c b/git.c\nindex efa1059..0e6bbee 100644\n--- a/git.c\n+++ b/git.c\n@@ -424,6 +424,7 @@ static struct cmd_struct commands[] = {\n \t{ \"diff-files\", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"diff-index\", cmd_diff_index, RUN_SETUP },\n \t{ \"diff-tree\", cmd_diff_tree, RUN_SETUP },\n+\t{ \"difftool\", cmd_difftool, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"fast-export\", cmd_fast_export, RUN_SETUP },\n \t{ \"fetch\", cmd_fetch, RUN_SETUP },\n \t{ \"fetch-pack\", cmd_fetch_pack, RUN_SETUP },\n-- \n2.10.1.583.g721a9e0\n"},{"id":"306396","messageId":"xmqqwpftltq9.fsf@gitster.mtv.corp.google.com","threadId":"44532","inReplyTo":"3e9a2f31c779476c7524849a2e5307f2d3396fe8.1479938494.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 1/1] difftool: add the builtin","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-11-23T22:25:02Z","receivedAt":"2016-11-23T22:25:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> +\tif (!strcmp(var, \"core.usebuiltindifftool\")) {\n> +\t\tuse_builtin_difftool = git_config_bool(var, value);\n> +\t\treturn 0;\n> +\t}\n\nThis no way belongs to the core set; difftool.usebuiltin would be\nmore appropriate.\n\n> +\tif (!use_builtin_difftool) {\n> +\t\tconst char *path = mkpath(\"%s/git-legacy-difftool\", git_exec_path());\n> +\n> +\t\tif (sane_execvp(path, (char **)argv) < 0)\n> +\t\t\tdie_errno(\"could not exec %s\", path);\n> +\n> +\t\treturn 0;\n> +\t}\n> + ...\n> diff --git a/git-difftool.perl b/git-legacy-difftool.perl\n> similarity index 100%\n> rename from git-difftool.perl\n> rename to git-legacy-difftool.perl\n> diff --git a/git.c b/git.c\n> index efa1059..0e6bbee 100644\n> --- a/git.c\n> +++ b/git.c\n> @@ -424,6 +424,7 @@ static struct cmd_struct commands[] = {\n>  \t{ \"diff-files\", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },\n>  \t{ \"diff-index\", cmd_diff_index, RUN_SETUP },\n>  \t{ \"diff-tree\", cmd_diff_tree, RUN_SETUP },\n> +\t{ \"difftool\", cmd_difftool, RUN_SETUP | NEED_WORK_TREE },\n\nRunning set-up would mean that the spawning of legacy-difftool would\nbe done after you chdir(2) up to the root level of the working tree,\nno?  I do not think you can safely add these two bits here until the\nmigration completes.\n\nI doubt that setting core.usebuiltindifftool to false and running\nthe tool from a subdirectory and a pathspec work correctly with this\npatch.  If running difftool from a subdirectory with a pathspec is\nnot tested in t7800, perhaps we should.\n\nIt is nice that we can now lose PERL prerequisite from t7800 ;-)\n\nThanks.\n"},{"id":"306397","messageId":"xmqqshqhlthu.fsf@gitster.mtv.corp.google.com","threadId":"44532","inReplyTo":"xmqqwpftltq9.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v2 1/1] difftool: add the builtin","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-11-23T22:30:05Z","receivedAt":"2016-11-23T22:31:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> ... I do not think you can safely add these two bits here until the\n> migration completes.\n\nI accidentally removed a more useful bit I wrote after the above\nsentence while editing.\n\nThe NEEDSWORK comment in 73c2779f42 (\"builtin-am: implement skeletal\nbuiltin am\", 2015-08-04) mentions why it calls setup-git-directory\nand setup-work-tree instead of letting run_builtin() do so; perhaps\nyou can do something similar here to fix this.\n\n> I doubt that setting core.usebuiltindifftool to false and running\n> the tool from a subdirectory and a pathspec work correctly with this\n> patch.  If running difftool from a subdirectory with a pathspec is\n> not tested in t7800, perhaps we should.\n>\n> It is nice that we can now lose PERL prerequisite from t7800 ;-)\n>\n> Thanks.\n"},{"id":"306403","messageId":"alpine.DEB.2.20.1611241123340.117539@virtualbox","threadId":"44532","inReplyTo":"xmqqshqhlthu.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v2 1/1] difftool: add the builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-24T10:38:06Z","receivedAt":"2016-11-24T10:38:21Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Wed, 23 Nov 2016, Junio C Hamano wrote:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> > ... I do not think you can safely add these two bits here until the\n> > migration completes.\n> \n> I accidentally removed a more useful bit I wrote after the above\n> sentence while editing.\n> \n> The NEEDSWORK comment in 73c2779f42 (\"builtin-am: implement skeletal\n> builtin am\", 2015-08-04) mentions why it calls setup-git-directory\n> and setup-work-tree instead of letting run_builtin() do so; perhaps\n> you can do something similar here to fix this.\n\nThis is the Catch-22 I mentioned a couple times: if you insist on a config\nsetting, the config has to be read. For that to work,\nsetup_git_directory() has to be called.\n\nSo no matter what you do, if you want to have conditional code that\ndepends on the config, and that wants setup_git_directory() *not* to be\ncalled before, you are simply out of luck.\n\nSadly, I now bought into your comment that using a file in exec-path as a\nfeature flag is a bad thing, and that we have to use a config setting. So\nnow I have to spend more time on fixing something that was not a problem\nin my original patches.\n\nHowever, this exchange has something else in it, apart from creating\nunneeded work for me.\n\nWhat you really accidentally did was to identify a fundamental problem\nwith the builtin difftool: when called from a subdirectory, the RUN_SETUP\nflag would make it chdir() to the top-level directory, and the\nsubsequently spawned Git processes would get the wrong idea about relative\npaths.\n\nThank you for that,\nDscho\n"},{"id":"306413","messageId":"cover.1480019834.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1479938494.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 0/2] Show Git Mailing List: a builtin difftool","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-24T20:55:02Z","receivedAt":"2016-11-24T20:55:15Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"I have been working on the builtin difftool for almost two weeks,\nfor two reasons:\n\n1. Perl is really not native on Windows. Not only is there a performance\n   penalty to be paid just for running Perl scripts, we also have to deal\n   with the fact that users may have different Perl installations, with\n   different options, and some other Perl installation may decide to set\n   PERL5LIB globally, wreaking havoc with Git for Windows' Perl (which we\n   have to use because almost all other Perl distributions lack the\n   Subversion bindings we need for `git svn`).\n\n2. Perl makes for a rather large reason that Git for Windows' installer\n   weighs in with >30MB. While one Perl script less does not relieve us\n   of that burden, it is one step in the right direction.\n\nThis patch serves two purposes: to ask for reviews, and to show what I\nplan to release as part of Git for Windows v2.11.0 (which is due this\ncoming Wednesday, if Git v2.11.0 is released on Tuesday, as planned).\n\nChanges since v2:\n\n- adjusted the config setting's name according to Junio's concerns\n\n- fixed launching difftool in a subdirectory\n\n- fixed dir-diff mode when there are no changes (it did not exit early\n  but tried to diff two empty directories)\n\n\nJohannes Schindelin (2):\n  difftool: add a skeleton for the upcoming builtin\n  difftool: implement the functionality in the builtin\n\n .gitignore                                    |   1 +\n Makefile                                      |   3 +-\n builtin.h                                     |   1 +\n builtin/difftool.c                            | 731 ++++++++++++++++++++++++++\n git-difftool.perl => git-legacy-difftool.perl |   0\n git.c                                         |   6 +\n t/t7800-difftool.sh                           |   2 +\n 7 files changed, 743 insertions(+), 1 deletion(-)\n create mode 100644 builtin/difftool.c\n rename git-difftool.perl => git-legacy-difftool.perl (100%)\n\n\nbase-commit: e2b2d6a172b76d44cb7b1ddb12ea5bfac9613a44\nPublished-As: https://github.com/dscho/git/releases/tag/builtin-difftool-v3\nFetch-It-Via: git fetch https://github.com/dscho/git builtin-difftool-v3\n\nInterdiff vs v2:\n\n diff --git a/builtin/difftool.c b/builtin/difftool.c\n index f845879..3480920 100644\n --- a/builtin/difftool.c\n +++ b/builtin/difftool.c\n @@ -13,17 +13,16 @@\n   */\n  #include \"cache.h\"\n  #include \"builtin.h\"\n -#include \"parse-options.h\"\n  #include \"run-command.h\"\n +#include \"exec_cmd.h\"\n +#include \"parse-options.h\"\n  #include \"argv-array.h\"\n  #include \"strbuf.h\"\n  #include \"lockfile.h\"\n  #include \"dir.h\"\n -#include \"exec_cmd.h\"\n  \n  static char *diff_gui_tool;\n  static int trust_exit_code;\n -static int use_builtin_difftool;\n  \n  static const char *const builtin_difftool_usage[] = {\n  \tN_(\"git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]\"),\n @@ -42,11 +41,6 @@ static int difftool_config(const char *var, const char *value, void *cb)\n  \t\treturn 0;\n  \t}\n  \n -\tif (!strcmp(var, \"core.usebuiltindifftool\")) {\n -\t\tuse_builtin_difftool = git_config_bool(var, value);\n -\t\treturn 0;\n -\t}\n -\n  \treturn git_default_config(var, value, cb);\n  }\n  \n @@ -257,7 +251,7 @@ static int ensure_leading_directories(char *path)\n  \t}\n  }\n  \n -static int run_dir_diff(const char *extcmd, int symlinks,\n +static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,\n  \t\t\tint argc, const char **argv)\n  {\n  \tchar tmpdir[PATH_MAX];\n @@ -283,7 +277,6 @@ static int run_dir_diff(const char *extcmd, int symlinks,\n  \tstruct hashmap wt_modified, tmp_modified;\n  \tint indices_loaded = 0;\n  \n -\tsetup_work_tree();\n  \tworkdir = get_git_work_tree();\n  \n  \t/* Setup temp directories */\n @@ -323,6 +316,7 @@ static int run_dir_diff(const char *extcmd, int symlinks,\n  \tchild.git_cmd = 1;\n  \tchild.use_shell = 0;\n  \tchild.clean_on_exit = 1;\n +\tchild.dir = prefix;\n  \tchild.out = -1;\n  \targv_array_pushl(&child.args, \"diff\", \"--raw\", \"--no-abbrev\", \"-z\",\n  \t\t\t NULL);\n @@ -333,6 +327,7 @@ static int run_dir_diff(const char *extcmd, int symlinks,\n  \tfp = xfdopen(child.out, \"r\");\n  \n  \t/* Build index info for left and right sides of the diff */\n +\ti = 0;\n  \twhile (!strbuf_getline_nul(&info, fp)) {\n  \t\tint lmode, rmode;\n  \t\tstruct object_id loid, roid;\n @@ -353,6 +348,7 @@ static int run_dir_diff(const char *extcmd, int symlinks,\n  \t\tsrc_path = lpath.buf;\n  \t\tsrc_path_len = lpath.len;\n  \n +\t\ti++;\n  \t\tif (status != 'C' && status != 'R') {\n  \t\t\tdst_path = src_path;\n  \t\t\tdst_path_len = src_path_len;\n @@ -456,11 +452,15 @@ static int run_dir_diff(const char *extcmd, int symlinks,\n  \t\t\t}\n  \t\t}\n  \t}\n +\n  \tif (finish_command(&child)) {\n  \t\tret = error(\"error occurred running diff --raw\");\n  \t\tgoto finish;\n  \t}\n  \n +\tif (!i)\n +\t\treturn 0;\n +\n  \t/*\n  \t * Changes to submodules require special treatment.This loop writes a\n  \t * temporary file to both the left and right directories to show the\n @@ -591,7 +591,8 @@ static int run_dir_diff(const char *extcmd, int symlinks,\n  \treturn ret;\n  }\n  \n -static int run_file_diff(int prompt, int argc, const char **argv)\n +static int run_file_diff(int prompt, const char *prefix,\n +\t\t\t int argc, const char **argv)\n  {\n  \tstruct argv_array args = ARGV_ARRAY_INIT;\n  \tconst char *env[] = {\n @@ -605,14 +606,39 @@ static int run_file_diff(int prompt, int argc, const char **argv)\n  \telse if (!prompt)\n  \t\tenv[2] = \"GIT_DIFFTOOL_NO_PROMPT=true\";\n  \n +\n  \targv_array_push(&args, \"diff\");\n  \tfor (i = 0; i < argc; i++)\n  \t\targv_array_push(&args, argv[i]);\n -\tret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, NULL, env);\n +\tret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env);\n  \texit(ret);\n  }\n  \n -int cmd_difftool(int argc, const char ** argv, const char * prefix)\n +/*\n + * NEEDSWORK: this function can go once the legacy-difftool Perl script is\n + * retired.\n + *\n + * We intentionally avoid reading the config directly here, to avoid messing up\n + * the GIT_* environment variables when we need to fall back to exec()ing the\n + * Perl script.\n + */\n +static int use_builtin_difftool(void) {\n +\tstruct child_process cp = CHILD_PROCESS_INIT;\n +\tstruct strbuf out = STRBUF_INIT;\n +\tint ret;\n +\n +\targv_array_pushl(&cp.args,\n +\t\t\t \"config\", \"--bool\", \"difftool.usebuiltin\", NULL);\n +\tcp.git_cmd = 1;\n +\tif (capture_command(&cp, &out, 6))\n +\t\treturn 0;\n +\tstrbuf_trim(&out);\n +\tret = !strcmp(\"true\", out.buf);\n +\tstrbuf_release(&out);\n +\treturn ret;\n +}\n +\n +int cmd_difftool(int argc, const char **argv, const char *prefix)\n  {\n  \tint use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,\n  \t    tool_help = 0;\n @@ -643,16 +669,29 @@ int cmd_difftool(int argc, const char ** argv, const char * prefix)\n  \t\tOPT_END()\n  \t};\n  \n -\tgit_config(difftool_config, NULL);\n -\tsymlinks = has_symlinks;\n -\tif (!use_builtin_difftool) {\n -\t\tconst char *path = mkpath(\"%s/git-legacy-difftool\", git_exec_path());\n +\t/*\n +\t * NEEDSWORK: Once the builtin difftool has been tested enough\n +\t * and git-legacy-difftool.perl is retired to contrib/, this preamble\n +\t * can be removed.\n +\t */\n +\tif (!use_builtin_difftool()) {\n +\t\tconst char *path = mkpath(\"%s/git-legacy-difftool\",\n +\t\t\t\t\t  git_exec_path());\n  \n  \t\tif (sane_execvp(path, (char **)argv) < 0)\n  \t\t\tdie_errno(\"could not exec %s\", path);\n  \n  \t\treturn 0;\n  \t}\n +\tprefix = setup_git_directory();\n +\ttrace_repo_setup(prefix);\n +\tsetup_work_tree();\n +\t/* NEEDSWORK: once we no longer spawn anything, remove this */\n +\tsetenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);\n +\tsetenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);\n +\n +\tgit_config(difftool_config, NULL);\n +\tsymlinks = has_symlinks;\n  \n  \targc = parse_options(argc, argv, prefix, builtin_difftool_options,\n  \t\t\t     builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |\n @@ -687,6 +726,6 @@ int cmd_difftool(int argc, const char ** argv, const char * prefix)\n  \t * each file that changed.\n  \t */\n  \tif (dir_diff)\n -\t\treturn run_dir_diff(extcmd, symlinks, argc, argv);\n -\treturn run_file_diff(prompt, argc, argv);\n +\t\treturn run_dir_diff(extcmd, symlinks, prefix, argc, argv);\n +\treturn run_file_diff(prompt, prefix, argc, argv);\n  }\n diff --git a/git.c b/git.c\n index e68b6eb..a8e6a15 100644\n --- a/git.c\n +++ b/git.c\n @@ -424,7 +424,12 @@ static struct cmd_struct commands[] = {\n  \t{ \"diff-files\", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },\n  \t{ \"diff-index\", cmd_diff_index, RUN_SETUP },\n  \t{ \"diff-tree\", cmd_diff_tree, RUN_SETUP },\n -\t{ \"difftool\", cmd_difftool, RUN_SETUP | NEED_WORK_TREE },\n +\t/*\n +\t * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in\n +\t * builtin/difftool.c has been removed, this entry should be changed to\n +\t * RUN_SETUP | NEED_WORK_TREE\n +\t */\n +\t{ \"difftool\", cmd_difftool },\n  \t{ \"fast-export\", cmd_fast_export, RUN_SETUP },\n  \t{ \"fetch\", cmd_fetch, RUN_SETUP },\n  \t{ \"fetch-pack\", cmd_fetch_pack, RUN_SETUP },\n diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh\n index 70a2de4..b6a6c30 100755\n --- a/t/t7800-difftool.sh\n +++ b/t/t7800-difftool.sh\n @@ -23,6 +23,8 @@ prompt_given ()\n  \ttest \"$prompt\" = \"Launch 'test-tool' [Y/n]? branch\"\n  }\n  \n +# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.\n +\n  # Create a file on master and change it on branch\n  test_expect_success PERL 'setup' '\n  \techo master >file &&\n\n-- \n2.10.1.583.g721a9e0\n\n"},{"id":"306414","messageId":"3a10fd72cf336743891d6bc9a003cfeb5c3f0063.1480019834.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1480019834.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-24T20:55:07Z","receivedAt":"2016-11-24T20:55:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This adds a builtin difftool that still falls back to the legacy Perl\nversion, which has been renamed to `legacy-difftool`.\n\nThe idea is that the new, experimental, builtin difftool immediately hands\noff to the legacy difftool for now, unless the config variable\ndifftool.useBuiltin is set to true.\n\nThis feature flag will be used in the upcoming Git for Windows v2.11.0\nrelease, to allow early testers to opt-in to use the builtin difftool and\nflesh out any bugs.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n .gitignore                                    |  1 +\n Makefile                                      |  3 +-\n builtin.h                                     |  1 +\n builtin/difftool.c                            | 63 +++++++++++++++++++++++++++\n git-difftool.perl => git-legacy-difftool.perl |  0\n git.c                                         |  6 +++\n t/t7800-difftool.sh                           |  2 +\n 7 files changed, 75 insertions(+), 1 deletion(-)\n create mode 100644 builtin/difftool.c\n rename git-difftool.perl => git-legacy-difftool.perl (100%)\n\ndiff --git a/.gitignore b/.gitignore\nindex 05cb58a..f96e50e 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -76,6 +76,7 @@\n /git-init-db\n /git-interpret-trailers\n /git-instaweb\n+/git-legacy-difftool\n /git-log\n /git-ls-files\n /git-ls-remote\ndiff --git a/Makefile b/Makefile\nindex f53fcc9..7863bc2 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -527,7 +527,7 @@ SCRIPT_LIB += git-sh-setup\n SCRIPT_LIB += git-sh-i18n\n \n SCRIPT_PERL += git-add--interactive.perl\n-SCRIPT_PERL += git-difftool.perl\n+SCRIPT_PERL += git-legacy-difftool.perl\n SCRIPT_PERL += git-archimport.perl\n SCRIPT_PERL += git-cvsexportcommit.perl\n SCRIPT_PERL += git-cvsimport.perl\n@@ -888,6 +888,7 @@ BUILTIN_OBJS += builtin/diff-files.o\n BUILTIN_OBJS += builtin/diff-index.o\n BUILTIN_OBJS += builtin/diff-tree.o\n BUILTIN_OBJS += builtin/diff.o\n+BUILTIN_OBJS += builtin/difftool.o\n BUILTIN_OBJS += builtin/fast-export.o\n BUILTIN_OBJS += builtin/fetch-pack.o\n BUILTIN_OBJS += builtin/fetch.o\ndiff --git a/builtin.h b/builtin.h\nindex b9122bc..67f8051 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix);\n extern int cmd_diff_index(int argc, const char **argv, const char *prefix);\n extern int cmd_diff(int argc, const char **argv, const char *prefix);\n extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);\n+extern int cmd_difftool(int argc, const char **argv, const char *prefix);\n extern int cmd_fast_export(int argc, const char **argv, const char *prefix);\n extern int cmd_fetch(int argc, const char **argv, const char *prefix);\n extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nnew file mode 100644\nindex 0000000..53870bb\n--- /dev/null\n+++ b/builtin/difftool.c\n@@ -0,0 +1,63 @@\n+/*\n+ * \"git difftool\" builtin command\n+ *\n+ * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible\n+ * git-difftool--helper script.\n+ *\n+ * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.\n+ * The GIT_DIFF* variables are exported for use by git-difftool--helper.\n+ *\n+ * Any arguments that are unknown to this script are forwarded to 'git diff'.\n+ *\n+ * Copyright (C) 2016 Johannes Schindelin\n+ */\n+#include \"builtin.h\"\n+#include \"run-command.h\"\n+#include \"exec_cmd.h\"\n+\n+/*\n+ * NEEDSWORK: this function can go once the legacy-difftool Perl script is\n+ * retired.\n+ *\n+ * We intentionally avoid reading the config directly here, to avoid messing up\n+ * the GIT_* environment variables when we need to fall back to exec()ing the\n+ * Perl script.\n+ */\n+static int use_builtin_difftool(void) {\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tstruct strbuf out = STRBUF_INIT;\n+\tint ret;\n+\n+\targv_array_pushl(&cp.args,\n+\t\t\t \"config\", \"--bool\", \"difftool.usebuiltin\", NULL);\n+\tcp.git_cmd = 1;\n+\tif (capture_command(&cp, &out, 6))\n+\t\treturn 0;\n+\tstrbuf_trim(&out);\n+\tret = !strcmp(\"true\", out.buf);\n+\tstrbuf_release(&out);\n+\treturn ret;\n+}\n+\n+int cmd_difftool(int argc, const char **argv, const char *prefix)\n+{\n+\t/*\n+\t * NEEDSWORK: Once the builtin difftool has been tested enough\n+\t * and git-legacy-difftool.perl is retired to contrib/, this preamble\n+\t * can be removed.\n+\t */\n+\tif (!use_builtin_difftool()) {\n+\t\tconst char *path = mkpath(\"%s/git-legacy-difftool\",\n+\t\t\t\t\t  git_exec_path());\n+\n+\t\tif (sane_execvp(path, (char **)argv) < 0)\n+\t\t\tdie_errno(\"could not exec %s\", path);\n+\n+\t\treturn 0;\n+\t}\n+\tprefix = setup_git_directory();\n+\ttrace_repo_setup(prefix);\n+\tsetup_work_tree();\n+\n+\tdie(\"TODO\");\n+}\ndiff --git a/git-difftool.perl b/git-legacy-difftool.perl\nsimilarity index 100%\nrename from git-difftool.perl\nrename to git-legacy-difftool.perl\ndiff --git a/git.c b/git.c\nindex e8b2baf..a8e6a15 100644\n--- a/git.c\n+++ b/git.c\n@@ -424,6 +424,12 @@ static struct cmd_struct commands[] = {\n \t{ \"diff-files\", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"diff-index\", cmd_diff_index, RUN_SETUP },\n \t{ \"diff-tree\", cmd_diff_tree, RUN_SETUP },\n+\t/*\n+\t * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in\n+\t * builtin/difftool.c has been removed, this entry should be changed to\n+\t * RUN_SETUP | NEED_WORK_TREE\n+\t */\n+\t{ \"difftool\", cmd_difftool },\n \t{ \"fast-export\", cmd_fast_export, RUN_SETUP },\n \t{ \"fetch\", cmd_fetch, RUN_SETUP },\n \t{ \"fetch-pack\", cmd_fetch_pack, RUN_SETUP },\ndiff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh\nindex 70a2de4..b6a6c30 100755\n--- a/t/t7800-difftool.sh\n+++ b/t/t7800-difftool.sh\n@@ -23,6 +23,8 @@ prompt_given ()\n \ttest \"$prompt\" = \"Launch 'test-tool' [Y/n]? branch\"\n }\n \n+# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.\n+\n # Create a file on master and change it on branch\n test_expect_success PERL 'setup' '\n \techo master >file &&\n-- \n2.10.1.583.g721a9e0\n\n\n"},{"id":"306415","messageId":"ac91e4818cfb5c5af6b5874662dbeb61cde1f69d.1480019834.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1480019834.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 2/2] difftool: implement the functionality in the builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-24T20:55:28Z","receivedAt":"2016-11-24T20:55:53Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch gives life to the skeleton added in the previous patch.\n\nThe motivation for converting the difftool is that Perl scripts are not at\nall native on Windows, and that `git difftool` therefore is pretty slow on\nthat platform, when there is no good reason for it to be slow.\n\nIn addition, Perl does not really have access to Git's internals. That\nmeans that any script will always have to jump through unnecessary\nhoops.\n\nThe current version of the builtin difftool does not, however, make full\nuse of the internals but instead chooses to spawn a couple of Git\nprocesses, still, to make for an easier conversion. There remains a lot\nof room for improvement, left for a later date.\n\nNote: to play it safe, the original difftool is still called unless the\nconfig setting difftool.useBuiltin is set to true.\n\nThe reason: this new, experimental, builtin difftool will be shipped as\npart of Git for Windows v2.11.0, to allow for easier large-scale\ntesting, but of course as an opt-in feature.\n\nSadly, the speedup is more noticable on Linux than on Windows: a quick\ntest shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s)\n(real/user/sys) in a Linux VM, down from  (6.529s/3.112s/0.644s), while\non Windows, it is (36.064s/2.730s/7.194s), down from\n(47.637s/2.407s/6.863s). The culprit is most likely the overhead\nincurred from *still* having to shell out to mergetool-lib.sh and\ndifftool--helper.sh.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/difftool.c | 670 ++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 669 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex 53870bb..3480920 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -11,9 +11,608 @@\n  *\n  * Copyright (C) 2016 Johannes Schindelin\n  */\n+#include \"cache.h\"\n #include \"builtin.h\"\n #include \"run-command.h\"\n #include \"exec_cmd.h\"\n+#include \"parse-options.h\"\n+#include \"argv-array.h\"\n+#include \"strbuf.h\"\n+#include \"lockfile.h\"\n+#include \"dir.h\"\n+\n+static char *diff_gui_tool;\n+static int trust_exit_code;\n+\n+static const char *const builtin_difftool_usage[] = {\n+\tN_(\"git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]\"),\n+\tNULL\n+};\n+\n+static int difftool_config(const char *var, const char *value, void *cb)\n+{\n+\tif (!strcmp(var, \"diff.guitool\")) {\n+\t\tdiff_gui_tool = xstrdup(value);\n+\t\treturn 0;\n+\t}\n+\n+\tif (!strcmp(var, \"difftool.trustexitcode\")) {\n+\t\ttrust_exit_code = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n+\treturn git_default_config(var, value, cb);\n+}\n+\n+static int print_tool_help(void)\n+{\n+\tconst char *argv[] = { \"mergetool\", \"--tool-help=diff\", NULL };\n+\treturn run_command_v_opt(argv, RUN_GIT_CMD);\n+}\n+\n+static int parse_index_info(char *p, int *mode1, int *mode2,\n+\t\t\t    struct object_id *oid1, struct object_id *oid2,\n+\t\t\t    char *status)\n+{\n+\tif (*p != ':')\n+\t\treturn error(\"expected ':', got '%c'\", *p);\n+\t*mode1 = (int)strtol(p + 1, &p, 8);\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\t*mode2 = (int)strtol(p + 1, &p, 8);\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\tif (get_oid_hex(++p, oid1))\n+\t\treturn error(\"expected object ID, got '%s'\", p + 1);\n+\tp += GIT_SHA1_HEXSZ;\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\tif (get_oid_hex(++p, oid2))\n+\t\treturn error(\"expected object ID, got '%s'\", p + 1);\n+\tp += GIT_SHA1_HEXSZ;\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\t*status = *++p;\n+\tif (!status || p[1])\n+\t\treturn error(\"unexpected trailer: '%s'\", p);\n+\treturn 0;\n+}\n+\n+/*\n+ * Remove any trailing slash from $workdir\n+ * before starting to avoid double slashes in symlink targets.\n+ */\n+static void add_path(struct strbuf *buf, size_t base_len, const char *path)\n+{\n+\tstrbuf_setlen(buf, base_len);\n+\tif (buf->len && buf->buf[buf->len - 1] != '/')\n+\t\tstrbuf_addch(buf, '/');\n+\tstrbuf_addstr(buf, path);\n+}\n+\n+/*\n+ * Determine whether we can simply reuse the file in the worktree.\n+ */\n+static int use_wt_file(const char *workdir, const char *name,\n+\t\t       struct object_id *oid)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct stat st;\n+\tint use = 0;\n+\n+\tstrbuf_addstr(&buf, workdir);\n+\tadd_path(&buf, buf.len, name);\n+\n+\tif (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) {\n+\t\tstruct object_id wt_oid;\n+\t\tint fd = open(buf.buf, O_RDONLY);\n+\n+\t\tif (!index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {\n+\t\t\tif (is_null_oid(oid)) {\n+\t\t\t\toidcpy(oid, &wt_oid);\n+\t\t\t\tuse = 1;\n+\t\t\t} else if (!oidcmp(oid, &wt_oid))\n+\t\t\t\tuse = 1;\n+\t\t}\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn use;\n+}\n+\n+struct working_tree_entry {\n+\tstruct hashmap_entry entry;\n+\tchar path[FLEX_ARRAY];\n+};\n+\n+static int working_tree_entry_cmp(struct working_tree_entry *a,\n+\t\t\t\t  struct working_tree_entry *b, void *keydata)\n+{\n+\treturn strcmp(a->path, b->path);\n+}\n+\n+/*\n+ * The `left` and `right` entries hold paths for the symlinks hashmap,\n+ * and a SHA-1 surrounded by brief text for submodules.\n+ */\n+struct pair_entry {\n+\tstruct hashmap_entry entry;\n+\tchar left[PATH_MAX], right[PATH_MAX];\n+\tconst char path[FLEX_ARRAY];\n+};\n+\n+static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata)\n+{\n+\treturn strcmp(a->path, b->path);\n+}\n+\n+static void add_left_or_right(struct hashmap *map, const char *path,\n+\t\t\t      const char *content, int is_right)\n+{\n+\tstruct pair_entry *e, *existing;\n+\n+\tFLEX_ALLOC_STR(e, path, path);\n+\thashmap_entry_init(e, strhash(path));\n+\texisting = hashmap_get(map, e, NULL);\n+\tif (existing) {\n+\t\tfree(e);\n+\t\te = existing;\n+\t} else {\n+\t\te->left[0] = e->right[0] = '\\0';\n+\t\thashmap_add(map, e);\n+\t}\n+\tstrcpy(is_right ? e->right : e->left, content);\n+}\n+\n+struct path_entry {\n+\tstruct hashmap_entry entry;\n+\tchar path[FLEX_ARRAY];\n+};\n+\n+int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key)\n+{\n+\treturn strcmp(a->path, key ? key : b->path);\n+}\n+\n+static void changed_files(struct hashmap *result, const char *index_path,\n+\t\t\t  const char *workdir)\n+{\n+\tstruct child_process update_index = CHILD_PROCESS_INIT;\n+\tstruct child_process diff_files = CHILD_PROCESS_INIT;\n+\tstruct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT;\n+\tconst char *git_dir = absolute_path(get_git_dir()), *env[] = {\n+\t\tNULL, NULL\n+\t};\n+\tFILE *fp;\n+\n+\tstrbuf_addf(&index_env, \"GIT_INDEX_FILE=%s\", index_path);\n+\tenv[0] = index_env.buf;\n+\n+\targv_array_pushl(&update_index.args,\n+\t\t\t \"--git-dir\", git_dir, \"--work-tree\", workdir,\n+\t\t\t \"update-index\", \"--really-refresh\", \"-q\",\n+\t\t\t \"--unmerged\", NULL);\n+\tupdate_index.no_stdin = 1;\n+\tupdate_index.no_stdout = 1;\n+\tupdate_index.no_stderr = 1;\n+\tupdate_index.git_cmd = 1;\n+\tupdate_index.use_shell = 0;\n+\tupdate_index.clean_on_exit = 1;\n+\tupdate_index.dir = workdir;\n+\tupdate_index.env = env;\n+\t/* Ignore any errors of update-index */\n+\trun_command(&update_index);\n+\n+\targv_array_pushl(&diff_files.args,\n+\t\t\t \"--git-dir\", git_dir, \"--work-tree\", workdir,\n+\t\t\t \"diff-files\", \"--name-only\", \"-z\", NULL);\n+\tdiff_files.no_stdin = 1;\n+\tdiff_files.git_cmd = 1;\n+\tdiff_files.use_shell = 0;\n+\tdiff_files.clean_on_exit = 1;\n+\tdiff_files.out = -1;\n+\tdiff_files.dir = workdir;\n+\tdiff_files.env = env;\n+\tif (start_command(&diff_files))\n+\t\tdie(\"could not obtain raw diff\");\n+\tfp = xfdopen(diff_files.out, \"r\");\n+\twhile (!strbuf_getline_nul(&buf, fp)) {\n+\t\tstruct path_entry *entry;\n+\t\tFLEX_ALLOC_STR(entry, path, buf.buf);\n+\t\thashmap_entry_init(entry, strhash(buf.buf));\n+\t\thashmap_add(result, entry);\n+\t}\n+\tif (finish_command(&diff_files))\n+\t\tdie(\"diff-files did not exit properly\");\n+\tstrbuf_release(&index_env);\n+\tstrbuf_release(&buf);\n+}\n+\n+static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstrbuf_addstr(&buf, tmpdir);\n+\tremove_dir_recursively(&buf, 0);\n+\tif (exit_code)\n+\t\twarning(_(\"failed: %d\"), exit_code);\n+\texit(exit_code);\n+}\n+\n+static int ensure_leading_directories(char *path)\n+{\n+\tswitch (safe_create_leading_directories(path)) {\n+\t\tcase SCLD_OK:\n+\t\tcase SCLD_EXISTS:\n+\t\t\treturn 0;\n+\t\tdefault:\n+\t\t\treturn error(_(\"could not create leading directories \"\n+\t\t\t\t       \"of '%s'\"), path);\n+\t}\n+}\n+\n+static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,\n+\t\t\tint argc, const char **argv)\n+{\n+\tchar tmpdir[PATH_MAX];\n+\tstruct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;\n+\tstruct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;\n+\tstruct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;\n+\tstruct strbuf wtdir = STRBUF_INIT;\n+\tsize_t ldir_len, rdir_len, wtdir_len;\n+\tstruct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1);\n+\tconst char *workdir, *tmp;\n+\tint ret = 0, i;\n+\tFILE *fp;\n+\tstruct hashmap working_tree_dups, submodules, symlinks2;\n+\tstruct hashmap_iter iter;\n+\tstruct pair_entry *entry;\n+\tenum object_type type;\n+\tunsigned long size;\n+\tstruct index_state wtindex;\n+\tstruct checkout lstate, rstate;\n+\tint rc, flags = RUN_GIT_CMD, err = 0;\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\tconst char *helper_argv[] = { \"difftool--helper\", NULL, NULL, NULL };\n+\tstruct hashmap wt_modified, tmp_modified;\n+\tint indices_loaded = 0;\n+\n+\tworkdir = get_git_work_tree();\n+\n+\t/* Setup temp directories */\n+\ttmp = getenv(\"TMPDIR\");\n+\txsnprintf(tmpdir, sizeof(tmpdir), \"%s/git-difftool.XXXXXX\", tmp ? tmp : \"/tmp\");\n+\tif (!mkdtemp(tmpdir))\n+\t\treturn error(\"could not create '%s'\", tmpdir);\n+\tstrbuf_addf(&ldir, \"%s/left/\", tmpdir);\n+\tstrbuf_addf(&rdir, \"%s/right/\", tmpdir);\n+\tstrbuf_addstr(&wtdir, workdir);\n+\tif (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))\n+\t\tstrbuf_addch(&wtdir, '/');\n+\tmkdir(ldir.buf, 0700);\n+\tmkdir(rdir.buf, 0700);\n+\n+\tmemset(&wtindex, 0, sizeof(wtindex));\n+\n+\tmemset(&lstate, 0, sizeof(lstate));\n+\tlstate.base_dir = ldir.buf;\n+\tlstate.base_dir_len = ldir.len;\n+\tlstate.force = 1;\n+\tmemset(&rstate, 0, sizeof(rstate));\n+\trstate.base_dir = rdir.buf;\n+\trstate.base_dir_len = rdir.len;\n+\trstate.force = 1;\n+\n+\tldir_len = ldir.len;\n+\trdir_len = rdir.len;\n+\twtdir_len = wtdir.len;\n+\n+\thashmap_init(&working_tree_dups,\n+\t\t     (hashmap_cmp_fn)working_tree_entry_cmp, 0);\n+\thashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0);\n+\thashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0);\n+\n+\tchild.no_stdin = 1;\n+\tchild.git_cmd = 1;\n+\tchild.use_shell = 0;\n+\tchild.clean_on_exit = 1;\n+\tchild.dir = prefix;\n+\tchild.out = -1;\n+\targv_array_pushl(&child.args, \"diff\", \"--raw\", \"--no-abbrev\", \"-z\",\n+\t\t\t NULL);\n+\tfor (i = 0; i < argc; i++)\n+\t\targv_array_push(&child.args, argv[i]);\n+\tif (start_command(&child))\n+\t\tdie(\"could not obtain raw diff\");\n+\tfp = xfdopen(child.out, \"r\");\n+\n+\t/* Build index info for left and right sides of the diff */\n+\ti = 0;\n+\twhile (!strbuf_getline_nul(&info, fp)) {\n+\t\tint lmode, rmode;\n+\t\tstruct object_id loid, roid;\n+\t\tchar status;\n+\t\tconst char *src_path, *dst_path;\n+\t\tsize_t src_path_len, dst_path_len;\n+\n+\t\tif (starts_with(info.buf, \"::\"))\n+\t\t\tdie(N_(\"combined diff formats('-c' and '--cc') are \"\n+\t\t\t       \"not supported in\\n\"\n+\t\t\t       \"directory diff mode('-d' and '--dir-diff').\"));\n+\n+\t\tif (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,\n+\t\t\t\t     &status))\n+\t\t\tbreak;\n+\t\tif (strbuf_getline_nul(&lpath, fp))\n+\t\t\tbreak;\n+\t\tsrc_path = lpath.buf;\n+\t\tsrc_path_len = lpath.len;\n+\n+\t\ti++;\n+\t\tif (status != 'C' && status != 'R') {\n+\t\t\tdst_path = src_path;\n+\t\t\tdst_path_len = src_path_len;\n+\t\t} else {\n+\t\t\tif (strbuf_getline_nul(&rpath, fp))\n+\t\t\t\tbreak;\n+\t\t\tdst_path = rpath.buf;\n+\t\t\tdst_path_len = rpath.len;\n+\t\t}\n+\n+\t\tif (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) {\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"Subproject commit %s\",\n+\t\t\t\t    oid_to_hex(&loid));\n+\t\t\tadd_left_or_right(&submodules, src_path, buf.buf, 0);\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"Subproject commit %s\",\n+\t\t\t\t    oid_to_hex(&roid));\n+\t\t\tif (!oidcmp(&loid, &roid))\n+\t\t\t\tstrbuf_addstr(&buf, \"-dirty\");\n+\t\t\tadd_left_or_right(&submodules, dst_path, buf.buf, 1);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (S_ISLNK(lmode)) {\n+\t\t\tchar *content = read_sha1_file(loid.hash, &type, &size);\n+\t\t\tadd_left_or_right(&symlinks2, src_path, content, 0);\n+\t\t\tfree(content);\n+\t\t}\n+\n+\t\tif (S_ISLNK(rmode)) {\n+\t\t\tchar *content = read_sha1_file(roid.hash, &type, &size);\n+\t\t\tadd_left_or_right(&symlinks2, dst_path, content, 1);\n+\t\t\tfree(content);\n+\t\t}\n+\n+\t\tif (lmode && status != 'C') {\n+\t\t\tce->ce_mode = lmode;\n+\t\t\toidcpy(&ce->oid, &loid);\n+\t\t\tstrcpy(ce->name, src_path);\n+\t\t\tce->ce_namelen = src_path_len;\n+\t\t\tif (checkout_entry(ce, &lstate, NULL))\n+\t\t\t\treturn error(\"could not write '%s'\", src_path);\n+\t\t}\n+\n+\t\tif (rmode) {\n+\t\t\tstruct working_tree_entry *entry;\n+\n+\t\t\t/* Avoid duplicate working_tree entries */\n+\t\t\tFLEX_ALLOC_STR(entry, path, dst_path);\n+\t\t\thashmap_entry_init(entry, strhash(dst_path));\n+\t\t\tif (hashmap_get(&working_tree_dups, entry, NULL)) {\n+\t\t\t\tfree(entry);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\thashmap_add(&working_tree_dups, entry);\n+\n+\t\t\tif (!use_wt_file(workdir, dst_path, &roid)) {\n+\t\t\t\tce->ce_mode = rmode;\n+\t\t\t\toidcpy(&ce->oid, &roid);\n+\t\t\t\tstrcpy(ce->name, dst_path);\n+\t\t\t\tce->ce_namelen = dst_path_len;\n+\t\t\t\tif (checkout_entry(ce, &rstate, NULL))\n+\t\t\t\t\treturn error(\"could not write '%s'\",\n+\t\t\t\t\t\t     dst_path);\n+\t\t\t} else if (!is_null_oid(&roid)) {\n+\t\t\t\t/*\n+\t\t\t\t * Changes in the working tree need special\n+\t\t\t\t * treatment since they are not part of the\n+\t\t\t\t * index.\n+\t\t\t\t */\n+\t\t\t\tstruct cache_entry *ce2 =\n+\t\t\t\t\tmake_cache_entry(rmode, roid.hash,\n+\t\t\t\t\t\t\t dst_path, 0, 0);\n+\t\t\t\tce_mode_from_stat(ce2, rmode);\n+\n+\t\t\t\tadd_index_entry(&wtindex, ce2,\n+\t\t\t\t\t\tADD_CACHE_JUST_APPEND);\n+\n+\t\t\t\tadd_path(&wtdir, wtdir_len, dst_path);\n+\t\t\t\tadd_path(&rdir, rdir_len, dst_path);\n+\t\t\t\tif (ensure_leading_directories(rdir.buf))\n+\t\t\t\t\treturn error(\"could not create \"\n+\t\t\t\t\t\t     \"directory for '%s'\",\n+\t\t\t\t\t\t     dst_path);\n+\t\t\t\tif (symlinks) {\n+\t\t\t\t\tif (symlink(wtdir.buf, rdir.buf)) {\n+\t\t\t\t\t\tret = error_errno(\"could not symlink '%s' to '%s'\", wtdir.buf, rdir.buf);\n+\t\t\t\t\t\tgoto finish;\n+\t\t\t\t\t}\n+\t\t\t\t} else {\n+\t\t\t\t\tstruct stat st;\n+\t\t\t\t\tif (stat(wtdir.buf, &st))\n+\t\t\t\t\t\tst.st_mode = 0644;\n+\t\t\t\t\tif (copy_file(rdir.buf, wtdir.buf,\n+\t\t\t\t\t\t      st.st_mode)) {\n+\t\t\t\t\t\tret = error(\"could not copy '%s' to '%s'\", wtdir.buf, rdir.buf);\n+\t\t\t\t\t\tgoto finish;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (finish_command(&child)) {\n+\t\tret = error(\"error occurred running diff --raw\");\n+\t\tgoto finish;\n+\t}\n+\n+\tif (!i)\n+\t\treturn 0;\n+\n+\t/*\n+\t * Changes to submodules require special treatment.This loop writes a\n+\t * temporary file to both the left and right directories to show the\n+\t * change in the recorded SHA1 for the submodule.\n+\t */\n+\thashmap_iter_init(&submodules, &iter);\n+\twhile ((entry = hashmap_iter_next(&iter))) {\n+\t\tif (*entry->left) {\n+\t\t\tadd_path(&ldir, ldir_len, entry->path);\n+\t\t\tensure_leading_directories(ldir.buf);\n+\t\t\twrite_file(ldir.buf, \"%s\", entry->left);\n+\t\t}\n+\t\tif (*entry->right) {\n+\t\t\tadd_path(&rdir, rdir_len, entry->path);\n+\t\t\tensure_leading_directories(rdir.buf);\n+\t\t\twrite_file(rdir.buf, \"%s\", entry->right);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Symbolic links require special treatment.The standard \"git diff\"\n+\t * shows only the link itself, not the contents of the link target.\n+\t * This loop replicates that behavior.\n+\t */\n+\thashmap_iter_init(&symlinks2, &iter);\n+\twhile ((entry = hashmap_iter_next(&iter))) {\n+\t\tif (*entry->left) {\n+\t\t\tadd_path(&ldir, ldir_len, entry->path);\n+\t\t\tensure_leading_directories(ldir.buf);\n+\t\t\twrite_file(ldir.buf, \"%s\", entry->left);\n+\t\t}\n+\t\tif (*entry->right) {\n+\t\t\tadd_path(&rdir, rdir_len, entry->path);\n+\t\t\tensure_leading_directories(rdir.buf);\n+\t\t\twrite_file(rdir.buf, \"%s\", entry->right);\n+\t\t}\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\tstrbuf_setlen(&ldir, ldir_len);\n+\thelper_argv[1] = ldir.buf;\n+\tstrbuf_setlen(&rdir, rdir_len);\n+\thelper_argv[2] = rdir.buf;\n+\n+\tif (extcmd) {\n+\t\thelper_argv[0] = extcmd;\n+\t\tflags = 0;\n+\t} else\n+\t\tsetenv(\"GIT_DIFFTOOL_DIRDIFF\", \"true\", 1);\n+\trc = run_command_v_opt(helper_argv, flags);\n+\n+\t/*\n+\t * If the diff includes working copy files and those\n+\t * files were modified during the diff, then the changes\n+\t * should be copied back to the working tree.\n+\t * Do not copy back files when symlinks are used and the\n+\t * external tool did not replace the original link with a file.\n+\t *\n+\t * These hashes are loaded lazily since they aren't needed\n+\t * in the common case of --symlinks and the difftool updating\n+\t * files through the symlink.\n+\t */\n+\thashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp,\n+\t\t     wtindex.cache_nr);\n+\thashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp,\n+\t\t     wtindex.cache_nr);\n+\n+\tfor (i = 0; i < wtindex.cache_nr; i++) {\n+\t\tstruct hashmap_entry dummy;\n+\t\tconst char *name = wtindex.cache[i]->name;\n+\t\tstruct stat st;\n+\n+\t\tadd_path(&rdir, rdir_len, name);\n+\t\tif (lstat(rdir.buf, &st))\n+\t\t\tcontinue;\n+\n+\t\tif ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode))\n+\t\t\tcontinue;\n+\n+\t\tif (!indices_loaded) {\n+\t\t\tstatic struct lock_file lock;\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"%s/wtindex\", tmpdir);\n+\t\t\tif (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||\n+\t\t\t    write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {\n+\t\t\t\tret = error(\"could not write %s\", buf.buf);\n+\t\t\t\trollback_lock_file(&lock);\n+\t\t\t\tgoto finish;\n+\t\t\t}\n+\t\t\tchanged_files(&wt_modified, buf.buf, workdir);\n+\t\t\tstrbuf_setlen(&rdir, rdir_len);\n+\t\t\tchanged_files(&tmp_modified, buf.buf, rdir.buf);\n+\t\t\tadd_path(&rdir, rdir_len, name);\n+\t\t\tindices_loaded = 1;\n+\t\t}\n+\n+\t\thashmap_entry_init(&dummy, strhash(name));\n+\t\tif (hashmap_get(&tmp_modified, &dummy, name)) {\n+\t\t\tadd_path(&wtdir, wtdir_len, name);\n+\t\t\tif (hashmap_get(&wt_modified, &dummy, name)) {\n+\t\t\t\twarning(_(\"both files modified: '%s' and '%s'.\"),\n+\t\t\t\t\twtdir.buf, rdir.buf);\n+\t\t\t\twarning(_(\"working tree file has been left.\"));\n+\t\t\t\twarning(\"\");\n+\t\t\t\terr = 1;\n+\t\t\t} else if (unlink(wtdir.buf) ||\n+\t\t\t\t   copy_file(wtdir.buf, rdir.buf, st.st_mode))\n+\t\t\t\twarning_errno(_(\"could not copy '%s' to '%s'\"),\n+\t\t\t\t\t      rdir.buf, wtdir.buf);\n+\t\t}\n+\t}\n+\n+\tif (err) {\n+\t\twarning(_(\"temporary files exist in '%s'.\"), tmpdir);\n+\t\twarning(_(\"you may want to cleanup or recover these.\"));\n+\t\texit(1);\n+\t} else\n+\t\texit_cleanup(tmpdir, rc);\n+\n+finish:\n+\tfree(ce);\n+\tstrbuf_release(&ldir);\n+\tstrbuf_release(&rdir);\n+\tstrbuf_release(&wtdir);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+static int run_file_diff(int prompt, const char *prefix,\n+\t\t\t int argc, const char **argv)\n+{\n+\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\tconst char *env[] = {\n+\t\t\"GIT_PAGER=\", \"GIT_EXTERNAL_DIFF=git-difftool--helper\", NULL,\n+\t\tNULL\n+\t};\n+\tint ret = 0, i;\n+\n+\tif (prompt > 0)\n+\t\tenv[2] = \"GIT_DIFFTOOL_PROMPT=true\";\n+\telse if (!prompt)\n+\t\tenv[2] = \"GIT_DIFFTOOL_NO_PROMPT=true\";\n+\n+\n+\targv_array_push(&args, \"diff\");\n+\tfor (i = 0; i < argc; i++)\n+\t\targv_array_push(&args, argv[i]);\n+\tret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env);\n+\texit(ret);\n+}\n \n /*\n  * NEEDSWORK: this function can go once the legacy-difftool Perl script is\n@@ -41,6 +640,35 @@ static int use_builtin_difftool(void) {\n \n int cmd_difftool(int argc, const char **argv, const char *prefix)\n {\n+\tint use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,\n+\t    tool_help = 0;\n+\tstatic char *difftool_cmd = NULL, *extcmd = NULL;\n+\tstruct option builtin_difftool_options[] = {\n+\t\tOPT_BOOL('g', \"gui\", &use_gui_tool,\n+\t\t\t N_(\"use `diff.guitool` instead of `diff.tool`\")),\n+\t\tOPT_BOOL('d', \"dir-diff\", &dir_diff,\n+\t\t\t N_(\"perform a full-directory diff\")),\n+\t\t{ OPTION_SET_INT, 'y', \"no-prompt\", &prompt, NULL,\n+\t\t\tN_(\"do not prompt before launching a diff tool\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},\n+\t\t{ OPTION_SET_INT, 0, \"prompt\", &prompt, NULL, NULL,\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n+\t\t\tNULL, 1 },\n+\t\tOPT_BOOL(0, \"symlinks\", &symlinks,\n+\t\t\t N_(\"use symlinks in dir-diff mode\")),\n+\t\tOPT_STRING('t', \"tool\", &difftool_cmd, N_(\"<tool>\"),\n+\t\t\t   N_(\"use the specified diff tool\")),\n+\t\tOPT_BOOL(0, \"tool-help\", &tool_help,\n+\t\t\t N_(\"print a list of diff tools that may be used with \"\n+\t\t\t    \"`--tool`\")),\n+\t\tOPT_BOOL(0, \"trust-exit-code\", &trust_exit_code,\n+\t\t\t N_(\"make 'git-difftool' exit when an invoked diff \"\n+\t\t\t    \"tool returns a non - zero exit code\")),\n+\t\tOPT_STRING('x', \"extcmd\", &extcmd, N_(\"<command>\"),\n+\t\t\t   N_(\"specify a custom command for viewing diffs\")),\n+\t\tOPT_END()\n+\t};\n+\n \t/*\n \t * NEEDSWORK: Once the builtin difftool has been tested enough\n \t * and git-legacy-difftool.perl is retired to contrib/, this preamble\n@@ -58,6 +686,46 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)\n \tprefix = setup_git_directory();\n \ttrace_repo_setup(prefix);\n \tsetup_work_tree();\n+\t/* NEEDSWORK: once we no longer spawn anything, remove this */\n+\tsetenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);\n+\tsetenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);\n+\n+\tgit_config(difftool_config, NULL);\n+\tsymlinks = has_symlinks;\n+\n+\targc = parse_options(argc, argv, prefix, builtin_difftool_options,\n+\t\t\t     builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n \n-\tdie(\"TODO\");\n+\tif (tool_help)\n+\t\treturn print_tool_help();\n+\n+\tif (use_gui_tool && diff_gui_tool && *diff_gui_tool)\n+\t\tsetenv(\"GIT_DIFF_TOOL\", diff_gui_tool, 1);\n+\telse if (difftool_cmd) {\n+\t\tif (*difftool_cmd)\n+\t\t\tsetenv(\"GIT_DIFF_TOOL\", difftool_cmd, 1);\n+\t\telse\n+\t\t\tdie(_(\"no <tool> given for --tool=<tool>\"));\n+\t}\n+\n+\tif (extcmd) {\n+\t\tif (*extcmd)\n+\t\t\tsetenv(\"GIT_DIFFTOOL_EXTCMD\", extcmd, 1);\n+\t\telse\n+\t\t\tdie(_(\"no <cmd> given for --extcmd=<cmd>\"));\n+\t}\n+\n+\tsetenv(\"GIT_DIFFTOOL_TRUST_EXIT_CODE\",\n+\t       trust_exit_code ? \"true\" : \"false\", 1);\n+\n+\t/*\n+\t * In directory diff mode, 'git-difftool--helper' is called once\n+\t * to compare the a / b directories. In file diff mode, 'git diff'\n+\t * will invoke a separate instance of 'git-difftool--helper' for\n+\t * each file that changed.\n+\t */\n+\tif (dir_diff)\n+\t\treturn run_dir_diff(extcmd, symlinks, prefix, argc, argv);\n+\treturn run_file_diff(prompt, prefix, argc, argv);\n }\n-- \n2.10.1.583.g721a9e0\n"},{"id":"306416","messageId":"20161124210841.c4yi2nv57tjfejgj@sigill.intra.peff.net","threadId":"44532","inReplyTo":"3a10fd72cf336743891d6bc9a003cfeb5c3f0063.1480019834.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-11-24T21:08:41Z","receivedAt":"2016-11-24T21:08:57Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Nov 24, 2016 at 09:55:07PM +0100, Johannes Schindelin wrote:\n\n> +/*\n> + * NEEDSWORK: this function can go once the legacy-difftool Perl script is\n> + * retired.\n> + *\n> + * We intentionally avoid reading the config directly here, to avoid messing up\n> + * the GIT_* environment variables when we need to fall back to exec()ing the\n> + * Perl script.\n> + */\n> +static int use_builtin_difftool(void) {\n> +\tstruct child_process cp = CHILD_PROCESS_INIT;\n> +\tstruct strbuf out = STRBUF_INIT;\n> +\tint ret;\n> +\n> +\targv_array_pushl(&cp.args,\n> +\t\t\t \"config\", \"--bool\", \"difftool.usebuiltin\", NULL);\n> +\tcp.git_cmd = 1;\n> +\tif (capture_command(&cp, &out, 6))\n> +\t\treturn 0;\n> +\tstrbuf_trim(&out);\n> +\tret = !strcmp(\"true\", out.buf);\n> +\tstrbuf_release(&out);\n> +\treturn ret;\n> +}\n\nFWIW, it should mostly Just Work to use the internal config functions\nthese days, with the caveat that they will not read repo-level config if\nyou haven't done repo setup yet.\n\nI think it would probably be OK to ship with that caveat (people would\nprobably use --global config, or \"git -c\" for a quick override), but if\nyou really wanted to address it, you can do something like what\npager.c:read_early_config() does.\n\nOf course, your method here is fine, too; I just know you are sensitive\nto forking extra processes.\n\nAlso, a minor nit: capture_command() might return data in \"out\" with a\nnon-zero exit if the command both generates stdout and exits non-zero\nitself. I'm not sure that's possible with git-config, though, so it\nmight not be worth worrying about.\n\n-Peff\n"},{"id":"306417","messageId":"alpine.DEB.2.20.1611242211450.117539@virtualbox","threadId":"44532","inReplyTo":"20161124210841.c4yi2nv57tjfejgj@sigill.intra.peff.net","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-24T21:56:23Z","receivedAt":"2016-11-24T21:58:12Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Thu, 24 Nov 2016, Jeff King wrote:\n\n> On Thu, Nov 24, 2016 at 09:55:07PM +0100, Johannes Schindelin wrote:\n> \n> > +/*\n> > + * NEEDSWORK: this function can go once the legacy-difftool Perl script is\n> > + * retired.\n> > + *\n> > + * We intentionally avoid reading the config directly here, to avoid messing up\n> > + * the GIT_* environment variables when we need to fall back to exec()ing the\n> > + * Perl script.\n> > + */\n> > +static int use_builtin_difftool(void) {\n> > +\tstruct child_process cp = CHILD_PROCESS_INIT;\n> > +\tstruct strbuf out = STRBUF_INIT;\n> > +\tint ret;\n> > +\n> > +\targv_array_pushl(&cp.args,\n> > +\t\t\t \"config\", \"--bool\", \"difftool.usebuiltin\", NULL);\n> > +\tcp.git_cmd = 1;\n> > +\tif (capture_command(&cp, &out, 6))\n> > +\t\treturn 0;\n> > +\tstrbuf_trim(&out);\n> > +\tret = !strcmp(\"true\", out.buf);\n> > +\tstrbuf_release(&out);\n> > +\treturn ret;\n> > +}\n> \n> FWIW, it should mostly Just Work to use the internal config functions\n> these days, with the caveat that they will not read repo-level config if\n> you haven't done repo setup yet.\n> \n> I think it would probably be OK to ship with that caveat (people would\n> probably use --global config, or \"git -c\" for a quick override), but if\n> you really wanted to address it, you can do something like what\n> pager.c:read_early_config() does.\n\nThe config setting is already overkill (and does even make something much\nharder than before: running tests with the builtin difftool used to be as\nsimply as `touch use-builtin-difftool && make -C t t7800-difftool.sh, now\nI have to edit t7800-difftool.sh to configure difftool.useBuiltin, and\nwithout the repo-level config even that would not be working).\n\nImitating read_early_config() would be overkill deluxe.\n\n> Of course, your method here is fine, too; I just know you are sensitive\n> to forking extra processes.\n> \n> Also, a minor nit: capture_command() might return data in \"out\" with a\n> non-zero exit if the command both generates stdout and exits non-zero\n> itself. I'm not sure that's possible with git-config, though, so it\n> might not be worth worrying about.\n\nAs it is, I spent way too much time on a feature flag *that will go away\nreal soon*. And not only I spent too much time on it: everybody who even\nbothered to think about it spent too much time on it. It is a temporary\nfeature flag. It will go away. If it is inefficient, or inelegant, it\nwon't matter in a month from now.\n\nIn other words, it was not really necessary to spend all of that time and\nall of that brain power to first discuss the shortcomings of having the\npresence of a file in exec path as a feature flag, then converting it into\na config setting, only to find out that this *causes* problems that were\nnot there before.\n\nFrankly, that was not what I was hoping for. I was hoping to get a decent\nreview of the *difftool* functionality. David did a fine job to provide\nthat review. All that discussion about the temporary feature flag was\nreally a side track for me, and I hate to admit that I let myself get\nsucked into it, and it cost me quite some time that I would have rather\nspent on release engineering the preview based on v2.11.0-rc3 (including\nthe difftool with *any* type of working opt-in feature flag).\n\nMaybe I will write more explicitly in the next cover letter what I intend\nto do, and what parts of the patch series is intended to be throw-away\nmaterial (hence \"good enough\" is good enough, and discussions about it are\nreally wasting all of our time). Of course, I cannot dictate what other\npeople find interesting to comment on, but at least I will have a good\nexcuse to ignore suggestions that only distract from the work that is\nreally needed.\n\nMy only solace is that I did some substantially more intensive testing in\nthe wake of this discussion, thanks to the test suite breakages incurred\nby switching the feature flag to a soon-to-be-abandoned config setting.\nThat makes me much more confident about the builtin difftool, which is all\nI wanted in the first place.\n\nCiao,\nDscho\n"},{"id":"306421","messageId":"20161125031809.mueurvjijlnzy2s5@sigill.intra.peff.net","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1611242211450.117539@virtualbox","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-11-25T03:18:09Z","receivedAt":"2016-11-25T03:18:15Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Nov 24, 2016 at 10:56:23PM +0100, Johannes Schindelin wrote:\n\n> > I think it would probably be OK to ship with that caveat (people would\n> > probably use --global config, or \"git -c\" for a quick override), but if\n> > you really wanted to address it, you can do something like what\n> > pager.c:read_early_config() does.\n> \n> The config setting is already overkill (and does even make something much\n> harder than before: running tests with the builtin difftool used to be as\n> simply as `touch use-builtin-difftool && make -C t t7800-difftool.sh, now\n> I have to edit t7800-difftool.sh to configure difftool.useBuiltin, and\n> without the repo-level config even that would not be working).\n> \n> Imitating read_early_config() would be overkill deluxe.\n\nI would have expected it to just be a build-time flag, like:\n\n  make BUILTIN_DIFFTOOL=Yes test\n\nbut I did not closely follow the rest of the conversation, so I am\nprobably just repeating bits that were already said. So probably ignore\nme.\n\nI'm happy with pretty much anything under the reasoning of \"this does not\nmatter much because it is going away soon\".\n\n-Peff\n"},{"id":"306422","messageId":"alpine.DEB.2.20.1611251201580.117539@virtualbox","threadId":"44532","inReplyTo":"20161125031809.mueurvjijlnzy2s5@sigill.intra.peff.net","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-25T11:05:00Z","receivedAt":"2016-11-25T11:05:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Thu, 24 Nov 2016, Jeff King wrote:\n\n> On Thu, Nov 24, 2016 at 10:56:23PM +0100, Johannes Schindelin wrote:\n> \n> > > I think it would probably be OK to ship with that caveat (people would\n> > > probably use --global config, or \"git -c\" for a quick override), but if\n> > > you really wanted to address it, you can do something like what\n> > > pager.c:read_early_config() does.\n> > \n> > The config setting is already overkill (and does even make something much\n> > harder than before: running tests with the builtin difftool used to be as\n> > simply as `touch use-builtin-difftool && make -C t t7800-difftool.sh, now\n> > I have to edit t7800-difftool.sh to configure difftool.useBuiltin, and\n> > without the repo-level config even that would not be working).\n> > \n> > Imitating read_early_config() would be overkill deluxe.\n> \n> I would have expected it to just be a build-time flag, like:\n> \n>   make BUILTIN_DIFFTOOL=Yes test\n\nThat works for Git developers.\n\nI want to let as many users as possible test the builtin difftool.\nHopefully a lot more users than there are Git developers.\n\nWhich means that I need a feature flag in production code, not a build\ntime flag.\n\n> I'm happy with pretty much anything under the reasoning of \"this does not\n> matter much because it is going away soon\".\n\nYeah, well, I am more happy with anything along the lines of David's\nreview, pointing out flaws in the current revision of the builtin difftool\nbefore it bites users ;-)\n\nCiao,\nDscho\n"},{"id":"306432","messageId":"20161125171940.rizbqyhsygdsoujr@sigill.intra.peff.net","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1611251201580.117539@virtualbox","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-11-25T17:19:40Z","receivedAt":"2016-11-25T17:19:54Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Nov 25, 2016 at 12:05:00PM +0100, Johannes Schindelin wrote:\n\n> > I would have expected it to just be a build-time flag, like:\n> > \n> >   make BUILTIN_DIFFTOOL=Yes test\n> \n> That works for Git developers.\n> \n> I want to let as many users as possible test the builtin difftool.\n> Hopefully a lot more users than there are Git developers.\n> \n> Which means that I need a feature flag in production code, not a build\n> time flag.\n\nAh, I didn't realize that was a requirement. If this is going to be part\nof a release and real end-users are going to see it, that does make me\nthink the config option is the better path (than the presence of some\nfile), as it's our standard way of tweaking run-time behavior.\n\nThe implementation can still remain slightly gross if it's eventually\ngoing away.\n\n> > I'm happy with pretty much anything under the reasoning of \"this does not\n> > matter much because it is going away soon\".\n> \n> Yeah, well, I am more happy with anything along the lines of David's\n> review, pointing out flaws in the current revision of the builtin difftool\n> before it bites users ;-)\n\nSorry, I can't really help much there, not having much knowledge of\ndifftool.\n\n-Peff\n"},{"id":"306436","messageId":"alpine.DEB.2.20.1611251841030.117539@virtualbox","threadId":"44532","inReplyTo":"20161125171940.rizbqyhsygdsoujr@sigill.intra.peff.net","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-25T17:41:23Z","receivedAt":"2016-11-25T17:42:07Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Fri, 25 Nov 2016, Jeff King wrote:\n\n> On Fri, Nov 25, 2016 at 12:05:00PM +0100, Johannes Schindelin wrote:\n> \n> > > I would have expected it to just be a build-time flag, like:\n> > > \n> > >   make BUILTIN_DIFFTOOL=Yes test\n> > \n> > That works for Git developers.\n> > \n> > I want to let as many users as possible test the builtin difftool.\n> > Hopefully a lot more users than there are Git developers.\n> > \n> > Which means that I need a feature flag in production code, not a build\n> > time flag.\n> \n> Ah, I didn't realize that was a requirement. If this is going to be part\n> of a release and real end-users are going to see it, that does make me\n> think the config option is the better path (than the presence of some\n> file), as it's our standard way of tweaking run-time behavior.\n\nSo how do you easily switch back and forth between testing the old vs the\nnew difftool via the test suite?\n\nCiao,\nDscho\n"},{"id":"306437","messageId":"20161125174721.f35mzc276kdwakzm@sigill.intra.peff.net","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1611251841030.117539@virtualbox","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-11-25T17:47:22Z","receivedAt":"2016-11-25T17:47:39Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Nov 25, 2016 at 06:41:23PM +0100, Johannes Schindelin wrote:\n\n> > Ah, I didn't realize that was a requirement. If this is going to be part\n> > of a release and real end-users are going to see it, that does make me\n> > think the config option is the better path (than the presence of some\n> > file), as it's our standard way of tweaking run-time behavior.\n> \n> So how do you easily switch back and forth between testing the old vs the\n> new difftool via the test suite?\n\nIf it's for a specific tool, I'd consider teaching the test suite to run\nthe whole script twice: once with the flag set and once without.\n\nThat is sometimes more complicated, though, if the script creates many\nsub-repos. An environment variable might be more natural. If you already\nsupport flipping the default via config, you can probably do:\n\n  GIT_CONFIG_PARAMETERS=\"'difftool.usebuiltin=true'\"\n  export GIT_CONFIG_PARAMETERS\n\n-Peff\n"},{"id":"306441","messageId":"2994b0d6-4b6c-84e7-d0d5-257bcae3be98@gmail.com","threadId":"44532","inReplyTo":"ac91e4818cfb5c5af6b5874662dbeb61cde1f69d.1480019834.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v3 2/2] difftool: implement the functionality in the builtin","fromName":"Jakub Narębski","fromEmail":"jnareb@gmail.com","sentAt":"2016-11-25T21:24:33Z","receivedAt":"2016-11-25T21:26:05Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"W dniu 24.11.2016 o 21:55, Johannes Schindelin pisze:\n\n> The current version of the builtin difftool does not, however, make full\n> use of the internals but instead chooses to spawn a couple of Git\n> processes, still, to make for an easier conversion. There remains a lot\n> of room for improvement, left for a later date.\n[...]\n\n> Sadly, the speedup is more noticable on Linux than on Windows: a quick\n> test shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s)\n> (real/user/sys) in a Linux VM, down from  (6.529s/3.112s/0.644s), while\n> on Windows, it is (36.064s/2.730s/7.194s), down from\n> (47.637s/2.407s/6.863s). The culprit is most likely the overhead\n> incurred from *still* having to shell out to mergetool-lib.sh and\n> difftool--helper.sh.\n\nDoes this mean that our shell-based testsuite is not well suited to be\nbenchmark suite for comparing performance on MS Windows?\n\nOr does it mean that \"builtin-difftool\" spawning Git processes is the\nproblem?\n \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  builtin/difftool.c | 670 ++++++++++++++++++++++++++++++++++++++++++++++++++++-\n>  1 file changed, 669 insertions(+), 1 deletion(-)\n> \n> diff --git a/builtin/difftool.c b/builtin/difftool.c\n> index 53870bb..3480920 100644\n> --- a/builtin/difftool.c\n> +++ b/builtin/difftool.c\n> @@ -11,9 +11,608 @@\n>   *\n>   * Copyright (C) 2016 Johannes Schindelin\n>   */\n> +#include \"cache.h\"\n>  #include \"builtin.h\"\n>  #include \"run-command.h\"\n>  #include \"exec_cmd.h\"\n> +#include \"parse-options.h\"\n> +#include \"argv-array.h\"\n> +#include \"strbuf.h\"\n> +#include \"lockfile.h\"\n> +#include \"dir.h\"\n> +\n> +static char *diff_gui_tool;\n> +static int trust_exit_code;\n> +\n> +static const char *const builtin_difftool_usage[] = {\n> +\tN_(\"git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]\"),\n> +\tNULL\n> +};\n> +\n> +static int difftool_config(const char *var, const char *value, void *cb)\n> +{\n> +\tif (!strcmp(var, \"diff.guitool\")) {\n\nShouldn't you also read other configuration variables, like \"diff.tool\",\nand it's mergetool fallbacks (\"merge.guitool\", \"merge.tool\")?\n\n> +\t\tdiff_gui_tool = xstrdup(value);\n> +\t\treturn 0;\n> +\t}\n> +\n> +\tif (!strcmp(var, \"difftool.trustexitcode\")) {\n> +\t\ttrust_exit_code = git_config_bool(var, value);\n> +\t\treturn 0;\n> +\t}\n\nWhy you do not need to check \"difftool.prompt\"?  And \"mergetool.*\" fallbacks?\n\n> +\n> +\treturn git_default_config(var, value, cb);\n> +}\n> +\n> +static int print_tool_help(void)\n> +{\n> +\tconst char *argv[] = { \"mergetool\", \"--tool-help=diff\", NULL };\n> +\treturn run_command_v_opt(argv, RUN_GIT_CMD);\n\nThis looks a bit strange to me, but I guess this is to avoid recursively\ninvoking ourself, and { \"legacy-difftool\", \"--tool-help\", NULL }; isn't\nthat much better.\n\n> +}\n> +\n> +static int parse_index_info(char *p, int *mode1, int *mode2,\n> +\t\t\t    struct object_id *oid1, struct object_id *oid2,\n> +\t\t\t    char *status)\n\n  There are only two hard things in Computer Science:\n  cache invalidation and naming things.\n    -- Phil Karlton\n\nWhy did you name function that parses \"diff --raw\" output (aka \"diff-tree\"\noutput) parse_index_info() instead of parse_raw_diff() or parse_diff_tree()?\nI went searching for `update-index --index-info` formats...\n\nThis is not that important, because this function is file-static; though\nfuture developers would thank you for more descriptive naming.\n\nADDED: Disregard this, I see this function is about index (?) part of\nraw diff, that is only a part of \"git diff --raw -z\" output.  Though...\n\n> +{\n> +\tif (*p != ':')\n> +\t\treturn error(\"expected ':', got '%c'\", *p);\n> +\t*mode1 = (int)strtol(p + 1, &p, 8);\n> +\tif (*p != ' ')\n> +\t\treturn error(\"expected ' ', got '%c'\", *p);\n\nNitpicking.\n\nI guess because this error shouldn't really happen, and because current\nimplementation is transient, we don't need to worry about better error\nmessages (was it problem with parsing, or was it unexpected character).\n\nFor example '10064x', or '10064\\n' would fail parse, but it is not\nspace that we were expecting...\n\n> +\t*mode2 = (int)strtol(p + 1, &p, 8);\n> +\tif (*p != ' ')\n> +\t\treturn error(\"expected ' ', got '%c'\", *p);\n> +\tif (get_oid_hex(++p, oid1))\n> +\t\treturn error(\"expected object ID, got '%s'\", p + 1);\n> +\tp += GIT_SHA1_HEXSZ;\n> +\tif (*p != ' ')\n> +\t\treturn error(\"expected ' ', got '%c'\", *p);\n> +\tif (get_oid_hex(++p, oid2))\n> +\t\treturn error(\"expected object ID, got '%s'\", p + 1);\n> +\tp += GIT_SHA1_HEXSZ;\n> +\tif (*p != ' ')\n> +\t\treturn error(\"expected ' ', got '%c'\", *p);\n> +\t*status = *++p;\n> +\tif (!status || p[1])\n> +\t\treturn error(\"unexpected trailer: '%s'\", p);\n> +\treturn 0;\n> +}\n> +\n> +/*\n> + * Remove any trailing slash from $workdir\n> + * before starting to avoid double slashes in symlink targets.\n> + */\n\nErr... that's not what add_path() does, in its current implementation.\nIt doesn't remove trailing slashes, but it checks if there is trailing\nslash, and if there isn't, it adds it as separator before adding path.\n\nOr was it original comment from the Perl implementation?  It look\nlike this, with '$workdir'...  If it is meant to be straight copy\nof comment from legacy-difftool, a note would be nice.\n\n> +static void add_path(struct strbuf *buf, size_t base_len, const char *path)\n\nNaming: I think strbuf_addpath() would be a better name, but I guess\nit is a matter of taste.\n\n> +{\n> +\tstrbuf_setlen(buf, base_len);\n> +\tif (buf->len && buf->buf[buf->len - 1] != '/')\n> +\t\tstrbuf_addch(buf, '/');\n> +\tstrbuf_addstr(buf, path);\n> +}\n> +\n> +/*\n> + * Determine whether we can simply reuse the file in the worktree.\n> + */\n> +static int use_wt_file(const char *workdir, const char *name,\n\nShould it be 'name' or 'pathname'?\n\n> +\t\t       struct object_id *oid)\n> +{\n> +\tstruct strbuf buf = STRBUF_INIT;\n> +\tstruct stat st;\n> +\tint use = 0;\n> +\n> +\tstrbuf_addstr(&buf, workdir);\n> +\tadd_path(&buf, buf.len, name);\n\nWith proposed rename, it would IMVVVHO looks better\n\n  +\tstrbuf_addstr(&buf, workdir);\n  +\tstrbuf_addpath(&buf, buf.len, name);\n\nBut that is a matter of taste (again, the function is file-local).\n\n> +\n> +\tif (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) {\n> +\t\tstruct object_id wt_oid;\n> +\t\tint fd = open(buf.buf, O_RDONLY);\n> +\n> +\t\tif (!index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {\n> +\t\t\tif (is_null_oid(oid)) {\n> +\t\t\t\toidcpy(oid, &wt_oid);\n> +\t\t\t\tuse = 1;\n> +\t\t\t} else if (!oidcmp(oid, &wt_oid))\n> +\t\t\t\tuse = 1;\n> +\t\t}\n> +\t}\n> +\n> +\tstrbuf_release(&buf);\n> +\n> +\treturn use;\n> +}\n\n[...]\n\n> +static int ensure_leading_directories(char *path)\n> +{\n> +\tswitch (safe_create_leading_directories(path)) {\n> +\t\tcase SCLD_OK:\n> +\t\tcase SCLD_EXISTS:\n> +\t\t\treturn 0;\n> +\t\tdefault:\n> +\t\t\treturn error(_(\"could not create leading directories \"\n> +\t\t\t\t       \"of '%s'\"), path);\n> +\t}\n> +}\n\nNice function, I wonder if it would be useful in other places.\n\n> +\n> +static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,\n> +\t\t\tint argc, const char **argv)\n> +{\n> +\tchar tmpdir[PATH_MAX];\n> +\tstruct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;\n> +\tstruct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;\n\nNitpicking.\n\nTo be symmetric, it could be reordered like this:\n\n  +\tstruct strbuf info = STRBUF_INIT, buf = STRBUF_INIT;\n  +\tstruct strbuf lpath = STRBUF_INIT, rpath = STRBUF_INIT;\n\nSee: lpath, rpath; ldir, rdir; ldir_len, rdir_len.\n\n> +\tstruct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;\n> +\tstruct strbuf wtdir = STRBUF_INIT;\n> +\tsize_t ldir_len, rdir_len, wtdir_len;\n\n[...]\n> +\targv_array_pushl(&child.args, \"diff\", \"--raw\", \"--no-abbrev\", \"-z\",\n> +\t\t\t NULL);\n> +\tfor (i = 0; i < argc; i++)\n> +\t\targv_array_push(&child.args, argv[i]);\n> +\tif (start_command(&child))\n> +\t\tdie(\"could not obtain raw diff\");\n> +\tfp = xfdopen(child.out, \"r\");\n> +\n> +\t/* Build index info for left and right sides of the diff */\n> +\ti = 0;\n> +\twhile (!strbuf_getline_nul(&info, fp)) {\n> +\t\tint lmode, rmode;\n> +\t\tstruct object_id loid, roid;\n> +\t\tchar status;\n> +\t\tconst char *src_path, *dst_path;\n> +\t\tsize_t src_path_len, dst_path_len;\n> +\n> +\t\tif (starts_with(info.buf, \"::\"))\n> +\t\t\tdie(N_(\"combined diff formats('-c' and '--cc') are \"\n> +\t\t\t       \"not supported in\\n\"\n> +\t\t\t       \"directory diff mode('-d' and '--dir-diff').\"));\n> +\n> +\t\tif (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,\n> +\t\t\t\t     &status))\n\nAfter rename it would read as:\n\n  +\t\tif (parse_raw_diff(info.buf, &lmode, &rmode, &loid, &roid,\n  +\t\t\t\t   &status))\n\nThough now I see that you parse here index information of raw diff\n(I think)... so disregard my musings.\n\n> +\t\t\tbreak;\n> +\t\tif (strbuf_getline_nul(&lpath, fp))\n> +\t\t\tbreak;\n> +\t\tsrc_path = lpath.buf;\n> +\t\tsrc_path_len = lpath.len;\n> +\n> +\t\ti++;\n> +\t\tif (status != 'C' && status != 'R') {\n> +\t\t\tdst_path = src_path;\n> +\t\t\tdst_path_len = src_path_len;\n> +\t\t} else {\n> +\t\t\tif (strbuf_getline_nul(&rpath, fp))\n> +\t\t\t\tbreak;\n> +\t\t\tdst_path = rpath.buf;\n> +\t\t\tdst_path_len = rpath.len;\n> +\t\t}\n\n\n[...]\n> +\t/*\n> +\t * Changes to submodules require special treatment.This loop writes a\n\nHere and in few other places you are missing space after full stop.\n\n  +\t * Changes to submodules require special treatment. This loop writes a\n\n\n> +\t * temporary file to both the left and right directories to show the\n> +\t * change in the recorded SHA1 for the submodule.\n> +\t */\n> +\thashmap_iter_init(&submodules, &iter);\n> +\twhile ((entry = hashmap_iter_next(&iter))) {\n> +\t\tif (*entry->left) {\n> +\t\t\tadd_path(&ldir, ldir_len, entry->path);\n> +\t\t\tensure_leading_directories(ldir.buf);\n> +\t\t\twrite_file(ldir.buf, \"%s\", entry->left);\n> +\t\t}\n> +\t\tif (*entry->right) {\n> +\t\t\tadd_path(&rdir, rdir_len, entry->path);\n> +\t\t\tensure_leading_directories(rdir.buf);\n> +\t\t\twrite_file(rdir.buf, \"%s\", entry->right);\n> +\t\t}\n> +\t}\n> +\n> +\t/*\n> +\t * Symbolic links require special treatment.The standard \"git diff\"\n\nSame here.\n\n> +\t * shows only the link itself, not the contents of the link target.\n> +\t * This loop replicates that behavior.\n> +\t */\n\nBest,\n-- \nJakub Narębski\n\n"},{"id":"306443","messageId":"alpine.DEB.2.20.1611261320050.117539@virtualbox","threadId":"44532","inReplyTo":"20161125174721.f35mzc276kdwakzm@sigill.intra.peff.net","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-26T12:22:28Z","receivedAt":"2016-11-26T12:23:05Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Fri, 25 Nov 2016, Jeff King wrote:\n\n> On Fri, Nov 25, 2016 at 06:41:23PM +0100, Johannes Schindelin wrote:\n> \n> > > Ah, I didn't realize that was a requirement. If this is going to be part\n> > > of a release and real end-users are going to see it, that does make me\n> > > think the config option is the better path (than the presence of some\n> > > file), as it's our standard way of tweaking run-time behavior.\n> > \n> > So how do you easily switch back and forth between testing the old vs the\n> > new difftool via the test suite?\n> \n> If it's for a specific tool, I'd consider teaching the test suite to run\n> the whole script twice: once with the flag set and once without.\n> \n> That is sometimes more complicated, though, if the script creates many\n> sub-repos. An environment variable might be more natural. If you already\n> support flipping the default via config, you can probably do:\n> \n>   GIT_CONFIG_PARAMETERS=\"'difftool.usebuiltin=true'\"\n>   export GIT_CONFIG_PARAMETERS\n\nExcept that that does not work, of course. To figure out why, apply this\ndiff:\n\n-- snip --\ndiff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh\nindex 17f3008277..27159f65f3 100755\n--- a/t/t7800-difftool.sh\n+++ b/t/t7800-difftool.sh\n@@ -10,6 +10,9 @@ Testing basic diff tool invocation\n \n . ./test-lib.sh\n \n+echo \"config $(git config difftool.usebuiltin).\" >&2\n+exit 1\n+\n difftool_test_setup ()\n {\n \ttest_config diff.tool test-tool &&\ndiff --git a/t/test-lib.sh b/t/test-lib.sh\nindex 9980a46133..0ddeded92b 100644\n--- a/t/test-lib.sh\n+++ b/t/test-lib.sh\n@@ -86,6 +86,7 @@ EDITOR=:\n # /usr/xpg4/bin/sh and /bin/ksh to bail out.  So keep the unsets\n # deriving from the command substitution clustered with the other\n # ones.\n+echo \"before $(git config difftool.usebuiltin).\" >&2\n unset VISUAL EMAIL LANGUAGE COLUMNS $(\"$PERL_PATH\" -e '\n \tmy @env = keys %ENV;\n \tmy $ok = join(\"|\", qw(\n@@ -104,6 +105,7 @@ unset VISUAL EMAIL LANGUAGE COLUMNS $(\"$PERL_PATH\" -e\n'\n \tmy @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env);\n \tprint join(\"\\n\", @vars);\n ')\n+echo \"after $(git config difftool.usebuiltin).\" >&2\n unset XDG_CONFIG_HOME\n unset GITPERLLIB\n GIT_AUTHOR_EMAIL=author@example.com\n-- snap --\n\nand then weep at this output:\n\nGIT_CONFIG_PARAMETERS=\"'difftool.usebuiltin=true'\"; export GIT_CONFIG_PARAMETERS; bash t7800-difftool.sh -i -v -x\nbefore true.\nafter .\nInitialized empty Git repository in\n/home/virtualbox/git/git-for-windows/t/trash\ndirectory.t7800-difftool/.git/\nconfig .\nFATAL: Unexpected exit with code 1\n\nIn other words, GIT_CONFIG_PARAMETERS is *explicitly scrubbed* from the\nenvironment when we run our tests (by the code block between the \"before\"\nand the \"after\" statements in the diff above).\n\nCiao,\nDscho\n"},{"id":"306451","messageId":"20161126161907.xol62zytn2jb45gh@sigill.intra.peff.net","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1611261320050.117539@virtualbox","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-11-26T16:19:07Z","receivedAt":"2016-11-26T16:19:15Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Nov 26, 2016 at 01:22:28PM +0100, Johannes Schindelin wrote:\n\n> In other words, GIT_CONFIG_PARAMETERS is *explicitly scrubbed* from the\n> environment when we run our tests (by the code block between the \"before\"\n> and the \"after\" statements in the diff above).\n\nSorry if I wasn't clear. I meant to modify t7800 to run the tests twice,\nonce with the existing script and once with the builtin. I.e., to set\nthe variable after test-lib.sh has done its scrubbing, and then use a\nloop or similar to go through the tests twice.\n\nIf you want to control it from outside the test script, you'd need\nsomething like:\n\n  if test \"$GIT_TEST_DIFFTOOL\" = \"builtin\"\n  then\n\tGIT_CONFIG_PARAMETERS=...\n  fi\n\n-Peff\n"},{"id":"306459","messageId":"alpine.DEB.2.20.1611261400300.117539@virtualbox","threadId":"44532","inReplyTo":"20161126161907.xol62zytn2jb45gh@sigill.intra.peff.net","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-26T13:01:36Z","receivedAt":"2016-11-27T10:58:44Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Sat, 26 Nov 2016, Jeff King wrote:\n\n> On Sat, Nov 26, 2016 at 01:22:28PM +0100, Johannes Schindelin wrote:\n> \n> > In other words, GIT_CONFIG_PARAMETERS is *explicitly scrubbed* from the\n> > environment when we run our tests (by the code block between the \"before\"\n> > and the \"after\" statements in the diff above).\n> \n> Sorry if I wasn't clear. I meant to modify t7800 to run the tests twice,\n> once with the existing script and once with the builtin. I.e., to set\n> the variable after test-lib.sh has done its scrubbing, and then use a\n> loop or similar to go through the tests twice.\n> \n> If you want to control it from outside the test script, you'd need\n> something like:\n> \n>   if test \"$GIT_TEST_DIFFTOOL\" = \"builtin\"\n\nThat is a bit magic. I first used \"GIT_USE_BUILTIN_DIFFTOOL\" and it did\nnot work. My name is arguably more correct (see also Jakub's note about\n\"naming is hard\"), but yours works because there is a \"TEST\" substring in\nit.\n\nCiao,\nDscho\n"},{"id":"306460","messageId":"alpine.DEB.2.20.1611261407520.117539@virtualbox","threadId":"44532","inReplyTo":"2994b0d6-4b6c-84e7-d0d5-257bcae3be98@gmail.com","subject":"Re: [PATCH v3 2/2] difftool: implement the functionality in the builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-27T11:10:24Z","receivedAt":"2016-11-27T11:10:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Jakub,\n\nOn Fri, 25 Nov 2016, Jakub Narębski wrote:\n\n> W dniu 24.11.2016 o 21:55, Johannes Schindelin pisze:\n> \n> > The current version of the builtin difftool does not, however, make\n> > full use of the internals but instead chooses to spawn a couple of Git\n> > processes, still, to make for an easier conversion. There remains a\n> > lot of room for improvement, left for a later date.\n> [...]\n> \n> > Sadly, the speedup is more noticable on Linux than on Windows: a quick\n> > test shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s)\n> > (real/user/sys) in a Linux VM, down from  (6.529s/3.112s/0.644s),\n> > while on Windows, it is (36.064s/2.730s/7.194s), down from\n> > (47.637s/2.407s/6.863s). The culprit is most likely the overhead\n> > incurred from *still* having to shell out to mergetool-lib.sh and\n> > difftool--helper.sh.\n> \n> Does this mean that our shell-based testsuite is not well suited to be\n> benchmark suite for comparing performance on MS Windows?\n\nIt is quite likely the case that shell-based testing will always be\ninappropriate for performance testing. Even on Linux.\n\n> Or does it mean that \"builtin-difftool\" spawning Git processes is the\n> problem?\n\nAt the moment I would have to guess, and I'd rather not.\n\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> >  builtin/difftool.c | 670 ++++++++++++++++++++++++++++++++++++++++++++++++++++-\n> >  1 file changed, 669 insertions(+), 1 deletion(-)\n> > \n> > diff --git a/builtin/difftool.c b/builtin/difftool.c\n> > index 53870bb..3480920 100644\n> > --- a/builtin/difftool.c\n> > +++ b/builtin/difftool.c\n> > @@ -11,9 +11,608 @@\n> >   *\n> >   * Copyright (C) 2016 Johannes Schindelin\n> >   */\n> > +#include \"cache.h\"\n> >  #include \"builtin.h\"\n> >  #include \"run-command.h\"\n> >  #include \"exec_cmd.h\"\n> > +#include \"parse-options.h\"\n> > +#include \"argv-array.h\"\n> > +#include \"strbuf.h\"\n> > +#include \"lockfile.h\"\n> > +#include \"dir.h\"\n> > +\n> > +static char *diff_gui_tool;\n> > +static int trust_exit_code;\n> > +\n> > +static const char *const builtin_difftool_usage[] = {\n> > +\tN_(\"git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]\"),\n> > +\tNULL\n> > +};\n> > +\n> > +static int difftool_config(const char *var, const char *value, void *cb)\n> > +{\n> > +\tif (!strcmp(var, \"diff.guitool\")) {\n> \n> Shouldn't you also read other configuration variables, like \"diff.tool\",\n> and it's mergetool fallbacks (\"merge.guitool\", \"merge.tool\")?\n\nNo, as those configuration variables are not used by the builtin difftool\ndirectly but read by subsequently spawned commands separately. There would\nbe no use reading them here, for now.\n\n> > +static int print_tool_help(void)\n> > +{\n> > +\tconst char *argv[] = { \"mergetool\", \"--tool-help=diff\", NULL };\n> > +\treturn run_command_v_opt(argv, RUN_GIT_CMD);\n> \n> This looks a bit strange to me, but I guess this is to avoid recursively\n> invoking ourself, and { \"legacy-difftool\", \"--tool-help\", NULL }; isn't\n> that much better.\n\nThis is obviously a straight translation of the Perl script (see\nhttps://github.com/git/git/blob/v2.10.2/git-difftool.perl#L40-L46):\n\n\tsub print_tool_help\n\t{\n\t\t# See the comment at the bottom of file_diff() for the reason\n\t\t# behind\n\t\t# using system() followed by exit() instead of exec().\n\t\tmy $rc = system(qw(git mergetool --tool-help=diff));\n\t\texit($rc | ($rc >> 8));\n\t}\n\nI read the rest of your review, but it appears that it is more about\nstyle than about substance, while I am only willing to address the latter\nissues at the moment. You see, I want to focus on getting difftool correct\nfirst before attempting to make it pretty.\n\nCiao,\nDscho"},{"id":"306461","messageId":"CANQwDwcbsCTWsS8oFJaC8+dsLpqjivfxJt741G3QpCvir-_WAg@mail.gmail.com","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1611261407520.117539@virtualbox","subject":"Re: [PATCH v3 2/2] difftool: implement the functionality in the builtin","fromName":"Jakub Narębski","fromEmail":"jnareb@gmail.com","sentAt":"2016-11-27T11:20:17Z","receivedAt":"2016-11-27T11:21:04Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Hello Johannes,\n\nOn 27 November 2016 at 12:10, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> On Fri, 25 Nov 2016, Jakub Narębski wrote:\n> > W dniu 24.11.2016 o 21:55, Johannes Schindelin pisze:\n\n[...]\n> > > +static int difftool_config(const char *var, const char *value, void *cb)\n> > > +{\n> > > +   if (!strcmp(var, \"diff.guitool\")) {\n> >\n> > Shouldn't you also read other configuration variables, like \"diff.tool\",\n> > and it's mergetool fallbacks (\"merge.guitool\", \"merge.tool\")?\n>\n> No, as those configuration variables are not used by the builtin difftool\n> directly but read by subsequently spawned commands separately. There would\n> be no use reading them here, for now.\n\nAh, all right then.\n\nThough NEEDSWORK comment would be nice to have here (for when\nwe don't spawn commands).\n\n[...]\n> I read the rest of your review, but it appears that it is more about\n> style than about substance, while I am only willing to address the latter\n> issues at the moment. You see, I want to focus on getting difftool correct\n> first before attempting to make it pretty.\n>\n> Ciao,\n> Dscho\n\nWell, excet for the submodule-relates stuff, which I have skipped,\nit looks good to me.\n-- \nJakub Narębski\n"},{"id":"306465","messageId":"20161127165058.uxujjehyjq7httro@sigill.intra.peff.net","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1611261400300.117539@virtualbox","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-11-27T16:50:59Z","receivedAt":"2016-11-27T16:51:08Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Nov 26, 2016 at 02:01:36PM +0100, Johannes Schindelin wrote:\n\n> > If you want to control it from outside the test script, you'd need\n> > something like:\n> > \n> >   if test \"$GIT_TEST_DIFFTOOL\" = \"builtin\"\n> \n> That is a bit magic. I first used \"GIT_USE_BUILTIN_DIFFTOOL\" and it did\n> not work. My name is arguably more correct (see also Jakub's note about\n> \"naming is hard\"), but yours works because there is a \"TEST\" substring in\n> it.\n\nYes. You are free to add an exception to the env list in test-lib.sh,\nbut we usually use GIT_TEST_* to avoid having to do so.\n\n-Peff\n"},{"id":"306499","messageId":"xmqqa8cjlekl.fsf@gitster.mtv.corp.google.com","threadId":"44532","inReplyTo":"20161127165058.uxujjehyjq7httro@sigill.intra.peff.net","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-11-28T17:06:02Z","receivedAt":"2016-11-28T17:06:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Sat, Nov 26, 2016 at 02:01:36PM +0100, Johannes Schindelin wrote:\n>\n>> > If you want to control it from outside the test script, you'd need\n>> > something like:\n>> > \n>> >   if test \"$GIT_TEST_DIFFTOOL\" = \"builtin\"\n>> \n>> That is a bit magic. I first used \"GIT_USE_BUILTIN_DIFFTOOL\" and it did\n>> not work. My name is arguably more correct (see also Jakub's note about\n>> \"naming is hard\"), but yours works because there is a \"TEST\" substring in\n>> it.\n>\n> Yes. You are free to add an exception to the env list in test-lib.sh,\n> but we usually use GIT_TEST_* to avoid having to do so.\n\nPerhaps\n\n - The switch between \"do I use builtin, or scripted?\" mechanism in\n   1/2 can look at an environment (just like the old \"am\" rewrite\n   series did), instead of configuration.  This would make the code\n   a lot more simppler (you do not have to worry about the\n   interaction between \"setup\" and .git/config).\n\n - That environment variable can be named GIT_TEST_BUILTIN_DIFFTOOL;\n   after all, people are opting into helping to test the new shiny\n   to make/prove it ready sooner.\n\n - The bulk of the existing test for difftool can be moved to a\n   dot-included file (in a way similar to t/annotate-tests are\n   usable to test both annotate and blame-imitating-annotate).\n   Existing PERL prerequisites can all be lost.\n\n - Two tests can include that dot-included file; one would\n   explicitly unset that environment (and gives up without PERL\n   prerequisite), while the other explicitly sets it.\n\n"},{"id":"306501","messageId":"alpine.DEB.2.20.1611281830040.117539@virtualbox","threadId":"44532","inReplyTo":"xmqqa8cjlekl.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-28T17:34:17Z","receivedAt":"2016-11-28T17:34:51Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Mon, 28 Nov 2016, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > On Sat, Nov 26, 2016 at 02:01:36PM +0100, Johannes Schindelin wrote:\n> >\n> >> > If you want to control it from outside the test script, you'd need\n> >> > something like:\n> >> > \n> >> >   if test \"$GIT_TEST_DIFFTOOL\" = \"builtin\"\n> >> \n> >> That is a bit magic. I first used \"GIT_USE_BUILTIN_DIFFTOOL\" and it did\n> >> not work. My name is arguably more correct (see also Jakub's note about\n> >> \"naming is hard\"), but yours works because there is a \"TEST\" substring in\n> >> it.\n> >\n> > Yes. You are free to add an exception to the env list in test-lib.sh,\n> > but we usually use GIT_TEST_* to avoid having to do so.\n> \n> Perhaps\n> \n>  - The switch between \"do I use builtin, or scripted?\" mechanism in\n>    1/2 can look at an environment (just like the old \"am\" rewrite\n>    series did), instead of configuration.  This would make the code\n>    a lot more simppler (you do not have to worry about the\n>    interaction between \"setup\" and .git/config).\n> \n>  - That environment variable can be named GIT_TEST_BUILTIN_DIFFTOOL;\n>    after all, people are opting into helping to test the new shiny\n>    to make/prove it ready sooner.\n> \n>  - The bulk of the existing test for difftool can be moved to a\n>    dot-included file (in a way similar to t/annotate-tests are\n>    usable to test both annotate and blame-imitating-annotate).\n>    Existing PERL prerequisites can all be lost.\n> \n>  - Two tests can include that dot-included file; one would\n>    explicitly unset that environment (and gives up without PERL\n>    prerequisite), while the other explicitly sets it.\n\nIf my main worry was the test suite, I would agree with this plan.\n\nHowever, I have been bitten time and again by problems that occurred only\nin production, our test suite (despite taking already waaaaaay too long to\nbe truly useful in my daily development) was simply not good enough.\n\nSo my plan was different: to let end users opt-in to test this new beast\nthoroughly, more thoroughly than any review would.\n\nAnd for that, environment variables are just not an option. I need\nsomething that can be configured in a portable application, so that the\nmain Git for Windows installation is unaffected.\n\nMy original \"create a file in libexec/git-core/\" was simple, did the job\nreliably, and worked also for testing.\n\nIt is a pity that you two gentlemen shot it down for being inelegant. And\never since, we try to find a solution that is as simple, works as\nreliably, also for testing, *and* appeases your tastes.\n\nCiao,\nDscho\n"},{"id":"306512","messageId":"xmqqa8cjjtfn.fsf@gitster.mtv.corp.google.com","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1611281830040.117539@virtualbox","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-11-28T19:27:56Z","receivedAt":"2016-11-28T19:28:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> However, I have been bitten time and again by problems that occurred only\n> in production, our test suite (despite taking already waaaaaay too long to\n> be truly useful in my daily development) was simply not good enough.\n>\n> So my plan was different: to let end users opt-in to test this new beast\n> thoroughly, more thoroughly than any review would.\n\nI agree with that 100%.  \n\nWe need to ensure \"fallback to known working code\" escape hatch is\nrobust for that plan to work well, and that is why (1) I have been\nmore focused on getting 1/2 right, and (2) I do not think it should\nbe Windows-only like in your early plan, and (3) I do not think it\nwould be \"this will merely be there for only a month or so\", like\nyou said earlier.\n\n> And for that, environment variables are just not an option. I need\n> something that can be configured in a portable application, so that the\n> main Git for Windows installation is unaffected.\n\nI am not sure I follow here.  \n\nAre you saying that the users who are opting into the experiment\nwill keep two installations, one for daily use that avoids getting\nhit by the experimental code and the other that is used for testing?\nHow are they switching between the two?  By using different %PATH%?\nI am not sure how it is different from setting an environment\n$GIT_TEST_BUILTIN_DIFFTOOL.\n\nIn any case, I do not have strong preference between environment and\nconfiguration.  If you can make 1/2 robust with configuration, that\nis just as well.  My message you are responding to was merely to\nsuggest another possibility.\n\nThe latter two points in my four-bullet list are hopefully still\nviable if you go with the configuration; it may go something like:\n\n - The bulk of the tests is moved into a common dot-sourced file,\n   with (1) PERL prerequite stripped and (2) \"git difftool\" replaced\n   with $git_difftool\n\n - Two test files do one of\n\n    git_difftool=\"git difftool\"\n    git_difftool=\"git -c difftool.useBuiltin=true difftool\"\n\n   and include the dot-sourced file.  The one that does the former\n   needs to give up early depending on PERL prerequisite.\n\nperhaps.\n\n> My original \"create a file in libexec/git-core/\" was simple, did the job\n> reliably, and worked also for testing.\n\nIt may have been OK for quick-and-dirty hack during development, but\nI do not think it was good in anything released.\n"},{"id":"306568","messageId":"alpine.DEB.2.20.1611292128340.117539@virtualbox","threadId":"44532","inReplyTo":"xmqqa8cjjtfn.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-29T20:36:55Z","receivedAt":"2016-11-29T20:37:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Mon, 28 Nov 2016, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > However, I have been bitten time and again by problems that occurred only\n> > in production, our test suite (despite taking already waaaaaay too long to\n> > be truly useful in my daily development) was simply not good enough.\n> >\n> > So my plan was different: to let end users opt-in to test this new beast\n> > thoroughly, more thoroughly than any review would.\n> \n> I agree with that 100%.  \n> \n> [...]\n> \n> > And for that, environment variables are just not an option. I need\n> > something that can be configured in a portable application, so that the\n> > main Git for Windows installation is unaffected.\n> \n> I am not sure I follow here.  \n> \n> Are you saying that the users who are opting into the experiment\n> will keep two installations, one for daily use that avoids getting\n> hit by the experimental code and the other that is used for testing?\n\nI have obviously done a real bad job at explaining the Windows situation\nwell enough.\n\nMany, many users have multiple installations of Git for Windows. If you\nhave GitHub for Windows and installed the command-line tools: you got one.\nIf you installed Git for Windows, you got another one. If you installed\nVisual Studio, chances are you have another one. If you got any number of\nthird-party tools requiring Git functionality, you have another one.\n\nThey all live in separate directories that are their own little pseudo\nUnix root directory structures, complete with etc/, usr/, var/.\n\nUsers do not necessarily keep track, or for that matter, are aware of, the\nmultiple different installations.\n\nObviously, I do not want any installation other than the one the user just\ninstalled to pick up on the configuration.\n\nSo the suggestion by both you and Peff, to use an environment variable,\nwhich is either global, or requires the user to set it manually per\nsession, is simply not a good idea at all.\n\n> > My original \"create a file in libexec/git-core/\" was simple, did the job\n> > reliably, and worked also for testing.\n> \n> It may have been OK for quick-and-dirty hack during development, but\n> I do not think it was good in anything released.\n\nWell, you say that it is quick and dirty.\n\nI say it is the only viable solution I saw so far. All proposed\nalternative solutions fall flat on their bellies, simply by not working in\nall the cases I need them to work.\n\nAs I said elsewhere: I look for a correct solution first, and then I\nthrive to make it pretty. You start the other way round, and I do not have\ntime for that right now.\n\nCiao,\nDscho\n"},{"id":"306570","messageId":"20161129204915.lo45b2jz57dlfug5@sigill.intra.peff.net","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1611292128340.117539@virtualbox","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-11-29T20:49:15Z","receivedAt":"2016-11-29T20:49:48Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Nov 29, 2016 at 09:36:55PM +0100, Johannes Schindelin wrote:\n\n> So the suggestion by both you and Peff, to use an environment variable,\n> which is either global, or requires the user to set it manually per\n> session, is simply not a good idea at all.\n\nNo, my suggestion was to use config and have the test suite use an\nenvironment variable to test both cases (preferably automatically,\nwithout the user having to do anything).\n\nI do not see how that fails to cover all of your use cases.\n\n-Peff\n"},{"id":"306571","messageId":"xmqqshqadn0f.fsf@gitster.mtv.corp.google.com","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1611292128340.117539@virtualbox","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-11-29T20:55:28Z","receivedAt":"2016-11-29T20:55:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> So the suggestion by both you and Peff, to use an environment variable,\n> which is either global, or requires the user to set it manually per\n> session, is simply not a good idea at all.\n\nAs I already said, I do not have a strong preference between config\nand env.  I raised the env as a possible alternative that you can\nthink about its pros and cons, and as I already said, if you thought\nand your concluded that config would work better for your needs,\nthat is fine by me.\n"},{"id":"306587","messageId":"alpine.DEB.2.20.1611301325210.117539@virtualbox","threadId":"44532","inReplyTo":"xmqqshqadn0f.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-30T12:30:04Z","receivedAt":"2016-11-30T12:30:44Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Tue, 29 Nov 2016, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > So the suggestion by both you and Peff, to use an environment variable,\n> > which is either global, or requires the user to set it manually per\n> > session, is simply not a good idea at all.\n> \n> As I already said, I do not have a strong preference between config\n> and env.  I raised the env as a possible alternative that you can\n> think about its pros and cons, and as I already said, if you thought\n> and your concluded that config would work better for your needs,\n> that is fine by me.\n\nThe env flat out fails, on the grounds of not integrating nicely into a\nGit for Windows installer.\n\nThe config kinda works now. But for what price. It stole 4 hours I did not\nhave. When the libexec/git-core/use-builtin-difftool solution took me a\ngrand total of half an hour to devise, implement and test.\n\nAnd you know what? I still do not really see what is so bad about it.\n\nAnd I still see what is bad about the config \"solution\": it *creates* a\nchicken-and-egg problem with the order of config reading vs running\nscripts. It *creates* problems for requiring to spawn a `git config` call\nbecause reading the config in-process would mess up the global variables\nand environment *beyond repair*, making it *impossible* to even spawn the\ngit-legacy-difftool Perl script.\n\nIn short: the config setting now works. But it is ugly as hell. I wish I\nnever had listened to you.\n\nCiao,\nDscho\n"},{"id":"306588","messageId":"alpine.DEB.2.20.1611301330100.117539@virtualbox","threadId":"44532","inReplyTo":"20161129204915.lo45b2jz57dlfug5@sigill.intra.peff.net","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-11-30T12:30:47Z","receivedAt":"2016-11-30T12:31:05Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Tue, 29 Nov 2016, Jeff King wrote:\n\n> On Tue, Nov 29, 2016 at 09:36:55PM +0100, Johannes Schindelin wrote:\n> \n> > So the suggestion by both you and Peff, to use an environment variable,\n> > which is either global, or requires the user to set it manually per\n> > session, is simply not a good idea at all.\n> \n> No, my suggestion was to use config and have the test suite use an\n> environment variable to test both cases (preferably automatically,\n> without the user having to do anything).\n> \n> I do not see how that fails to cover all of your use cases.\n\nOh, so the suggestion is to have *both* a config *and* an environment\nvariable. That is not elegant.\n\nCiao,\nDscho\n"},{"id":"306590","messageId":"20161130123538.pydczcx6rbth7ng7@sigill.intra.peff.net","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1611301330100.117539@virtualbox","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-11-30T12:35:38Z","receivedAt":"2016-11-30T12:35:51Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Nov 30, 2016 at 01:30:47PM +0100, Johannes Schindelin wrote:\n\n> On Tue, 29 Nov 2016, Jeff King wrote:\n> \n> > On Tue, Nov 29, 2016 at 09:36:55PM +0100, Johannes Schindelin wrote:\n> > \n> > > So the suggestion by both you and Peff, to use an environment variable,\n> > > which is either global, or requires the user to set it manually per\n> > > session, is simply not a good idea at all.\n> > \n> > No, my suggestion was to use config and have the test suite use an\n> > environment variable to test both cases (preferably automatically,\n> > without the user having to do anything).\n> > \n> > I do not see how that fails to cover all of your use cases.\n> \n> Oh, so the suggestion is to have *both* a config *and* an environment\n> variable. That is not elegant.\n\nNo, that is not at all what I said.  I was going to explain myself\nagain, but I do not see what good it would do, as clearly my point did\nnot come across in the other three emails. And then you would just\ncomplain that I am making work for you. So whatever. I do not care about\nyour difftool topic at all. Do whatever you like (which hey, I already\nsaid before, too).\n\n-Peff\n"},{"id":"306591","messageId":"855b9172-7225-e09e-e46d-87940f9fda75@gmail.com","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1611281830040.117539@virtualbox","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Jakub Narębski","fromEmail":"jnareb@gmail.com","sentAt":"2016-11-30T16:02:08Z","receivedAt":"2016-11-30T16:02:23Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Hello,\n\nW dniu 28.11.2016 o 18:34, Johannes Schindelin pisze:\n\n> My original \"create a file in libexec/git-core/\" was simple, did the job\n> reliably, and worked also for testing.\n> \n> It is a pity that you two gentlemen shot it down for being inelegant. And\n> ever since, we try to find a solution that is as simple, works as\n> reliably, also for testing, *and* appeases your tastes.\n\nI just would like to note that existence of file is used for both\ngit-daemon and gitweb (the latter following the git-daemon example).\n\nSo there is a precedent for the use of this mechanism.\n\nBest,\n-- \nJakub Narębski\n\n"},{"id":"306594","messageId":"xmqqy400dd6w.fsf@gitster.mtv.corp.google.com","threadId":"44532","inReplyTo":"855b9172-7225-e09e-e46d-87940f9fda75@gmail.com","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-11-30T18:39:51Z","receivedAt":"2016-11-30T18:40:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jakub Narębski <jnareb@gmail.com> writes:\n\n>> My original \"create a file in libexec/git-core/\" was simple, did the job\n>> reliably, and worked also for testing.\n>> \n>> It is a pity that you two gentlemen shot it down for being inelegant. And\n>> ever since, we try to find a solution that is as simple, works as\n>> reliably, also for testing, *and* appeases your tastes.\n>\n> I just would like to note that existence of file is used for both\n> git-daemon and gitweb (the latter following the git-daemon example).\n>\n> So there is a precedent for the use of this mechanism.\n\nI think you are thinking about git-daemon-export-ok (for 'git\ndaemon') and $GITWEB_EXPORT_OK file (for 'gitweb').\n\nYou do realize that it is apples-and-oranges [*1*] to take these as\nanalogous to what Dscho is trying to do, don't you?\n\nFirst of all, these are to control access to each repository on the\nserver side; the presence of the file is checked in each repository.\nWhat Dscho wants is to control the behaviour of an installation of\nGit as a whole, no matter which repository is being accessed [*2*,\n*3*].\n\nMore importantly, did you notice that git-daemon-export-ok predates\nthe configuration mechanism by a large margin?  The \"does the file\nexist?\" check done in a87e8be2ae (\"Add a \"git-daemon\" that listens\non a TCP port\", 2005-07-13) is a relic from the past [*4*], and\n32f4aaccaa (\"gitweb: export options\", 2006-09-17) added\nGITWEB_EXPORT_OK to mimic it, also long time ago [*5*].  They are\nnot something you would want to mimic in new programs these days.\n\nBesides, $GIT_EXEC_PATH is where you place git subcommands.  Who in\nthe right mind considers it even remotely sane to design a system\nwhere you have to throw in a file that is not a command to /usr/bin\nto control the behaviour of your system? [*6*]\n\nSo the \"precedent\" is irrelevant in the first place, and even if it\nwere relevant, it is a bad piece of advice to mimic it.\n\n\n[Footnote]\n\n*1* Or is it apples-and-pineapples these days?\n\n*2* Not that I agree with that desire, if I understand him correctly\n    from his description against the approach based on an\n    environment variable.  If a user has multiple installations and\n    not even aware of which one of them s/he is currently using, a\n    mechanism that affects only one of them (instead of consistently\n    affecting all of them) would lead to more confusion, I would\n    think.  \n\n*3* If such hermetically configured independent installations are\n    desirable, etc/gitconfig aka \"git config --system\" is a more\n    appropriate thing to use, and you do not need to do repository\n    discovery before you can read it.\n\n*4* If we had config mechanism, we would have used it just like we\n    use daemon.* variables to control what services are enabled for\n    each repository.\n\n*5* By that time, the config mechanism did already exist, so the\n    GITWEB_EXPORT_OK could have been a per-repository configuration,\n    but \"gitweb\" had another excuse to deviate from the norm.  \"Is\n    this repository visible?\" was done during repository listing and\n    the script did not want to run \"git config\" in each and every\n    repository-like directory it encountered in File::Find::find().\n\n*6* And I do not think $GIT_EXEC_PATH vs /usr/bin is\n    apples-and-oranges analogy.\n"},{"id":"306775","messageId":"xmqqlgvz6x87.fsf@gitster.mtv.corp.google.com","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1611301325210.117539@virtualbox","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-12-01T23:33:28Z","receivedAt":"2016-12-01T23:33:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> The config kinda works now. But for what price. It stole 4 hours I did not\n> have. When the libexec/git-core/use-builtin-difftool solution took me a\n> grand total of half an hour to devise, implement and test.\n>\n> And you know what? I still do not really see what is so bad about it.\n\nI was wondering if I should explain myself again, even though I do\nnot see what good it would do, as clearly my point did not come\nacross in the other emails.  And then you would just complain that I\nam making work for you.  Clearly you do not seem to see why placing\nrandom files in $GIT_EXEC_PATH, which is a place for git subcommand\nimplementations, is wrong, so I won't repeat it to you again.\n\nBut you need to remember that you are not working on a Windows-only\nproject.  In non-Windows environment, many users would not have\nwrite access to /usr/libexec/git-core directory, but it is not just\neasy for them to write into ~/.gitconfig, but that is the way they\nare accustomed to, in order to affect the behaviour of Git for them.\n\nAs to \"I have to spawn config\", I think it is sensible to start the\ncmd_difftool() wrapper without adding RUN_SETUP to the command\ntable, then call git_config_get_bool() to check the configuration\nonly from system and per-user files, and then finally either call\ninto builtin_difftool() where setup_git_directory() is called, or\nspawn the scripted difftool, as Peff already said.  Your \"users\nopt-in while installing\" is not about setting per-repository option.\n\nCalling git_config*(), setup_git_directory() and then git_config*()\nin this order should be safe, as setup_git_directory() would clear\npotentially cached configuration values read by any previous\ngit_config*() calls, so any configuration enquiry made by\nbuiltin_difftool() would read from all three sources, not just\nsystem and per-user.\n\nSo there is no chicken-and-egg issue, either.\n"},{"id":"306924","messageId":"alpine.DEB.2.20.1612051126320.117539@virtualbox","threadId":"44532","inReplyTo":"xmqqlgvz6x87.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-12-05T10:36:19Z","receivedAt":"2016-12-05T10:36:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Thu, 1 Dec 2016, Junio C Hamano wrote:\n\n> As to \"I have to spawn config\", I think it is sensible to start the\n> cmd_difftool() wrapper without adding RUN_SETUP to the command table,\n> then call git_config_get_bool() to check the configuration only from\n> system and per-user files, and then finally either call into\n> builtin_difftool() where setup_git_directory() is called, or spawn the\n> scripted difftool, as Peff already said.\n\nI just spent a considerable amount of time to figure out that I overlooked\nyour comment about \"only from system and per-user files\".\n\n> Your \"users opt-in while installing\" is not about setting per-repository\n> option.\n\nWow. That would make things really inconsistent.\n\nI know that *I* would want to be able to switch that opt-in feature off\nfor repositories where I absolutely rely on some rock-solid difftool. And\nas a user I would not only be shocked when it would not work as expected,\nbut I would be *positively* shocked to learn that this is intended, by\ndesign.\n\nSeriously, do you really think it is a good idea to have\ngit_config_get_value() *ignore* any value in .git/config?\n\nReally?\n\nIt caught *me* by surprise, and it definitely makes for a very, very bad\nuser experience.\n\nAnd this is much more fundamental than just difftool.useBuiltin.\n\nI see for example that our pager.<cmd> setting is ignoring per-repository\nsettings. That is, if the user sets pager.<cmd> in ~/.gitconfig and then\ntries to override this in a specific repository (which any user would only\ndo for very good reasons, I am sure), Git would happily ignore that\ncareful setup and create an angry user.\n\nThe only reason this did not blow up in our face yet is that users\napparently do not make use of this feature yet. Which does not make this\nbehavior (that \"git_config_get_value()\" happily ignores .git/config) less\nbroken.\n\nWe need to fix this.\n\nCiao,\nDscho\n"},{"id":"306944","messageId":"xmqqy3zu43yk.fsf@gitster.mtv.corp.google.com","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1612051126320.117539@virtualbox","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-12-05T18:37:39Z","receivedAt":"2016-12-05T18:37:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Seriously, do you really think it is a good idea to have\n> git_config_get_value() *ignore* any value in .git/config?\n\nWhen we do not know \".git/\" directory we see is the repository we\nwere told to work on, then we should ignore \".git/config\" file.  So\nallowing git_config_get_value() to _ignore_ \".git/config\" before the\nprogram calls setup_git_directory() does have its uses.\n\nBut I agree that \"difftool.useBuiltin\" that flips between two\nimplementations is a different use case than the above.  Both\nimplementations eventually want to work on \".git/\" and would\nwant to read from \".git/config\".\n\n> We need to fix this.\n\nI have a feeling that \"environment modifications that cannot be\nundone\" we used as the rationale in 73c2779f42 (\"builtin-am:\nimplement skeletal builtin am\", 2015-08-04) might be overly\npessimistic and in order to implement undo_setup_git_directory(),\nall we need to do may just be matter of doing a chdir(2) back to\nprefix and unsetting GIT_PREFIX environment, but I haven't looked\ninto details of the setup sequence recently.\n\n"},{"id":"307000","messageId":"alpine.DEB.2.20.1612061411000.117539@virtualbox","threadId":"44532","inReplyTo":"xmqqy3zu43yk.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-12-06T13:16:35Z","receivedAt":"2016-12-06T13:26:56Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Mon, 5 Dec 2016, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > Seriously, do you really think it is a good idea to have\n> > git_config_get_value() *ignore* any value in .git/config?\n> \n> When we do not know \".git/\" directory we see is the repository we were\n> told to work on, then we should ignore \".git/config\" file.  So allowing\n> git_config_get_value() to _ignore_ \".git/config\" before the program\n> calls setup_git_directory() does have its uses.\n\nI think you are wrong. This is yet another inconsistent behavior that\nviolates the Law of Least Surprises.\n\n> > We need to fix this.\n> \n> I have a feeling that \"environment modifications that cannot be undone\"\n> we used as the rationale in 73c2779f42 (\"builtin-am: implement skeletal\n> builtin am\", 2015-08-04) might be overly pessimistic and in order to\n> implement undo_setup_git_directory(), all we need to do may just be\n> matter of doing a chdir(2) back to prefix and unsetting GIT_PREFIX\n> environment, but I haven't looked into details of the setup sequence\n> recently.\n\nThe problem is that we may not know enough anymore to \"undo\nsetup_git_directory()\", as some environment variables may have been set\nbefore calling Git. If we simply unset the environment variables, we do it\nincorrectly. On the other hand, the environment variables *may* have been\nset by setup_git_directory(). In which case we *do* have to unset them.\n\nThe entire setup_git_directory() logic is a bit of a historically-grown\ngarden.\n\nCiao,\nDscho\n"},{"id":"307001","messageId":"20161206133650.t7gkg4f6wzw3zxki@sigill.intra.peff.net","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1612061411000.117539@virtualbox","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-12-06T13:36:50Z","receivedAt":"2016-12-06T13:37:17Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Dec 06, 2016 at 02:16:35PM +0100, Johannes Schindelin wrote:\n\n> > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> > \n> > > Seriously, do you really think it is a good idea to have\n> > > git_config_get_value() *ignore* any value in .git/config?\n> > \n> > When we do not know \".git/\" directory we see is the repository we were\n> > told to work on, then we should ignore \".git/config\" file.  So allowing\n> > git_config_get_value() to _ignore_ \".git/config\" before the program\n> > calls setup_git_directory() does have its uses.\n> \n> I think you are wrong. This is yet another inconsistent behavior that\n> violates the Law of Least Surprises.\n\nThere are surprises in this code any way you turn.  If we have not\ncalled setup_git_directory(), then how does git_config_get_value() know\nif we are in a repository or not?\n\nShould it blindly look at \".git/config\"? Now your program behaves\ndifferently depending on whether you are in the top-level of the working\ntree.\n\nShould it speculatively do repo discovery, and use any discovered config\nfile? Now some commands respect config that they shouldn't (e.g.,\nrunning \"git init foo.git\" from inside another repository will\naccidentally pick up the value of core.sharedrepository from wherever\nyou happen to run it).\n\nSo I think the caller of the config code has to provide some kind of\ncontext about how it is expecting to run and how the value will be used.\n\nRight now if setup_git_directory() or similar hasn't been called, the\nconfig code does not look. Ideally there would be a way for a caller to\nsay \"I am running early and not even sure yet if we are in a repo;\nplease speculatively try to find repo config for me\".\n\nThe pager code does this manually, and without great accuracy; see the\nhack in pager.c's read_early_config(). I think the way forward is:\n\n  1. Make that an optional behavior in git_config_with_options() so\n     other spots can reuse it (probably alias lookup, and something like\n     your difftool config).\n\n  2. Make it more accurate. Right now it blindly looks in .git/config,\n     but it should be able to do the usual repo-detection (_without_\n     actually entering the repo) to try to find a possible config file.\n\n-Peff\n"},{"id":"307010","messageId":"alpine.DEB.2.20.1612061512190.117539@virtualbox","threadId":"44532","inReplyTo":"20161206133650.t7gkg4f6wzw3zxki@sigill.intra.peff.net","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-12-06T14:48:38Z","receivedAt":"2016-12-06T14:49:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Tue, 6 Dec 2016, Jeff King wrote:\n\n> On Tue, Dec 06, 2016 at 02:16:35PM +0100, Johannes Schindelin wrote:\n> \n> > > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> > > \n> > > > Seriously, do you really think it is a good idea to have\n> > > > git_config_get_value() *ignore* any value in .git/config?\n> > > \n> > > When we do not know \".git/\" directory we see is the repository we\n> > > were told to work on, then we should ignore \".git/config\" file.  So\n> > > allowing git_config_get_value() to _ignore_ \".git/config\" before the\n> > > program calls setup_git_directory() does have its uses.\n> > \n> > I think you are wrong. This is yet another inconsistent behavior that\n> > violates the Law of Least Surprises.\n> \n> There are surprises in this code any way you turn.  If we have not\n> called setup_git_directory(), then how does git_config_get_value() know\n> if we are in a repository or not?\n\nMy biggest surprise, frankly, would be that `git init` and `git clone` are\nnot special-cased.\n\n> Should it blindly look at \".git/config\"?\n\nAbsolutely not, of course. You did not need me to say that.\n\n> Now your program behaves differently depending on whether you are in the\n> top-level of the working tree.\n\nExactly. This, BTW, is already how the code would behave if anybody called\n`git_path()` before `setup_git_directory()`, as the former function\nimplicitly calls `setup_git_env()` which does *not* call\n`setup_git_directory()` but *does* set up `git_dir` which is then used by\n`do_git_config_sequence()`..\n\nWe have a few of these nasty surprises in our code base, where code\nsilently assumes that global state is set up correctly, and succeeds in\nsometimes surprising ways if it is not.\n\n> Should it speculatively do repo discovery, and use any discovered config\n> file?\n\nPersonally, I find the way we discover the repository most irritating. It\nseems that we have multiple, mutually incompatible code paths\n(`setup_git_directory()` and `setup_git_env()` come to mind already, and\nit does not help that consecutive calls to `setup_git_directory()` will\nyield a very unexpected outcome).\n\nJust try to explain to a veteran software engineer why you cannot call\n`setup_git_directory_gently()` multiple times and expect the very same\nreturn value every time.\n\nAnother irritation is that some commands that clearly would like to use a\nrepository if there is one (such as `git diff`) are *not* marked with\nRUN_SETUP_GENTLY, due to these unfortunate implementation details.\n\n> Now some commands respect config that they shouldn't (e.g., running \"git\n> init foo.git\" from inside another repository will accidentally pick up\n> the value of core.sharedrepository from wherever you happen to run it).\n\nRight. That points to another problem with the way we do things: we leak\nglobal state from discovering a git_dir, which means that we can neither\nundo nor override it.\n\nIf we discovered our git_dir in a robust manner, `git init` could say:\nhey, this git_dir is actually not what I wanted, here is what I want.\n\nLikewise, `git submodule` would eventually be able to run in the very same\nprocess as the calling `git`, as would a local fetch.\n\n> So I think the caller of the config code has to provide some kind of\n> context about how it is expecting to run and how the value will be used.\n\nYep.\n\nMaybe even go a step further and say that the config code needs a context\n\"object\".\n\n> Right now if setup_git_directory() or similar hasn't been called, the\n> config code does not look.\n\nCorrect.\n\nActually, half correct. If setup_git_directory() has not been called, but,\nsay, git_path() (and thereby implicitly setup_git_env()), the config code\n*does* look. At a hard-coded .git/config.\n\n> Ideally there would be a way for a caller to say \"I am running early and\n> not even sure yet if we are in a repo; please speculatively try to find\n> repo config for me\".\n\nAnd ideally, it would not roll *yet another* way to discover the git_dir,\nbut it would reuse the same function (fixing it not to chdir()\nunilaterally).\n\nOf course, not using `chdir()` means that we have to figure out symbolic\nlinks somehow, in case somebody works from a symlinked subdirectory, e.g.:\n\n\tln -s $PWD/t/ ~/test-directory\n\tcd ~/test-directory\n\tgit log\n\n> The pager code does this manually, and without great accuracy; see the\n> hack in pager.c's read_early_config().\n\nI saw it. And that is what triggered the mail you are responding to (you\nprobably saw my eye-rolling between the lines).\n\nThe real question is: can we fix this? Or is there simply too great\nreluctance to change the current code?\n\n> I think the way forward is:\n> \n>   1. Make that an optional behavior in git_config_with_options() so\n>      other spots can reuse it (probably alias lookup, and something like\n>      your difftool config).\n\nIdeally: *any* early call to `git_config_get_value()`. Make things less\nsurprising.\n\n>   2. Make it more accurate. Right now it blindly looks in .git/config,\n>      but it should be able to do the usual repo-detection (_without_\n>      actually entering the repo) to try to find a possible config file.\n\nThe real trick will be to convince Junio to have a single function for\ngit_dir discovery, I guess, lest we have multiple, slightly incompatible\nways to discover it. I expect a lot of resistance here, because we would\nhave to change tried-and-tested (if POLA-violating) code.\n\nCiao,\nDscho\n"},{"id":"307012","messageId":"20161206150955.mvq4ocamaei52bap@sigill.intra.peff.net","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1612061512190.117539@virtualbox","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-12-06T15:09:55Z","receivedAt":"2016-12-06T15:10:02Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Dec 06, 2016 at 03:48:38PM +0100, Johannes Schindelin wrote:\n\n> > Should it blindly look at \".git/config\"?\n> \n> Absolutely not, of course. You did not need me to say that.\n> \n> > Now your program behaves differently depending on whether you are in the\n> > top-level of the working tree.\n> \n> Exactly. This, BTW, is already how the code would behave if anybody called\n> `git_path()` before `setup_git_directory()`, as the former function\n> implicitly calls `setup_git_env()` which does *not* call\n> `setup_git_directory()` but *does* set up `git_dir` which is then used by\n> `do_git_config_sequence()`..\n> \n> We have a few of these nasty surprises in our code base, where code\n> silently assumes that global state is set up correctly, and succeeds in\n> sometimes surprising ways if it is not.\n\nRight. I have been working on fixing this. v2.11 has a ton of tweaks in\nthis area, and my patch to die() rather than default to \".git\" is\ncooking in next to catch any stragglers.\n\n> > Should it speculatively do repo discovery, and use any discovered config\n> > file?\n> \n> Personally, I find the way we discover the repository most irritating. It\n> seems that we have multiple, mutually incompatible code paths\n> (`setup_git_directory()` and `setup_git_env()` come to mind already, and\n> it does not help that consecutive calls to `setup_git_directory()` will\n> yield a very unexpected outcome).\n\nI agree. We should be killing off setup_git_env(), which is something\nI've been slowly working towards over the years.\n\nThere are some annoyances with setup_git_directory(), too (like the fact\nthat you cannot ask \"is there a git repository you can find\" without\nmaking un-recoverable changes to the process state). I think we should\nfix those, too.\n\n> > Now some commands respect config that they shouldn't (e.g., running \"git\n> > init foo.git\" from inside another repository will accidentally pick up\n> > the value of core.sharedrepository from wherever you happen to run it).\n> \n> Right. That points to another problem with the way we do things: we leak\n> global state from discovering a git_dir, which means that we can neither\n> undo nor override it.\n> \n> If we discovered our git_dir in a robust manner, `git init` could say:\n> hey, this git_dir is actually not what I wanted, here is what I want.\n> \n> Likewise, `git submodule` would eventually be able to run in the very same\n> process as the calling `git`, as would a local fetch.\n\nYep, I agree with all that. I do think things _have_ been improving over\nthe years, and the code is way less tangled than it used to be. But\nthere are so many corner cases, and the code is so fundamental, that it\nhas been slow going. I'd be happy to review patches if you want to push\nit along.\n\n> > So I think the caller of the config code has to provide some kind of\n> > context about how it is expecting to run and how the value will be used.\n> \n> Yep.\n> \n> Maybe even go a step further and say that the config code needs a context\n> \"object\".\n\nIf I were writing git from scratch, I'd consider making a \"struct\nrepository\" object. I'm not sure how painful it would be to retro-fit it\nat this point.\n\n> > Right now if setup_git_directory() or similar hasn't been called, the\n> > config code does not look.\n> \n> Correct.\n> \n> Actually, half correct. If setup_git_directory() has not been called, but,\n> say, git_path() (and thereby implicitly setup_git_env()), the config code\n> *does* look. At a hard-coded .git/config.\n\nNot since b9605bc4f (config: only read .git/config from configured\nrepos, 2016-09-12). That's why pager.c needs its little hack.\n\nI guess you could see that as a step backwards, but I think it is\nnecessary one on the road to doing it right.\n\n> > Ideally there would be a way for a caller to say \"I am running early and\n> > not even sure yet if we are in a repo; please speculatively try to find\n> > repo config for me\".\n> \n> And ideally, it would not roll *yet another* way to discover the git_dir,\n> but it would reuse the same function (fixing it not to chdir()\n> unilaterally).\n\nYes, absolutely.\n\n> Of course, not using `chdir()` means that we have to figure out symbolic\n> links somehow, in case somebody works from a symlinked subdirectory, e.g.:\n> \n> \tln -s $PWD/t/ ~/test-directory\n> \tcd ~/test-directory\n> \tgit log\n\nThere's work happening elsewhere[1] on making real_path() work without\ncalling chdir(). Which necessarily involves resolving symlinks\nourselves. I wonder if we could leverage that work here (ideally by\nusing real_path() under the hood, and not reimplementing the same\nreadlink() logic ourselves).\n\n[1] http://public-inbox.org/git/1480964316-99305-1-git-send-email-bmwill@google.com/\n\n> > The pager code does this manually, and without great accuracy; see the\n> > hack in pager.c's read_early_config().\n> \n> I saw it. And that is what triggered the mail you are responding to (you\n> probably saw my eye-rolling between the lines).\n> \n> The real question is: can we fix this? Or is there simply too great\n> reluctance to change the current code?\n\nThe code in pager.c is only a month or two old. Like I said, it's ugly,\nbut I think it's a necessary step on the way forward. So I don't think\nthere's reluctance at all. The next steps (which I outlined) just\nhaven't been taken yet.\n\n> > I think the way forward is:\n> > \n> >   1. Make that an optional behavior in git_config_with_options() so\n> >      other spots can reuse it (probably alias lookup, and something like\n> >      your difftool config).\n> \n> Ideally: *any* early call to `git_config_get_value()`. Make things less\n> surprising.\n\nI'm not convinced that's a good idea. The changes in b9605bc4f were\nmotivated by a real bug, which your suggestion would reintroduce (namely\nlow-level code run by git-init ending up with config variables from a\nrepo that _should_ be unrelated).\n\nIn my mental model, the cases are:\n\n  1. We are \"early\" in the process, before we know if we have a repo or\n     not. These early looks should speculatively look at repo config,\n     which is confined to generic things like pager config, alias\n     config, etc.\n\n  2. We are in a repo. Obviously look at $GIT_DIR/config.\n\n  3. We are in a program which has done setup and determined we are\n     _not_ in a repo. Definitely do not look at .git/config or anything\n     else.\n\nMy plan was for the config code to default to (3) when we are not in a\nrepo, but let some lookups ask specifically for (1).\n\nIf you want to default to (1), you need some way for programs to say \"I\nam really case (3); do not look for a repo\". And it needs to be global,\nas the config lookup may be done by much lower-level code. That could be\nby turning startup_info->have_repository into a tri-state. It just\nwasn't the way I was planning on it.\n\n> >   2. Make it more accurate. Right now it blindly looks in .git/config,\n> >      but it should be able to do the usual repo-detection (_without_\n> >      actually entering the repo) to try to find a possible config file.\n> \n> The real trick will be to convince Junio to have a single function for\n> git_dir discovery, I guess, lest we have multiple, slightly incompatible\n> ways to discover it. I expect a lot of resistance here, because we would\n> have to change tried-and-tested (if POLA-violating) code.\n\nPersonally, I haven't seen any resistance from Junio on refactoring this\narea. I'm sure he is concerned that we do not regress, but it's not like\nthe area has been unchanged over the years. It has been slow going\nbecause we want to do it carefully, but I think we are actually at the\npoint now where the next step is making setup_git_directory() more sane.\n\n-Peff\n"},{"id":"307024","messageId":"CAGZ79kZoy2zSgSEc7kfAZ9tg9_uJxa+_FNFVO8UEDLVK6YDxVg@mail.gmail.com","threadId":"44532","inReplyTo":"20161206150955.mvq4ocamaei52bap@sigill.intra.peff.net","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-12-06T18:22:21Z","receivedAt":"2016-12-06T18:22:27Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Tue, Dec 6, 2016 at 7:09 AM, Jeff King <peff@peff.net> wrote:\n\n>>\n>> Maybe even go a step further and say that the config code needs a context\n>> \"object\".\n>\n> If I were writing git from scratch, I'd consider making a \"struct\n> repository\" object. I'm not sure how painful it would be to retro-fit it\n> at this point.\n\nWould it be possible to introduce \"the repo\" struct similar to \"the index\"\nin cache.h?\n\nFrom a submodule perspective I would very much welcome this\nobject oriented approach to repositories.\n"},{"id":"307034","messageId":"20161206183527.t6rjkkff7fxv5i5c@sigill.intra.peff.net","threadId":"44532","inReplyTo":"CAGZ79kZoy2zSgSEc7kfAZ9tg9_uJxa+_FNFVO8UEDLVK6YDxVg@mail.gmail.com","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-12-06T18:35:28Z","receivedAt":"2016-12-06T18:35:33Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Dec 06, 2016 at 10:22:21AM -0800, Stefan Beller wrote:\n\n> >> Maybe even go a step further and say that the config code needs a context\n> >> \"object\".\n> >\n> > If I were writing git from scratch, I'd consider making a \"struct\n> > repository\" object. I'm not sure how painful it would be to retro-fit it\n> > at this point.\n> \n> Would it be possible to introduce \"the repo\" struct similar to \"the index\"\n> in cache.h?\n> \n> From a submodule perspective I would very much welcome this\n> object oriented approach to repositories.\n\nI think it may be more complicated, because there's some implicit global\nstate in \"the repo\", like where files are relative to our cwd. All of\nthose low-level functions would have to start caring about which repo\nwe're talking about so they can prefix the appropriate working tree\npath, etc.\n\nFor some operations that would be fine, but there are things that would\nsubtly fail for submodules. I'm thinking we'd end up with some code\nstate like:\n\n  /* finding a repo does not modify global state; good */\n  struct repository *repo = repo_discover(\".\");\n\n  /* obvious repo-level operations like looking up refs can be done with\n   * a repository object; good */\n  repo_for_each_ref(repo, callback, NULL);\n\n  /*\n   * \"enter\" the repo so that we are at the top-level of the working\n   * tree, etc. After this you can actually look at the index without\n   * things breaking.\n   */\n  repo_enter(repo);\n\nThat would be enough to implement a lot of submodule-level stuff, but it\nwould break pretty subtly as soon as you asked the submodule about its\nworking tree. The solution is to make everything that accesses the\nworking tree aware of the idea of a working tree root besides the cwd.\nBut that's a pretty invasive change.\n\n-Peff\n"},{"id":"308675","messageId":"cover.1483373635.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1480019834.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 0/4] Show Git Mailing List: a builtin difftool","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-01-02T16:16:44Z","receivedAt":"2017-01-02T16:17:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"I have been working on the builtin difftool for a while now, for two\nreasons:\n\n1. Perl is really not native on Windows. Not only is there a performance\n   penalty to be paid just for running Perl scripts, we also have to deal\n   with the fact that users may have different Perl installations, with\n   different options, and some other Perl installation may decide to set\n   PERL5LIB globally, wreaking havoc with Git for Windows' Perl (which we\n   have to use because almost all other Perl distributions lack the\n   Subversion bindings we need for `git svn`).\n\n2. Perl makes for a rather large reason that Git for Windows' installer\n   weighs in with >30MB. While one Perl script less does not relieve us\n   of that burden, it is one step in the right direction.\n\nChanges since v3:\n\n- made path_entry_cmp static\n\n- fixed a few bugs identified by Coverity\n\n- fixed overzealous status parsing that did not expect any number after\n  C or R\n\n\nJohannes Schindelin (4):\n  Avoid Coverity warning about unfree()d git_exec_path()\n  difftool: add a skeleton for the upcoming builtin\n  difftool: implement the functionality in the builtin\n  t7800: run both builtin and scripted difftool, for now\n\n .gitignore                                    |   1 +\n Makefile                                      |   3 +-\n builtin.h                                     |   1 +\n builtin/difftool.c                            | 733 ++++++++++++++++++++++++++\n exec_cmd.c                                    |   5 +-\n git-difftool.perl => git-legacy-difftool.perl |   0\n git.c                                         |   6 +\n t/t7800-difftool.sh                           |  29 +\n 8 files changed, 776 insertions(+), 2 deletions(-)\n create mode 100644 builtin/difftool.c\n rename git-difftool.perl => git-legacy-difftool.perl (100%)\n\n\nbase-commit: e05806da9ec4aff8adfed142ab2a2b3b02e33c8c\nPublished-As: https://github.com/dscho/git/releases/tag/builtin-difftool-v4\nFetch-It-Via: git fetch https://github.com/dscho/git builtin-difftool-v4\n\nInterdiff vs v3:\n\n diff --git a/builtin/difftool.c b/builtin/difftool.c\n index 3480920fea..2115e548a5 100644\n --- a/builtin/difftool.c\n +++ b/builtin/difftool.c\n @@ -73,8 +73,10 @@ static int parse_index_info(char *p, int *mode1, int *mode2,\n  \tif (*p != ' ')\n  \t\treturn error(\"expected ' ', got '%c'\", *p);\n  \t*status = *++p;\n -\tif (!status || p[1])\n -\t\treturn error(\"unexpected trailer: '%s'\", p);\n +\tif (!*status)\n +\t\treturn error(\"missing status\");\n +\tif (p[1] && !isdigit(p[1]))\n +\t\treturn error(\"unexpected trailer: '%s'\", p + 1);\n  \treturn 0;\n  }\n  \n @@ -107,7 +109,8 @@ static int use_wt_file(const char *workdir, const char *name,\n  \t\tstruct object_id wt_oid;\n  \t\tint fd = open(buf.buf, O_RDONLY);\n  \n -\t\tif (!index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {\n +\t\tif (fd >= 0 &&\n +\t\t    !index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {\n  \t\t\tif (is_null_oid(oid)) {\n  \t\t\t\toidcpy(oid, &wt_oid);\n  \t\t\t\tuse = 1;\n @@ -162,7 +165,7 @@ static void add_left_or_right(struct hashmap *map, const char *path,\n  \t\te->left[0] = e->right[0] = '\\0';\n  \t\thashmap_add(map, e);\n  \t}\n -\tstrcpy(is_right ? e->right : e->left, content);\n +\tstrlcpy(is_right ? e->right : e->left, content, PATH_MAX);\n  }\n  \n  struct path_entry {\n @@ -170,7 +173,7 @@ struct path_entry {\n  \tchar path[FLEX_ARRAY];\n  };\n  \n -int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key)\n +static int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key)\n  {\n  \treturn strcmp(a->path, key ? key : b->path);\n  }\n @@ -423,17 +426,16 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,\n  \t\t\t\tstruct cache_entry *ce2 =\n  \t\t\t\t\tmake_cache_entry(rmode, roid.hash,\n  \t\t\t\t\t\t\t dst_path, 0, 0);\n -\t\t\t\tce_mode_from_stat(ce2, rmode);\n  \n  \t\t\t\tadd_index_entry(&wtindex, ce2,\n  \t\t\t\t\t\tADD_CACHE_JUST_APPEND);\n  \n -\t\t\t\tadd_path(&wtdir, wtdir_len, dst_path);\n  \t\t\t\tadd_path(&rdir, rdir_len, dst_path);\n  \t\t\t\tif (ensure_leading_directories(rdir.buf))\n  \t\t\t\t\treturn error(\"could not create \"\n  \t\t\t\t\t\t     \"directory for '%s'\",\n  \t\t\t\t\t\t     dst_path);\n +\t\t\t\tadd_path(&wtdir, wtdir_len, dst_path);\n  \t\t\t\tif (symlinks) {\n  \t\t\t\t\tif (symlink(wtdir.buf, rdir.buf)) {\n  \t\t\t\t\t\tret = error_errno(\"could not symlink '%s' to '%s'\", wtdir.buf, rdir.buf);\n diff --git a/exec_cmd.c b/exec_cmd.c\n index 19ac2146d0..587bd7eb48 100644\n --- a/exec_cmd.c\n +++ b/exec_cmd.c\n @@ -65,6 +65,7 @@ void git_set_argv_exec_path(const char *exec_path)\n  const char *git_exec_path(void)\n  {\n  \tconst char *env;\n +\tstatic char *system_exec_path;\n  \n  \tif (argv_exec_path)\n  \t\treturn argv_exec_path;\n @@ -74,7 +75,9 @@ const char *git_exec_path(void)\n  \t\treturn env;\n  \t}\n  \n -\treturn system_path(GIT_EXEC_PATH);\n +\tif (!system_exec_path)\n +\t\tsystem_exec_path = system_path(GIT_EXEC_PATH);\n +\treturn system_exec_path;\n  }\n  \n  static void add_path(struct strbuf *out, const char *path)\n diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh\n index e94910c563..273ab55723 100755\n --- a/t/t7800-difftool.sh\n +++ b/t/t7800-difftool.sh\n @@ -23,6 +23,20 @@ prompt_given ()\n  \ttest \"$prompt\" = \"Launch 'test-tool' [Y/n]? branch\"\n  }\n  \n +for use_builtin_difftool in false true\n +do\n +\n +test_expect_success 'verify we are running the correct difftool' '\n +\tif test true = '$use_builtin_difftool'\n +\tthen\n +\t\ttest_must_fail ok=129 git difftool -h >help &&\n +\t\tgrep \"g, --gui\" help\n +\telse\n +\t\tgit difftool -h >help &&\n +\t\tgrep \"g|--gui\" help\n +\tfi\n +'\n +\n  # NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.\n  \n  # Create a file on master and change it on branch\n @@ -606,4 +620,17 @@ test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' '\n  \t)\n  '\n  \n +test true != $use_builtin_difftool || break\n +\n +test_expect_success 'tear down for re-run' '\n +\trm -rf * .[a-z]* &&\n +\tgit init\n +'\n +\n +# run as builtin difftool now\n +GIT_CONFIG_PARAMETERS=\"'difftool.usebuiltin=true'\"\n +export GIT_CONFIG_PARAMETERS\n +\n +done\n +\n  test_done\n\n-- \n2.11.0.rc3.windows.1\n\n"},{"id":"308676","messageId":"9b72680e69429a037879fdab6b8fb2501f1d9c81.1483373635.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1483373635.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 2/4] difftool: add a skeleton for the upcoming builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-01-02T16:22:37Z","receivedAt":"2017-01-02T16:22:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This adds a builtin difftool that still falls back to the legacy Perl\nversion, which has been renamed to `legacy-difftool`.\n\nThe idea is that the new, experimental, builtin difftool immediately hands\noff to the legacy difftool for now, unless the config variable\ndifftool.useBuiltin is set to true.\n\nThis feature flag will be used in the upcoming Git for Windows v2.11.0\nrelease, to allow early testers to opt-in to use the builtin difftool and\nflesh out any bugs.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n .gitignore                                    |  1 +\n Makefile                                      |  3 +-\n builtin.h                                     |  1 +\n builtin/difftool.c                            | 63 +++++++++++++++++++++++++++\n git-difftool.perl => git-legacy-difftool.perl |  0\n git.c                                         |  6 +++\n t/t7800-difftool.sh                           |  2 +\n 7 files changed, 75 insertions(+), 1 deletion(-)\n create mode 100644 builtin/difftool.c\n rename git-difftool.perl => git-legacy-difftool.perl (100%)\n\ndiff --git a/.gitignore b/.gitignore\nindex 6722f78f9a..5555ae025b 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -76,6 +76,7 @@\n /git-init-db\n /git-interpret-trailers\n /git-instaweb\n+/git-legacy-difftool\n /git-log\n /git-ls-files\n /git-ls-remote\ndiff --git a/Makefile b/Makefile\nindex d861bd9985..8cf5bef034 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -522,7 +522,7 @@ SCRIPT_LIB += git-sh-setup\n SCRIPT_LIB += git-sh-i18n\n \n SCRIPT_PERL += git-add--interactive.perl\n-SCRIPT_PERL += git-difftool.perl\n+SCRIPT_PERL += git-legacy-difftool.perl\n SCRIPT_PERL += git-archimport.perl\n SCRIPT_PERL += git-cvsexportcommit.perl\n SCRIPT_PERL += git-cvsimport.perl\n@@ -883,6 +883,7 @@ BUILTIN_OBJS += builtin/diff-files.o\n BUILTIN_OBJS += builtin/diff-index.o\n BUILTIN_OBJS += builtin/diff-tree.o\n BUILTIN_OBJS += builtin/diff.o\n+BUILTIN_OBJS += builtin/difftool.o\n BUILTIN_OBJS += builtin/fast-export.o\n BUILTIN_OBJS += builtin/fetch-pack.o\n BUILTIN_OBJS += builtin/fetch.o\ndiff --git a/builtin.h b/builtin.h\nindex b9122bc5f4..67f80519da 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix);\n extern int cmd_diff_index(int argc, const char **argv, const char *prefix);\n extern int cmd_diff(int argc, const char **argv, const char *prefix);\n extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);\n+extern int cmd_difftool(int argc, const char **argv, const char *prefix);\n extern int cmd_fast_export(int argc, const char **argv, const char *prefix);\n extern int cmd_fetch(int argc, const char **argv, const char *prefix);\n extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nnew file mode 100644\nindex 0000000000..53870bbaf7\n--- /dev/null\n+++ b/builtin/difftool.c\n@@ -0,0 +1,63 @@\n+/*\n+ * \"git difftool\" builtin command\n+ *\n+ * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible\n+ * git-difftool--helper script.\n+ *\n+ * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.\n+ * The GIT_DIFF* variables are exported for use by git-difftool--helper.\n+ *\n+ * Any arguments that are unknown to this script are forwarded to 'git diff'.\n+ *\n+ * Copyright (C) 2016 Johannes Schindelin\n+ */\n+#include \"builtin.h\"\n+#include \"run-command.h\"\n+#include \"exec_cmd.h\"\n+\n+/*\n+ * NEEDSWORK: this function can go once the legacy-difftool Perl script is\n+ * retired.\n+ *\n+ * We intentionally avoid reading the config directly here, to avoid messing up\n+ * the GIT_* environment variables when we need to fall back to exec()ing the\n+ * Perl script.\n+ */\n+static int use_builtin_difftool(void) {\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tstruct strbuf out = STRBUF_INIT;\n+\tint ret;\n+\n+\targv_array_pushl(&cp.args,\n+\t\t\t \"config\", \"--bool\", \"difftool.usebuiltin\", NULL);\n+\tcp.git_cmd = 1;\n+\tif (capture_command(&cp, &out, 6))\n+\t\treturn 0;\n+\tstrbuf_trim(&out);\n+\tret = !strcmp(\"true\", out.buf);\n+\tstrbuf_release(&out);\n+\treturn ret;\n+}\n+\n+int cmd_difftool(int argc, const char **argv, const char *prefix)\n+{\n+\t/*\n+\t * NEEDSWORK: Once the builtin difftool has been tested enough\n+\t * and git-legacy-difftool.perl is retired to contrib/, this preamble\n+\t * can be removed.\n+\t */\n+\tif (!use_builtin_difftool()) {\n+\t\tconst char *path = mkpath(\"%s/git-legacy-difftool\",\n+\t\t\t\t\t  git_exec_path());\n+\n+\t\tif (sane_execvp(path, (char **)argv) < 0)\n+\t\t\tdie_errno(\"could not exec %s\", path);\n+\n+\t\treturn 0;\n+\t}\n+\tprefix = setup_git_directory();\n+\ttrace_repo_setup(prefix);\n+\tsetup_work_tree();\n+\n+\tdie(\"TODO\");\n+}\ndiff --git a/git-difftool.perl b/git-legacy-difftool.perl\nsimilarity index 100%\nrename from git-difftool.perl\nrename to git-legacy-difftool.perl\ndiff --git a/git.c b/git.c\nindex dce529fcbf..044958a780 100644\n--- a/git.c\n+++ b/git.c\n@@ -424,6 +424,12 @@ static struct cmd_struct commands[] = {\n \t{ \"diff-files\", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"diff-index\", cmd_diff_index, RUN_SETUP },\n \t{ \"diff-tree\", cmd_diff_tree, RUN_SETUP },\n+\t/*\n+\t * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in\n+\t * builtin/difftool.c has been removed, this entry should be changed to\n+\t * RUN_SETUP | NEED_WORK_TREE\n+\t */\n+\t{ \"difftool\", cmd_difftool },\n \t{ \"fast-export\", cmd_fast_export, RUN_SETUP },\n \t{ \"fetch\", cmd_fetch, RUN_SETUP },\n \t{ \"fetch-pack\", cmd_fetch_pack, RUN_SETUP },\ndiff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh\nindex 99d4123461..e94910c563 100755\n--- a/t/t7800-difftool.sh\n+++ b/t/t7800-difftool.sh\n@@ -23,6 +23,8 @@ prompt_given ()\n \ttest \"$prompt\" = \"Launch 'test-tool' [Y/n]? branch\"\n }\n \n+# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.\n+\n # Create a file on master and change it on branch\n test_expect_success PERL 'setup' '\n \techo master >file &&\n-- \n2.11.0.rc3.windows.1\n\n\n"},{"id":"308677","messageId":"b11b132ffd76d00c68cd22f298a45065e7932666.1483373635.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1483373635.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 3/4] difftool: implement the functionality in the builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-01-02T16:22:41Z","receivedAt":"2017-01-02T16:22:59Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch gives life to the skeleton added in the previous patch.\n\nThe motivation for converting the difftool is that Perl scripts are not at\nall native on Windows, and that `git difftool` therefore is pretty slow on\nthat platform, when there is no good reason for it to be slow.\n\nIn addition, Perl does not really have access to Git's internals. That\nmeans that any script will always have to jump through unnecessary\nhoops.\n\nThe current version of the builtin difftool does not, however, make full\nuse of the internals but instead chooses to spawn a couple of Git\nprocesses, still, to make for an easier conversion. There remains a lot\nof room for improvement, left for a later date.\n\nNote: to play it safe, the original difftool is still called unless the\nconfig setting difftool.useBuiltin is set to true.\n\nThe reason: this new, experimental, builtin difftool will be shipped as\npart of Git for Windows v2.11.0, to allow for easier large-scale\ntesting, but of course as an opt-in feature.\n\nSadly, the speedup is more noticable on Linux than on Windows: a quick\ntest shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s)\n(real/user/sys) in a Linux VM, down from  (6.529s/3.112s/0.644s), while\non Windows, it is (36.064s/2.730s/7.194s), down from\n(47.637s/2.407s/6.863s). The culprit is most likely the overhead\nincurred from *still* having to shell out to mergetool-lib.sh and\ndifftool--helper.sh.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/difftool.c | 672 ++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 671 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex 53870bbaf7..2115e548a5 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -11,9 +11,610 @@\n  *\n  * Copyright (C) 2016 Johannes Schindelin\n  */\n+#include \"cache.h\"\n #include \"builtin.h\"\n #include \"run-command.h\"\n #include \"exec_cmd.h\"\n+#include \"parse-options.h\"\n+#include \"argv-array.h\"\n+#include \"strbuf.h\"\n+#include \"lockfile.h\"\n+#include \"dir.h\"\n+\n+static char *diff_gui_tool;\n+static int trust_exit_code;\n+\n+static const char *const builtin_difftool_usage[] = {\n+\tN_(\"git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]\"),\n+\tNULL\n+};\n+\n+static int difftool_config(const char *var, const char *value, void *cb)\n+{\n+\tif (!strcmp(var, \"diff.guitool\")) {\n+\t\tdiff_gui_tool = xstrdup(value);\n+\t\treturn 0;\n+\t}\n+\n+\tif (!strcmp(var, \"difftool.trustexitcode\")) {\n+\t\ttrust_exit_code = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n+\treturn git_default_config(var, value, cb);\n+}\n+\n+static int print_tool_help(void)\n+{\n+\tconst char *argv[] = { \"mergetool\", \"--tool-help=diff\", NULL };\n+\treturn run_command_v_opt(argv, RUN_GIT_CMD);\n+}\n+\n+static int parse_index_info(char *p, int *mode1, int *mode2,\n+\t\t\t    struct object_id *oid1, struct object_id *oid2,\n+\t\t\t    char *status)\n+{\n+\tif (*p != ':')\n+\t\treturn error(\"expected ':', got '%c'\", *p);\n+\t*mode1 = (int)strtol(p + 1, &p, 8);\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\t*mode2 = (int)strtol(p + 1, &p, 8);\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\tif (get_oid_hex(++p, oid1))\n+\t\treturn error(\"expected object ID, got '%s'\", p + 1);\n+\tp += GIT_SHA1_HEXSZ;\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\tif (get_oid_hex(++p, oid2))\n+\t\treturn error(\"expected object ID, got '%s'\", p + 1);\n+\tp += GIT_SHA1_HEXSZ;\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\t*status = *++p;\n+\tif (!*status)\n+\t\treturn error(\"missing status\");\n+\tif (p[1] && !isdigit(p[1]))\n+\t\treturn error(\"unexpected trailer: '%s'\", p + 1);\n+\treturn 0;\n+}\n+\n+/*\n+ * Remove any trailing slash from $workdir\n+ * before starting to avoid double slashes in symlink targets.\n+ */\n+static void add_path(struct strbuf *buf, size_t base_len, const char *path)\n+{\n+\tstrbuf_setlen(buf, base_len);\n+\tif (buf->len && buf->buf[buf->len - 1] != '/')\n+\t\tstrbuf_addch(buf, '/');\n+\tstrbuf_addstr(buf, path);\n+}\n+\n+/*\n+ * Determine whether we can simply reuse the file in the worktree.\n+ */\n+static int use_wt_file(const char *workdir, const char *name,\n+\t\t       struct object_id *oid)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct stat st;\n+\tint use = 0;\n+\n+\tstrbuf_addstr(&buf, workdir);\n+\tadd_path(&buf, buf.len, name);\n+\n+\tif (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) {\n+\t\tstruct object_id wt_oid;\n+\t\tint fd = open(buf.buf, O_RDONLY);\n+\n+\t\tif (fd >= 0 &&\n+\t\t    !index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {\n+\t\t\tif (is_null_oid(oid)) {\n+\t\t\t\toidcpy(oid, &wt_oid);\n+\t\t\t\tuse = 1;\n+\t\t\t} else if (!oidcmp(oid, &wt_oid))\n+\t\t\t\tuse = 1;\n+\t\t}\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn use;\n+}\n+\n+struct working_tree_entry {\n+\tstruct hashmap_entry entry;\n+\tchar path[FLEX_ARRAY];\n+};\n+\n+static int working_tree_entry_cmp(struct working_tree_entry *a,\n+\t\t\t\t  struct working_tree_entry *b, void *keydata)\n+{\n+\treturn strcmp(a->path, b->path);\n+}\n+\n+/*\n+ * The `left` and `right` entries hold paths for the symlinks hashmap,\n+ * and a SHA-1 surrounded by brief text for submodules.\n+ */\n+struct pair_entry {\n+\tstruct hashmap_entry entry;\n+\tchar left[PATH_MAX], right[PATH_MAX];\n+\tconst char path[FLEX_ARRAY];\n+};\n+\n+static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata)\n+{\n+\treturn strcmp(a->path, b->path);\n+}\n+\n+static void add_left_or_right(struct hashmap *map, const char *path,\n+\t\t\t      const char *content, int is_right)\n+{\n+\tstruct pair_entry *e, *existing;\n+\n+\tFLEX_ALLOC_STR(e, path, path);\n+\thashmap_entry_init(e, strhash(path));\n+\texisting = hashmap_get(map, e, NULL);\n+\tif (existing) {\n+\t\tfree(e);\n+\t\te = existing;\n+\t} else {\n+\t\te->left[0] = e->right[0] = '\\0';\n+\t\thashmap_add(map, e);\n+\t}\n+\tstrlcpy(is_right ? e->right : e->left, content, PATH_MAX);\n+}\n+\n+struct path_entry {\n+\tstruct hashmap_entry entry;\n+\tchar path[FLEX_ARRAY];\n+};\n+\n+static int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key)\n+{\n+\treturn strcmp(a->path, key ? key : b->path);\n+}\n+\n+static void changed_files(struct hashmap *result, const char *index_path,\n+\t\t\t  const char *workdir)\n+{\n+\tstruct child_process update_index = CHILD_PROCESS_INIT;\n+\tstruct child_process diff_files = CHILD_PROCESS_INIT;\n+\tstruct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT;\n+\tconst char *git_dir = absolute_path(get_git_dir()), *env[] = {\n+\t\tNULL, NULL\n+\t};\n+\tFILE *fp;\n+\n+\tstrbuf_addf(&index_env, \"GIT_INDEX_FILE=%s\", index_path);\n+\tenv[0] = index_env.buf;\n+\n+\targv_array_pushl(&update_index.args,\n+\t\t\t \"--git-dir\", git_dir, \"--work-tree\", workdir,\n+\t\t\t \"update-index\", \"--really-refresh\", \"-q\",\n+\t\t\t \"--unmerged\", NULL);\n+\tupdate_index.no_stdin = 1;\n+\tupdate_index.no_stdout = 1;\n+\tupdate_index.no_stderr = 1;\n+\tupdate_index.git_cmd = 1;\n+\tupdate_index.use_shell = 0;\n+\tupdate_index.clean_on_exit = 1;\n+\tupdate_index.dir = workdir;\n+\tupdate_index.env = env;\n+\t/* Ignore any errors of update-index */\n+\trun_command(&update_index);\n+\n+\targv_array_pushl(&diff_files.args,\n+\t\t\t \"--git-dir\", git_dir, \"--work-tree\", workdir,\n+\t\t\t \"diff-files\", \"--name-only\", \"-z\", NULL);\n+\tdiff_files.no_stdin = 1;\n+\tdiff_files.git_cmd = 1;\n+\tdiff_files.use_shell = 0;\n+\tdiff_files.clean_on_exit = 1;\n+\tdiff_files.out = -1;\n+\tdiff_files.dir = workdir;\n+\tdiff_files.env = env;\n+\tif (start_command(&diff_files))\n+\t\tdie(\"could not obtain raw diff\");\n+\tfp = xfdopen(diff_files.out, \"r\");\n+\twhile (!strbuf_getline_nul(&buf, fp)) {\n+\t\tstruct path_entry *entry;\n+\t\tFLEX_ALLOC_STR(entry, path, buf.buf);\n+\t\thashmap_entry_init(entry, strhash(buf.buf));\n+\t\thashmap_add(result, entry);\n+\t}\n+\tif (finish_command(&diff_files))\n+\t\tdie(\"diff-files did not exit properly\");\n+\tstrbuf_release(&index_env);\n+\tstrbuf_release(&buf);\n+}\n+\n+static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstrbuf_addstr(&buf, tmpdir);\n+\tremove_dir_recursively(&buf, 0);\n+\tif (exit_code)\n+\t\twarning(_(\"failed: %d\"), exit_code);\n+\texit(exit_code);\n+}\n+\n+static int ensure_leading_directories(char *path)\n+{\n+\tswitch (safe_create_leading_directories(path)) {\n+\t\tcase SCLD_OK:\n+\t\tcase SCLD_EXISTS:\n+\t\t\treturn 0;\n+\t\tdefault:\n+\t\t\treturn error(_(\"could not create leading directories \"\n+\t\t\t\t       \"of '%s'\"), path);\n+\t}\n+}\n+\n+static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,\n+\t\t\tint argc, const char **argv)\n+{\n+\tchar tmpdir[PATH_MAX];\n+\tstruct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;\n+\tstruct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;\n+\tstruct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;\n+\tstruct strbuf wtdir = STRBUF_INIT;\n+\tsize_t ldir_len, rdir_len, wtdir_len;\n+\tstruct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1);\n+\tconst char *workdir, *tmp;\n+\tint ret = 0, i;\n+\tFILE *fp;\n+\tstruct hashmap working_tree_dups, submodules, symlinks2;\n+\tstruct hashmap_iter iter;\n+\tstruct pair_entry *entry;\n+\tenum object_type type;\n+\tunsigned long size;\n+\tstruct index_state wtindex;\n+\tstruct checkout lstate, rstate;\n+\tint rc, flags = RUN_GIT_CMD, err = 0;\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\tconst char *helper_argv[] = { \"difftool--helper\", NULL, NULL, NULL };\n+\tstruct hashmap wt_modified, tmp_modified;\n+\tint indices_loaded = 0;\n+\n+\tworkdir = get_git_work_tree();\n+\n+\t/* Setup temp directories */\n+\ttmp = getenv(\"TMPDIR\");\n+\txsnprintf(tmpdir, sizeof(tmpdir), \"%s/git-difftool.XXXXXX\", tmp ? tmp : \"/tmp\");\n+\tif (!mkdtemp(tmpdir))\n+\t\treturn error(\"could not create '%s'\", tmpdir);\n+\tstrbuf_addf(&ldir, \"%s/left/\", tmpdir);\n+\tstrbuf_addf(&rdir, \"%s/right/\", tmpdir);\n+\tstrbuf_addstr(&wtdir, workdir);\n+\tif (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))\n+\t\tstrbuf_addch(&wtdir, '/');\n+\tmkdir(ldir.buf, 0700);\n+\tmkdir(rdir.buf, 0700);\n+\n+\tmemset(&wtindex, 0, sizeof(wtindex));\n+\n+\tmemset(&lstate, 0, sizeof(lstate));\n+\tlstate.base_dir = ldir.buf;\n+\tlstate.base_dir_len = ldir.len;\n+\tlstate.force = 1;\n+\tmemset(&rstate, 0, sizeof(rstate));\n+\trstate.base_dir = rdir.buf;\n+\trstate.base_dir_len = rdir.len;\n+\trstate.force = 1;\n+\n+\tldir_len = ldir.len;\n+\trdir_len = rdir.len;\n+\twtdir_len = wtdir.len;\n+\n+\thashmap_init(&working_tree_dups,\n+\t\t     (hashmap_cmp_fn)working_tree_entry_cmp, 0);\n+\thashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0);\n+\thashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0);\n+\n+\tchild.no_stdin = 1;\n+\tchild.git_cmd = 1;\n+\tchild.use_shell = 0;\n+\tchild.clean_on_exit = 1;\n+\tchild.dir = prefix;\n+\tchild.out = -1;\n+\targv_array_pushl(&child.args, \"diff\", \"--raw\", \"--no-abbrev\", \"-z\",\n+\t\t\t NULL);\n+\tfor (i = 0; i < argc; i++)\n+\t\targv_array_push(&child.args, argv[i]);\n+\tif (start_command(&child))\n+\t\tdie(\"could not obtain raw diff\");\n+\tfp = xfdopen(child.out, \"r\");\n+\n+\t/* Build index info for left and right sides of the diff */\n+\ti = 0;\n+\twhile (!strbuf_getline_nul(&info, fp)) {\n+\t\tint lmode, rmode;\n+\t\tstruct object_id loid, roid;\n+\t\tchar status;\n+\t\tconst char *src_path, *dst_path;\n+\t\tsize_t src_path_len, dst_path_len;\n+\n+\t\tif (starts_with(info.buf, \"::\"))\n+\t\t\tdie(N_(\"combined diff formats('-c' and '--cc') are \"\n+\t\t\t       \"not supported in\\n\"\n+\t\t\t       \"directory diff mode('-d' and '--dir-diff').\"));\n+\n+\t\tif (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,\n+\t\t\t\t     &status))\n+\t\t\tbreak;\n+\t\tif (strbuf_getline_nul(&lpath, fp))\n+\t\t\tbreak;\n+\t\tsrc_path = lpath.buf;\n+\t\tsrc_path_len = lpath.len;\n+\n+\t\ti++;\n+\t\tif (status != 'C' && status != 'R') {\n+\t\t\tdst_path = src_path;\n+\t\t\tdst_path_len = src_path_len;\n+\t\t} else {\n+\t\t\tif (strbuf_getline_nul(&rpath, fp))\n+\t\t\t\tbreak;\n+\t\t\tdst_path = rpath.buf;\n+\t\t\tdst_path_len = rpath.len;\n+\t\t}\n+\n+\t\tif (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) {\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"Subproject commit %s\",\n+\t\t\t\t    oid_to_hex(&loid));\n+\t\t\tadd_left_or_right(&submodules, src_path, buf.buf, 0);\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"Subproject commit %s\",\n+\t\t\t\t    oid_to_hex(&roid));\n+\t\t\tif (!oidcmp(&loid, &roid))\n+\t\t\t\tstrbuf_addstr(&buf, \"-dirty\");\n+\t\t\tadd_left_or_right(&submodules, dst_path, buf.buf, 1);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (S_ISLNK(lmode)) {\n+\t\t\tchar *content = read_sha1_file(loid.hash, &type, &size);\n+\t\t\tadd_left_or_right(&symlinks2, src_path, content, 0);\n+\t\t\tfree(content);\n+\t\t}\n+\n+\t\tif (S_ISLNK(rmode)) {\n+\t\t\tchar *content = read_sha1_file(roid.hash, &type, &size);\n+\t\t\tadd_left_or_right(&symlinks2, dst_path, content, 1);\n+\t\t\tfree(content);\n+\t\t}\n+\n+\t\tif (lmode && status != 'C') {\n+\t\t\tce->ce_mode = lmode;\n+\t\t\toidcpy(&ce->oid, &loid);\n+\t\t\tstrcpy(ce->name, src_path);\n+\t\t\tce->ce_namelen = src_path_len;\n+\t\t\tif (checkout_entry(ce, &lstate, NULL))\n+\t\t\t\treturn error(\"could not write '%s'\", src_path);\n+\t\t}\n+\n+\t\tif (rmode) {\n+\t\t\tstruct working_tree_entry *entry;\n+\n+\t\t\t/* Avoid duplicate working_tree entries */\n+\t\t\tFLEX_ALLOC_STR(entry, path, dst_path);\n+\t\t\thashmap_entry_init(entry, strhash(dst_path));\n+\t\t\tif (hashmap_get(&working_tree_dups, entry, NULL)) {\n+\t\t\t\tfree(entry);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\thashmap_add(&working_tree_dups, entry);\n+\n+\t\t\tif (!use_wt_file(workdir, dst_path, &roid)) {\n+\t\t\t\tce->ce_mode = rmode;\n+\t\t\t\toidcpy(&ce->oid, &roid);\n+\t\t\t\tstrcpy(ce->name, dst_path);\n+\t\t\t\tce->ce_namelen = dst_path_len;\n+\t\t\t\tif (checkout_entry(ce, &rstate, NULL))\n+\t\t\t\t\treturn error(\"could not write '%s'\",\n+\t\t\t\t\t\t     dst_path);\n+\t\t\t} else if (!is_null_oid(&roid)) {\n+\t\t\t\t/*\n+\t\t\t\t * Changes in the working tree need special\n+\t\t\t\t * treatment since they are not part of the\n+\t\t\t\t * index.\n+\t\t\t\t */\n+\t\t\t\tstruct cache_entry *ce2 =\n+\t\t\t\t\tmake_cache_entry(rmode, roid.hash,\n+\t\t\t\t\t\t\t dst_path, 0, 0);\n+\n+\t\t\t\tadd_index_entry(&wtindex, ce2,\n+\t\t\t\t\t\tADD_CACHE_JUST_APPEND);\n+\n+\t\t\t\tadd_path(&rdir, rdir_len, dst_path);\n+\t\t\t\tif (ensure_leading_directories(rdir.buf))\n+\t\t\t\t\treturn error(\"could not create \"\n+\t\t\t\t\t\t     \"directory for '%s'\",\n+\t\t\t\t\t\t     dst_path);\n+\t\t\t\tadd_path(&wtdir, wtdir_len, dst_path);\n+\t\t\t\tif (symlinks) {\n+\t\t\t\t\tif (symlink(wtdir.buf, rdir.buf)) {\n+\t\t\t\t\t\tret = error_errno(\"could not symlink '%s' to '%s'\", wtdir.buf, rdir.buf);\n+\t\t\t\t\t\tgoto finish;\n+\t\t\t\t\t}\n+\t\t\t\t} else {\n+\t\t\t\t\tstruct stat st;\n+\t\t\t\t\tif (stat(wtdir.buf, &st))\n+\t\t\t\t\t\tst.st_mode = 0644;\n+\t\t\t\t\tif (copy_file(rdir.buf, wtdir.buf,\n+\t\t\t\t\t\t      st.st_mode)) {\n+\t\t\t\t\t\tret = error(\"could not copy '%s' to '%s'\", wtdir.buf, rdir.buf);\n+\t\t\t\t\t\tgoto finish;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (finish_command(&child)) {\n+\t\tret = error(\"error occurred running diff --raw\");\n+\t\tgoto finish;\n+\t}\n+\n+\tif (!i)\n+\t\treturn 0;\n+\n+\t/*\n+\t * Changes to submodules require special treatment.This loop writes a\n+\t * temporary file to both the left and right directories to show the\n+\t * change in the recorded SHA1 for the submodule.\n+\t */\n+\thashmap_iter_init(&submodules, &iter);\n+\twhile ((entry = hashmap_iter_next(&iter))) {\n+\t\tif (*entry->left) {\n+\t\t\tadd_path(&ldir, ldir_len, entry->path);\n+\t\t\tensure_leading_directories(ldir.buf);\n+\t\t\twrite_file(ldir.buf, \"%s\", entry->left);\n+\t\t}\n+\t\tif (*entry->right) {\n+\t\t\tadd_path(&rdir, rdir_len, entry->path);\n+\t\t\tensure_leading_directories(rdir.buf);\n+\t\t\twrite_file(rdir.buf, \"%s\", entry->right);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Symbolic links require special treatment.The standard \"git diff\"\n+\t * shows only the link itself, not the contents of the link target.\n+\t * This loop replicates that behavior.\n+\t */\n+\thashmap_iter_init(&symlinks2, &iter);\n+\twhile ((entry = hashmap_iter_next(&iter))) {\n+\t\tif (*entry->left) {\n+\t\t\tadd_path(&ldir, ldir_len, entry->path);\n+\t\t\tensure_leading_directories(ldir.buf);\n+\t\t\twrite_file(ldir.buf, \"%s\", entry->left);\n+\t\t}\n+\t\tif (*entry->right) {\n+\t\t\tadd_path(&rdir, rdir_len, entry->path);\n+\t\t\tensure_leading_directories(rdir.buf);\n+\t\t\twrite_file(rdir.buf, \"%s\", entry->right);\n+\t\t}\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\tstrbuf_setlen(&ldir, ldir_len);\n+\thelper_argv[1] = ldir.buf;\n+\tstrbuf_setlen(&rdir, rdir_len);\n+\thelper_argv[2] = rdir.buf;\n+\n+\tif (extcmd) {\n+\t\thelper_argv[0] = extcmd;\n+\t\tflags = 0;\n+\t} else\n+\t\tsetenv(\"GIT_DIFFTOOL_DIRDIFF\", \"true\", 1);\n+\trc = run_command_v_opt(helper_argv, flags);\n+\n+\t/*\n+\t * If the diff includes working copy files and those\n+\t * files were modified during the diff, then the changes\n+\t * should be copied back to the working tree.\n+\t * Do not copy back files when symlinks are used and the\n+\t * external tool did not replace the original link with a file.\n+\t *\n+\t * These hashes are loaded lazily since they aren't needed\n+\t * in the common case of --symlinks and the difftool updating\n+\t * files through the symlink.\n+\t */\n+\thashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp,\n+\t\t     wtindex.cache_nr);\n+\thashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp,\n+\t\t     wtindex.cache_nr);\n+\n+\tfor (i = 0; i < wtindex.cache_nr; i++) {\n+\t\tstruct hashmap_entry dummy;\n+\t\tconst char *name = wtindex.cache[i]->name;\n+\t\tstruct stat st;\n+\n+\t\tadd_path(&rdir, rdir_len, name);\n+\t\tif (lstat(rdir.buf, &st))\n+\t\t\tcontinue;\n+\n+\t\tif ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode))\n+\t\t\tcontinue;\n+\n+\t\tif (!indices_loaded) {\n+\t\t\tstatic struct lock_file lock;\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"%s/wtindex\", tmpdir);\n+\t\t\tif (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||\n+\t\t\t    write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {\n+\t\t\t\tret = error(\"could not write %s\", buf.buf);\n+\t\t\t\trollback_lock_file(&lock);\n+\t\t\t\tgoto finish;\n+\t\t\t}\n+\t\t\tchanged_files(&wt_modified, buf.buf, workdir);\n+\t\t\tstrbuf_setlen(&rdir, rdir_len);\n+\t\t\tchanged_files(&tmp_modified, buf.buf, rdir.buf);\n+\t\t\tadd_path(&rdir, rdir_len, name);\n+\t\t\tindices_loaded = 1;\n+\t\t}\n+\n+\t\thashmap_entry_init(&dummy, strhash(name));\n+\t\tif (hashmap_get(&tmp_modified, &dummy, name)) {\n+\t\t\tadd_path(&wtdir, wtdir_len, name);\n+\t\t\tif (hashmap_get(&wt_modified, &dummy, name)) {\n+\t\t\t\twarning(_(\"both files modified: '%s' and '%s'.\"),\n+\t\t\t\t\twtdir.buf, rdir.buf);\n+\t\t\t\twarning(_(\"working tree file has been left.\"));\n+\t\t\t\twarning(\"\");\n+\t\t\t\terr = 1;\n+\t\t\t} else if (unlink(wtdir.buf) ||\n+\t\t\t\t   copy_file(wtdir.buf, rdir.buf, st.st_mode))\n+\t\t\t\twarning_errno(_(\"could not copy '%s' to '%s'\"),\n+\t\t\t\t\t      rdir.buf, wtdir.buf);\n+\t\t}\n+\t}\n+\n+\tif (err) {\n+\t\twarning(_(\"temporary files exist in '%s'.\"), tmpdir);\n+\t\twarning(_(\"you may want to cleanup or recover these.\"));\n+\t\texit(1);\n+\t} else\n+\t\texit_cleanup(tmpdir, rc);\n+\n+finish:\n+\tfree(ce);\n+\tstrbuf_release(&ldir);\n+\tstrbuf_release(&rdir);\n+\tstrbuf_release(&wtdir);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+static int run_file_diff(int prompt, const char *prefix,\n+\t\t\t int argc, const char **argv)\n+{\n+\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\tconst char *env[] = {\n+\t\t\"GIT_PAGER=\", \"GIT_EXTERNAL_DIFF=git-difftool--helper\", NULL,\n+\t\tNULL\n+\t};\n+\tint ret = 0, i;\n+\n+\tif (prompt > 0)\n+\t\tenv[2] = \"GIT_DIFFTOOL_PROMPT=true\";\n+\telse if (!prompt)\n+\t\tenv[2] = \"GIT_DIFFTOOL_NO_PROMPT=true\";\n+\n+\n+\targv_array_push(&args, \"diff\");\n+\tfor (i = 0; i < argc; i++)\n+\t\targv_array_push(&args, argv[i]);\n+\tret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env);\n+\texit(ret);\n+}\n \n /*\n  * NEEDSWORK: this function can go once the legacy-difftool Perl script is\n@@ -41,6 +642,35 @@ static int use_builtin_difftool(void) {\n \n int cmd_difftool(int argc, const char **argv, const char *prefix)\n {\n+\tint use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,\n+\t    tool_help = 0;\n+\tstatic char *difftool_cmd = NULL, *extcmd = NULL;\n+\tstruct option builtin_difftool_options[] = {\n+\t\tOPT_BOOL('g', \"gui\", &use_gui_tool,\n+\t\t\t N_(\"use `diff.guitool` instead of `diff.tool`\")),\n+\t\tOPT_BOOL('d', \"dir-diff\", &dir_diff,\n+\t\t\t N_(\"perform a full-directory diff\")),\n+\t\t{ OPTION_SET_INT, 'y', \"no-prompt\", &prompt, NULL,\n+\t\t\tN_(\"do not prompt before launching a diff tool\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},\n+\t\t{ OPTION_SET_INT, 0, \"prompt\", &prompt, NULL, NULL,\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n+\t\t\tNULL, 1 },\n+\t\tOPT_BOOL(0, \"symlinks\", &symlinks,\n+\t\t\t N_(\"use symlinks in dir-diff mode\")),\n+\t\tOPT_STRING('t', \"tool\", &difftool_cmd, N_(\"<tool>\"),\n+\t\t\t   N_(\"use the specified diff tool\")),\n+\t\tOPT_BOOL(0, \"tool-help\", &tool_help,\n+\t\t\t N_(\"print a list of diff tools that may be used with \"\n+\t\t\t    \"`--tool`\")),\n+\t\tOPT_BOOL(0, \"trust-exit-code\", &trust_exit_code,\n+\t\t\t N_(\"make 'git-difftool' exit when an invoked diff \"\n+\t\t\t    \"tool returns a non - zero exit code\")),\n+\t\tOPT_STRING('x', \"extcmd\", &extcmd, N_(\"<command>\"),\n+\t\t\t   N_(\"specify a custom command for viewing diffs\")),\n+\t\tOPT_END()\n+\t};\n+\n \t/*\n \t * NEEDSWORK: Once the builtin difftool has been tested enough\n \t * and git-legacy-difftool.perl is retired to contrib/, this preamble\n@@ -58,6 +688,46 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)\n \tprefix = setup_git_directory();\n \ttrace_repo_setup(prefix);\n \tsetup_work_tree();\n+\t/* NEEDSWORK: once we no longer spawn anything, remove this */\n+\tsetenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);\n+\tsetenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);\n+\n+\tgit_config(difftool_config, NULL);\n+\tsymlinks = has_symlinks;\n+\n+\targc = parse_options(argc, argv, prefix, builtin_difftool_options,\n+\t\t\t     builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n \n-\tdie(\"TODO\");\n+\tif (tool_help)\n+\t\treturn print_tool_help();\n+\n+\tif (use_gui_tool && diff_gui_tool && *diff_gui_tool)\n+\t\tsetenv(\"GIT_DIFF_TOOL\", diff_gui_tool, 1);\n+\telse if (difftool_cmd) {\n+\t\tif (*difftool_cmd)\n+\t\t\tsetenv(\"GIT_DIFF_TOOL\", difftool_cmd, 1);\n+\t\telse\n+\t\t\tdie(_(\"no <tool> given for --tool=<tool>\"));\n+\t}\n+\n+\tif (extcmd) {\n+\t\tif (*extcmd)\n+\t\t\tsetenv(\"GIT_DIFFTOOL_EXTCMD\", extcmd, 1);\n+\t\telse\n+\t\t\tdie(_(\"no <cmd> given for --extcmd=<cmd>\"));\n+\t}\n+\n+\tsetenv(\"GIT_DIFFTOOL_TRUST_EXIT_CODE\",\n+\t       trust_exit_code ? \"true\" : \"false\", 1);\n+\n+\t/*\n+\t * In directory diff mode, 'git-difftool--helper' is called once\n+\t * to compare the a / b directories. In file diff mode, 'git diff'\n+\t * will invoke a separate instance of 'git-difftool--helper' for\n+\t * each file that changed.\n+\t */\n+\tif (dir_diff)\n+\t\treturn run_dir_diff(extcmd, symlinks, prefix, argc, argv);\n+\treturn run_file_diff(prompt, prefix, argc, argv);\n }\n-- \n2.11.0.rc3.windows.1\n\n\n"},{"id":"308678","messageId":"18e9a1009aac2329cb9bf9d12fbac4e8ac19a5bb.1483373635.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1483373635.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-01-02T16:22:33Z","receivedAt":"2017-01-02T16:23:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Technically, it is correct that git_exec_path() returns a possibly\nmalloc()ed string. Practically, it is *sometimes* not malloc()ed. So\nlet's just use a static variable to make it a singleton. That'll shut\nCoverity up, hopefully.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n exec_cmd.c | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/exec_cmd.c b/exec_cmd.c\nindex 19ac2146d0..587bd7eb48 100644\n--- a/exec_cmd.c\n+++ b/exec_cmd.c\n@@ -65,6 +65,7 @@ void git_set_argv_exec_path(const char *exec_path)\n const char *git_exec_path(void)\n {\n \tconst char *env;\n+\tstatic char *system_exec_path;\n \n \tif (argv_exec_path)\n \t\treturn argv_exec_path;\n@@ -74,7 +75,9 @@ const char *git_exec_path(void)\n \t\treturn env;\n \t}\n \n-\treturn system_path(GIT_EXEC_PATH);\n+\tif (!system_exec_path)\n+\t\tsystem_exec_path = system_path(GIT_EXEC_PATH);\n+\treturn system_exec_path;\n }\n \n static void add_path(struct strbuf *out, const char *path)\n-- \n2.11.0.rc3.windows.1\n\n\n"},{"id":"308679","messageId":"0ae4a950a4cd2c8c4f05a6b46c60f127611580cf.1483373635.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1483373635.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-01-02T16:24:20Z","receivedAt":"2017-01-02T16:24:32Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is uglier than a simple\n\n\ttouch \"$GIT_EXEC_PATH/use-builtin-difftool\"\n\nof course. But oh well.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n\n\tThis patch implements the good ole' cross-validation technique\n\t(also known as \"GitHub Scientist\") that I already used for my\n\trebase--helper work.\n\n\tI am not sure whether we want to have that patch in `master`,\n\tever. But at least for the transition time, it may make sense.\n\n t/t7800-difftool.sh | 27 +++++++++++++++++++++++++++\n 1 file changed, 27 insertions(+)\n\ndiff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh\nindex e94910c563..273ab55723 100755\n--- a/t/t7800-difftool.sh\n+++ b/t/t7800-difftool.sh\n@@ -23,6 +23,20 @@ prompt_given ()\n \ttest \"$prompt\" = \"Launch 'test-tool' [Y/n]? branch\"\n }\n \n+for use_builtin_difftool in false true\n+do\n+\n+test_expect_success 'verify we are running the correct difftool' '\n+\tif test true = '$use_builtin_difftool'\n+\tthen\n+\t\ttest_must_fail ok=129 git difftool -h >help &&\n+\t\tgrep \"g, --gui\" help\n+\telse\n+\t\tgit difftool -h >help &&\n+\t\tgrep \"g|--gui\" help\n+\tfi\n+'\n+\n # NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.\n \n # Create a file on master and change it on branch\n@@ -606,4 +620,17 @@ test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' '\n \t)\n '\n \n+test true != $use_builtin_difftool || break\n+\n+test_expect_success 'tear down for re-run' '\n+\trm -rf * .[a-z]* &&\n+\tgit init\n+'\n+\n+# run as builtin difftool now\n+GIT_CONFIG_PARAMETERS=\"'difftool.usebuiltin=true'\"\n+export GIT_CONFIG_PARAMETERS\n+\n+done\n+\n test_done\n-- \n2.11.0.rc3.windows.1\n"},{"id":"308744","messageId":"CAGZ79kZ--jp08pK+xwn1N2VQQr8bA5+DveE2HsoY90R1gR6c_A@mail.gmail.com","threadId":"44532","inReplyTo":"18e9a1009aac2329cb9bf9d12fbac4e8ac19a5bb.1483373635.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path()","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2017-01-03T20:11:25Z","receivedAt":"2017-01-03T20:11:30Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Jan 2, 2017 at 8:22 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> Technically, it is correct that git_exec_path() returns a possibly\n> malloc()ed string. Practically, it is *sometimes* not malloc()ed. So\n> let's just use a static variable to make it a singleton. That'll shut\n> Coverity up, hopefully.\n\nI picked up this patch and applied it to the coverity branch\nthat I maintain at github/stefanbeller/git.\n\nI'd love to see this patch upstream as it reduces my maintenance\nburden of the coverity branch by a patch.\n\nEarly on when Git was new to coverity, some arguments were made\nthat patches like these only clutter the main code base which is read\nby a lot of people, hence we want these quirks for coverity not upstream.\nAnd I think that still holds.\n\nIf this patch is only to appease coverity (as the commit message eludes\nto) I think this may be a bad idea for upstream.  If this patch fixes an\nactual problem, then the commit message needs to spell that out.\n\nThanks,\nStefan\n"},{"id":"308753","messageId":"20170104011343.oq374uffgiltqzeo@sigill.intra.peff.net","threadId":"44532","inReplyTo":"CAGZ79kZ--jp08pK+xwn1N2VQQr8bA5+DveE2HsoY90R1gR6c_A@mail.gmail.com","subject":"Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-01-04T01:13:43Z","receivedAt":"2017-01-04T01:13:53Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jan 03, 2017 at 12:11:25PM -0800, Stefan Beller wrote:\n\n> On Mon, Jan 2, 2017 at 8:22 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > Technically, it is correct that git_exec_path() returns a possibly\n> > malloc()ed string. Practically, it is *sometimes* not malloc()ed. So\n> > let's just use a static variable to make it a singleton. That'll shut\n> > Coverity up, hopefully.\n> \n> I picked up this patch and applied it to the coverity branch\n> that I maintain at github/stefanbeller/git.\n> \n> I'd love to see this patch upstream as it reduces my maintenance\n> burden of the coverity branch by a patch.\n\nThere is another lurking issue in that function, which is that the\nreturn value of getenv() is not guaranteed to last beyond more calls to\ngetenv() or setenv(). It should probably xstrdup() that result, too.\n\nAt that point 2 out of 3 of the return cases would be malloc'd strings,\nso we _could_ switch the third and say \"caller must free the result\".\nBut I think I prefer something like Dscho's solution (more on that\nbelow).\n\n> Early on when Git was new to coverity, some arguments were made\n> that patches like these only clutter the main code base which is read\n> by a lot of people, hence we want these quirks for coverity not upstream.\n> And I think that still holds.\n> \n> If this patch is only to appease coverity (as the commit message eludes\n> to) I think this may be a bad idea for upstream.  If this patch fixes an\n> actual problem, then the commit message needs to spell that out.\n\nThis is a real leak, though in all cases the program typically exits\nsoon afterwards. But we leak from list_commands(), for example, and it\nis not immediately obvious that this is only called right before\nexiting.\n\nBut I think more important is that caching the result in a static\nvariable communicates something (both to Coverity and to a reader of the\ncode). This is a value we expect to live through the life of the\nprogram, and it is OK for it to \"leak\" when it goes out of scope by the\nprogram exiting.\n\nSo even though the behavior does not really change, the annotation has\nvalue.\n\n-Peff\n"},{"id":"308768","messageId":"alpine.DEB.2.20.1701032231400.3469@virtualbox","threadId":"44532","inReplyTo":"CAGZ79kZ--jp08pK+xwn1N2VQQr8bA5+DveE2HsoY90R1gR6c_A@mail.gmail.com","subject":"Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-01-03T21:33:46Z","receivedAt":"2017-01-04T09:50:08Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Tue, 3 Jan 2017, Stefan Beller wrote:\n\n> On Mon, Jan 2, 2017 at 8:22 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > Technically, it is correct that git_exec_path() returns a possibly\n> > malloc()ed string. Practically, it is *sometimes* not malloc()ed. So\n> > let's just use a static variable to make it a singleton. That'll shut\n> > Coverity up, hopefully.\n> \n> I picked up this patch and applied it to the coverity branch\n> that I maintain at github/stefanbeller/git.\n> \n> I'd love to see this patch upstream as it reduces my maintenance\n> burden of the coverity branch by a patch.\n> \n> Early on when Git was new to coverity, some arguments were made\n> that patches like these only clutter the main code base which is read\n> by a lot of people, hence we want these quirks for coverity not upstream.\n> And I think that still holds.\n> \n> If this patch is only to appease coverity (as the commit message eludes\n> to) I think this may be a bad idea for upstream.  If this patch fixes an\n> actual problem, then the commit message needs to spell that out.\n\nThis patch was originally only to appease Coverity, but it actually *does*\nplug a very real memory leak: previously, *every* call to git_exec_path()\n*possibly* returned a newly-malloc()ed buffer. Now, the first call will\nstore that pointer in a static variable and reuse it later.\n\nCould you maybe help me with improving the commit message?\n\nCiao,\nDscho\n"},{"id":"308794","messageId":"CAGZ79kbNYZpYh9fMDKEhMiO5L8SbbtSC8f79RAa-wsOUxE8LDA@mail.gmail.com","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1701032231400.3469@virtualbox","subject":"Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path()","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2017-01-04T18:09:10Z","receivedAt":"2017-01-04T18:16:03Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Tue, Jan 3, 2017 at 1:33 PM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> This patch was originally only to appease Coverity, but it actually *does*\n> plug a very real memory leak: previously, *every* call to git_exec_path()\n> *possibly* returned a newly-malloc()ed buffer. Now, the first call will\n> store that pointer in a static variable and reuse it later.\n>\n> Could you maybe help me with improving the commit message?\n\nAs someone not familiar with that area of code, this explained it\nenough for me to understand, so maybe:\n\n    exec_cmd: do not leak via git_exec_path\n\n    Every call to git_exec_path() possibly returned a newly-malloc()ed\n    buffer. Now, the first call will allocate the buffer and subsequent\n    calls return a pointer to it, which then prevents leaking memory\n    on each call.\n\nThe return value of a \"const char *\" hints to the caller, that the memory\nis not owned by the caller, do we need to be explicit there (i.e. a comment\ndeclaring the memory ownership? Probably not.)\n\nThanks,\nStefan\n"},{"id":"308972","messageId":"xmqqy3ylyqhf.fsf@gitster.mtv.corp.google.com","threadId":"44532","inReplyTo":"CAGZ79kZ--jp08pK+xwn1N2VQQr8bA5+DveE2HsoY90R1gR6c_A@mail.gmail.com","subject":"Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-01-09T01:25:00Z","receivedAt":"2017-01-09T01:25:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> On Mon, Jan 2, 2017 at 8:22 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n>> Technically, it is correct that git_exec_path() returns a possibly\n>> malloc()ed string. Practically, it is *sometimes* not malloc()ed. So\n>> let's just use a static variable to make it a singleton. That'll shut\n>> Coverity up, hopefully.\n>\n> I picked up this patch and applied it to the coverity branch\n> that I maintain at github/stefanbeller/git.\n>\n> I'd love to see this patch upstream as it reduces my maintenance\n> burden of the coverity branch by a patch.\n\nSo with the above, are you saying \"Dscho said 'hopefully', and I\nconfirm that this change does squelch misdiagnosis by Coverity\"?\n\n> If this patch is only to appease coverity (as the commit message eludes\n> to) I think this may be a bad idea for upstream.  If this patch fixes an\n> actual problem, then the commit message needs to spell that out.\n\nThat is true, and I see Peff pointed out another possible issue\naround getenv(), but I think from the \"one step at a time\" point of\nview, it is an improvement to call system_path() just once and cache\nit in \"static char *\".  \n\nHow about explaining it like this then?\n\n(only the log message has been corrected; diff is from the original).\n\ncommit c9bb5d101ca657fa466afa8c4368c43ea7b7aca8\nAuthor: Johannes Schindelin <johannes.schindelin@gmx.de>\nDate:   Mon Jan 2 17:22:33 2017 +0100\n\n    git_exec_path: avoid Coverity warning about unfree()d result\n    \n    Technically, it is correct that git_exec_path() returns a possibly\n    malloc()ed string returned from system_path(), and it is sometimes\n    not allocated.  Cache the result in a static variable and make sure\n    that we call system_path() only once, which plugs a potential leak.\n    \n    Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n\ndiff --git a/exec_cmd.c b/exec_cmd.c\nindex 9d5703a157..eae56fefba 100644\n--- a/exec_cmd.c\n+++ b/exec_cmd.c\n@@ -69,6 +69,7 @@ void git_set_argv_exec_path(const char *exec_path)\n const char *git_exec_path(void)\n {\n \tconst char *env;\n+\tstatic char *system_exec_path;\n \n \tif (argv_exec_path)\n \t\treturn argv_exec_path;\n@@ -78,7 +79,9 @@ const char *git_exec_path(void)\n \t\treturn env;\n \t}\n \n-\treturn system_path(GIT_EXEC_PATH);\n+\tif (!system_exec_path)\n+\t\tsystem_exec_path = system_path(GIT_EXEC_PATH);\n+\treturn system_exec_path;\n }\n \n static void add_path(struct strbuf *out, const char *path)\n\n"},{"id":"308973","messageId":"xmqqtw99ypvq.fsf@gitster.mtv.corp.google.com","threadId":"44532","inReplyTo":"0ae4a950a4cd2c8c4f05a6b46c60f127611580cf.1483373635.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-01-09T01:38:01Z","receivedAt":"2017-01-09T01:38:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> This is uglier than a simple\n>\n> \ttouch \"$GIT_EXEC_PATH/use-builtin-difftool\"\n>\n> of course. But oh well.\n\nThat is totally irrelevant.  \n\nThe more important point is that we do the same set of tests so\ninstead of running just one, we run BOTH.  If we did a bit more\nwork, we could even say \"even when there is no Perl installed, we\nrun tests with only the new built-in one\".  This does not go that\nfar yet, but that should not be too hard, I would think.  See below.\n\n> diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh\n> index e94910c563..273ab55723 100755\n> --- a/t/t7800-difftool.sh\n> +++ b/t/t7800-difftool.sh\n> @@ -23,6 +23,20 @@ prompt_given ()\n>  \ttest \"$prompt\" = \"Launch 'test-tool' [Y/n]? branch\"\n>  }\n>  \n> +for use_builtin_difftool in false true\n> +do\n\nInstead of the above, I'd suggest to make the loop like so:\n\n\tfor use_builtin_difftool in $TESTED_VARIANTS\n\tdo\n\nand before the loop begins, set TESTED_VARIANTS to either \"false\ntrue\" or \"true\" by checking the PERL prerequisite.\n\n> +test_expect_success 'verify we are running the correct difftool' '\n> +\tif test true = '$use_builtin_difftool'\n> +\tthen\n> +\t\ttest_must_fail ok=129 git difftool -h >help &&\n> +\t\tgrep \"g, --gui\" help\n> +\telse\n> +\t\tgit difftool -h >help &&\n> +\t\tgrep \"g|--gui\" help\n> +\tfi\n> +'\n> +\n>  # NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.\n\nAnd then we can lose this comment.  As all existing tests have PERL\nprereq that can go away with the above suggested change, we'd need\nto touch quite a many lines with \"s/_success PERL /_success /\".\n\nIt may be a good time to indent these existing tests to make it\nclear that they are inside the new \"for use_builtin_difftool\" loop.\n\n\n> +test true != $use_builtin_difftool || break\n> +\n> +test_expect_success 'tear down for re-run' '\n> +\trm -rf * .[a-z]* &&\n> +\tgit init\n> +'\n> +\n> +# run as builtin difftool now\n> +GIT_CONFIG_PARAMETERS=\"'difftool.usebuiltin=true'\"\n> +export GIT_CONFIG_PARAMETERS\n\n... and indentation would extend to these newly added lines, of\ncourse.\n\n> +done\n> +\n>  test_done\n"},{"id":"308999","messageId":"20170109060012.nuauxbgllqiicog7@sigill.intra.peff.net","threadId":"44532","inReplyTo":"xmqqy3ylyqhf.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-01-09T06:00:12Z","receivedAt":"2017-01-09T06:00:21Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Jan 08, 2017 at 05:25:00PM -0800, Junio C Hamano wrote:\n\n> > If this patch is only to appease coverity (as the commit message eludes\n> > to) I think this may be a bad idea for upstream.  If this patch fixes an\n> > actual problem, then the commit message needs to spell that out.\n> \n> That is true, and I see Peff pointed out another possible issue\n> around getenv(), but I think from the \"one step at a time\" point of\n> view, it is an improvement to call system_path() just once and cache\n> it in \"static char *\". \n\nYep, I don't think it's a big deal to do it on top, like this:\n\n-- >8 --\nSubject: git_exec_path: do not return the result of getenv()\n\nThe result of getenv() is not guaranteed by POSIX to last\nbeyond another call to getenv(), or setenv(), etc.  We\nshould duplicate the string before returning to the caller\nto avoid any surprises.\n\nWe already keep a cached pointer to avoid repeatedly leaking\nthe result of system_path(). We can use the same pointer\nhere to avoid allocating and leaking for each call.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n\nTo be honest, I do not know how big a problem this is. I looked at the\ncode paths that call git_exec_path(), and the most likely problem case\nis calling a second getenv() is via the strbuf functions, which call\nxmalloc(), which checks $GIT_ALLOC_LIMIT. We do cache that value, but\nit would be a potential problem if this is the first xmalloc call in\nthe program.\n\nBut we are not really solving that here, as xstrdup() would have the\nsame problem.  This _is_ safer, in that we've better contained the\nlength of time that we expect the result to be valid.\n\nI have no idea what platforms, if any, use a single static buffer for\nthe getenv() return. I don't know that we've ever gotten a bug report\nabout it (I only knew about it because somebody pointed it out in one\nof my patches a few years ago, so I have it in the back of my mind as\na potential problem).\n\nSo I don't mind if this is dropped as \"too esoteric\" until somebody\nactually reports a bug about it.\n\n exec_cmd.c | 17 ++++++++---------\n 1 file changed, 8 insertions(+), 9 deletions(-)\n\ndiff --git a/exec_cmd.c b/exec_cmd.c\nindex 587bd7eb4..fb94aeba9 100644\n--- a/exec_cmd.c\n+++ b/exec_cmd.c\n@@ -64,20 +64,19 @@ void git_set_argv_exec_path(const char *exec_path)\n /* Returns the highest-priority, location to look for git programs. */\n const char *git_exec_path(void)\n {\n-\tconst char *env;\n-\tstatic char *system_exec_path;\n+\tstatic char *cached_exec_path;\n \n \tif (argv_exec_path)\n \t\treturn argv_exec_path;\n \n-\tenv = getenv(EXEC_PATH_ENVIRONMENT);\n-\tif (env && *env) {\n-\t\treturn env;\n+\tif (!cached_exec_path) {\n+\t\tconst char *env = getenv(EXEC_PATH_ENVIRONMENT);\n+\t\tif (env && *env)\n+\t\t\tcached_exec_path = xstrdup(env);\n+\t\telse\n+\t\t\tcached_exec_path = system_path(GIT_EXEC_PATH);\n \t}\n-\n-\tif (!system_exec_path)\n-\t\tsystem_exec_path = system_path(GIT_EXEC_PATH);\n-\treturn system_exec_path;\n+\treturn cached_exec_path;\n }\n \n static void add_path(struct strbuf *out, const char *path)\n-- \n2.11.0.531.ge85397315\n\n"},{"id":"309003","messageId":"alpine.DEB.2.20.1701090848520.3469@virtualbox","threadId":"44532","inReplyTo":"xmqqy3ylyqhf.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-01-09T07:49:13Z","receivedAt":"2017-01-09T07:49:27Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sun, 8 Jan 2017, Junio C Hamano wrote:\n\n> How about explaining it like this then?\n> \n> (only the log message has been corrected; diff is from the original).\n> \n> commit c9bb5d101ca657fa466afa8c4368c43ea7b7aca8\n> Author: Johannes Schindelin <johannes.schindelin@gmx.de>\n> Date:   Mon Jan 2 17:22:33 2017 +0100\n> \n>     git_exec_path: avoid Coverity warning about unfree()d result\n>     \n>     Technically, it is correct that git_exec_path() returns a possibly\n>     malloc()ed string returned from system_path(), and it is sometimes\n>     not allocated.  Cache the result in a static variable and make sure\n>     that we call system_path() only once, which plugs a potential leak.\n>     \n>     Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n>     Signed-off-by: Junio C Hamano <gitster@pobox.com>\n\nSounds good to me.\n\nCiao,\nDscho\n"},{"id":"309004","messageId":"alpine.DEB.2.20.1701090850340.3469@virtualbox","threadId":"44532","inReplyTo":"xmqqtw99ypvq.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-01-09T07:56:29Z","receivedAt":"2017-01-09T07:56:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Sun, 8 Jan 2017, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> > This is uglier than a simple\n> >\n> > \ttouch \"$GIT_EXEC_PATH/use-builtin-difftool\"\n> >\n> > of course. But oh well.\n> \n> That is totally irrelevant.  \n> \n> The more important point is that we do the same set of tests so\n> instead of running just one, we run BOTH.\n\nAnd the most important point is that we do all of this only during a\nhopefully brief period in time that is mostly spent on reviewing the code\nand finding serious bugs and fixing them.\n\nDuring that period (which I would expect to be spent completely inside\n`pu`), the cross-validation does not have to be beautiful, but correct.\nAnd even more importantly: it has to come at a minimal integration cost,\nin case David decides to add another test case to t7800.\n\nAnd after that period, we retire the Perl script and switch to the builtin\ndifftool and simply drop this patch to cross-validate both difftools'\noutputs with one another.\n\nAt which point all of this safe-guarding and indenting and all of those\nchanges that are just meant to cross-validate the output during that\nhopefully brief period of time will become moot and all the work spent on\nthose changes will be worthless.\n\nOh, at that point also all review that went into *this* patch will have\nbeen spent in vain.\n\nIn short: can we please move on to reviewing the *actual* builtin difftool\nand spend our effort on making sure that it is correct?\n\nCiao,\nJohannes\n"},{"id":"309010","messageId":"xmqqbmvgy396.fsf@gitster.mtv.corp.google.com","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1701090850340.3469@virtualbox","subject":"Re: [PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-01-09T09:46:45Z","receivedAt":"2017-01-09T09:46:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> And the most important point is that we do all of this only during a\n> hopefully brief period in time that is mostly spent on reviewing the code\n> and finding serious bugs and fixing them.\n\nYou seem to misunderstand the purpose of the code review.  \n\nWe indeed spot bugs while reviewing patches, especially ones from\nless experienced folks, but that is the least important part of the\nreview.  In general, we review for:\n\n - Design.  Is the feature make sense?  Is it too narrow?  Are there\n   better ways?  Does it fit well with the rest of the system?\n\n - Explanation.  Is the purpose of the change in the bigger picture\n   explained well enough to allow future people to answer this\n   question: \"We now have an additional requirement to the feature.\n   If the original author knew about that when this was first\n   introduced, would s/he consider that our design for this\n   additional thing consistent with the original design?  Should we\n   design our enhancement in a different way?\"\n\n - Maintainability.  Does the implementation avoid reinventing data\n   structures and helper functions that already exist to interact\n   with elements in the system?  Would a future change to some\n   elements in the system that are touched by the implementation\n   require changes to both existing code _and_ reinvented ones the\n   patch introduced?\n\n - Correctness.  Does the implementation actually reflect the design\n   and the way the design was explained?\n\nFor the \"difftool in C\" topic, the first two are mostly irrelevant\nas the goal of the topic is to first replicate what already exists\nfaithfully (even in a bug-to-bug compatible way).  The issues in\ncorrectness is something your daily use before submission would\nhave caught, use of 'next' users as testers will help, and also\ncaught by running test suite (again, before submission).  \n\nI honestly do not expect glaring errors in the code from experienced\ncontributors remaining when their patches are polished enough to be\nsubmit to the list.\n"},{"id":"309054","messageId":"CAGZ79ka0+=om_iWyALCZcK502oU9vCFsZG15ktsN6MxkwKTN-w@mail.gmail.com","threadId":"44532","inReplyTo":"xmqqy3ylyqhf.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path()","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2017-01-09T19:21:41Z","receivedAt":"2017-01-09T19:21:47Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Sun, Jan 8, 2017 at 5:25 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> So with the above, are you saying \"Dscho said 'hopefully', and I\n> confirm that this change does squelch misdiagnosis by Coverity\"?\n\nI could not find the coverity issue any more.\n(It really misses easy access to \"recently fixed\" problems)\n\n> commit c9bb5d101ca657fa466afa8c4368c43ea7b7aca8\n> Author: Johannes Schindelin <johannes.schindelin@gmx.de>\n> Date:   Mon Jan 2 17:22:33 2017 +0100\n>\n>     git_exec_path: avoid Coverity warning about unfree()d result\n>\n>     Technically, it is correct that git_exec_path() returns a possibly\n>     malloc()ed string returned from system_path(), and it is sometimes\n>     not allocated.  Cache the result in a static variable and make sure\n>     that we call system_path() only once, which plugs a potential leak.\n>\n>     Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n>     Signed-off-by: Junio C Hamano <gitster@pobox.com>\n\nSounds good to me,\n\nThanks,\nStefan\n"},{"id":"309521","messageId":"8238bba389c031b091a37396fed43cac94d944e7.1484668473.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1484668473.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 3/3] Retire the scripted difftool","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-01-17T15:55:04Z","receivedAt":"2017-01-17T16:02:56Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"It served its purpose, but now we have a builtin difftool. Time for the\nPerl script to enjoy Florida.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n .gitignore                                         |  1 -\n Makefile                                           |  1 -\n builtin/difftool.c                                 | 41 ----------\n .../examples/git-difftool.perl                     |  0\n git.c                                              |  7 +-\n t/t7800-difftool.sh                                | 94 +++++++++++-----------\n 6 files changed, 47 insertions(+), 97 deletions(-)\n rename git-legacy-difftool.perl => contrib/examples/git-difftool.perl (100%)\n\ndiff --git a/.gitignore b/.gitignore\nindex 5555ae025b..6722f78f9a 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -76,7 +76,6 @@\n /git-init-db\n /git-interpret-trailers\n /git-instaweb\n-/git-legacy-difftool\n /git-log\n /git-ls-files\n /git-ls-remote\ndiff --git a/Makefile b/Makefile\nindex 8cf5bef034..e9aa6ae57c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -522,7 +522,6 @@ SCRIPT_LIB += git-sh-setup\n SCRIPT_LIB += git-sh-i18n\n \n SCRIPT_PERL += git-add--interactive.perl\n-SCRIPT_PERL += git-legacy-difftool.perl\n SCRIPT_PERL += git-archimport.perl\n SCRIPT_PERL += git-cvsexportcommit.perl\n SCRIPT_PERL += git-cvsimport.perl\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex 2115e548a5..42ad9e804a 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -616,30 +616,6 @@ static int run_file_diff(int prompt, const char *prefix,\n \texit(ret);\n }\n \n-/*\n- * NEEDSWORK: this function can go once the legacy-difftool Perl script is\n- * retired.\n- *\n- * We intentionally avoid reading the config directly here, to avoid messing up\n- * the GIT_* environment variables when we need to fall back to exec()ing the\n- * Perl script.\n- */\n-static int use_builtin_difftool(void) {\n-\tstruct child_process cp = CHILD_PROCESS_INIT;\n-\tstruct strbuf out = STRBUF_INIT;\n-\tint ret;\n-\n-\targv_array_pushl(&cp.args,\n-\t\t\t \"config\", \"--bool\", \"difftool.usebuiltin\", NULL);\n-\tcp.git_cmd = 1;\n-\tif (capture_command(&cp, &out, 6))\n-\t\treturn 0;\n-\tstrbuf_trim(&out);\n-\tret = !strcmp(\"true\", out.buf);\n-\tstrbuf_release(&out);\n-\treturn ret;\n-}\n-\n int cmd_difftool(int argc, const char **argv, const char *prefix)\n {\n \tint use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,\n@@ -671,23 +647,6 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n-\t/*\n-\t * NEEDSWORK: Once the builtin difftool has been tested enough\n-\t * and git-legacy-difftool.perl is retired to contrib/, this preamble\n-\t * can be removed.\n-\t */\n-\tif (!use_builtin_difftool()) {\n-\t\tconst char *path = mkpath(\"%s/git-legacy-difftool\",\n-\t\t\t\t\t  git_exec_path());\n-\n-\t\tif (sane_execvp(path, (char **)argv) < 0)\n-\t\t\tdie_errno(\"could not exec %s\", path);\n-\n-\t\treturn 0;\n-\t}\n-\tprefix = setup_git_directory();\n-\ttrace_repo_setup(prefix);\n-\tsetup_work_tree();\n \t/* NEEDSWORK: once we no longer spawn anything, remove this */\n \tsetenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);\n \tsetenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);\ndiff --git a/git-legacy-difftool.perl b/contrib/examples/git-difftool.perl\nsimilarity index 100%\nrename from git-legacy-difftool.perl\nrename to contrib/examples/git-difftool.perl\ndiff --git a/git.c b/git.c\nindex c58181e5ef..bd4d668a21 100644\n--- a/git.c\n+++ b/git.c\n@@ -424,12 +424,7 @@ static struct cmd_struct commands[] = {\n \t{ \"diff-files\", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"diff-index\", cmd_diff_index, RUN_SETUP },\n \t{ \"diff-tree\", cmd_diff_tree, RUN_SETUP },\n-\t/*\n-\t * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in\n-\t * builtin/difftool.c has been removed, this entry should be changed to\n-\t * RUN_SETUP | NEED_WORK_TREE\n-\t */\n-\t{ \"difftool\", cmd_difftool },\n+\t{ \"difftool\", cmd_difftool, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"fast-export\", cmd_fast_export, RUN_SETUP },\n \t{ \"fetch\", cmd_fetch, RUN_SETUP },\n \t{ \"fetch-pack\", cmd_fetch_pack, RUN_SETUP },\ndiff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh\nindex e94910c563..aa0ef02597 100755\n--- a/t/t7800-difftool.sh\n+++ b/t/t7800-difftool.sh\n@@ -23,10 +23,8 @@ prompt_given ()\n \ttest \"$prompt\" = \"Launch 'test-tool' [Y/n]? branch\"\n }\n \n-# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.\n-\n # Create a file on master and change it on branch\n-test_expect_success PERL 'setup' '\n+test_expect_success 'setup' '\n \techo master >file &&\n \tgit add file &&\n \tgit commit -m \"added file\" &&\n@@ -38,7 +36,7 @@ test_expect_success PERL 'setup' '\n '\n \n # Configure a custom difftool.<tool>.cmd and use it\n-test_expect_success PERL 'custom commands' '\n+test_expect_success 'custom commands' '\n \tdifftool_test_setup &&\n \ttest_config difftool.test-tool.cmd \"cat \\\"\\$REMOTE\\\"\" &&\n \techo master >expect &&\n@@ -51,21 +49,21 @@ test_expect_success PERL 'custom commands' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'custom tool commands override built-ins' '\n+test_expect_success 'custom tool commands override built-ins' '\n \ttest_config difftool.vimdiff.cmd \"cat \\\"\\$REMOTE\\\"\" &&\n \techo master >expect &&\n \tgit difftool --tool vimdiff --no-prompt branch >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool ignores bad --tool values' '\n+test_expect_success 'difftool ignores bad --tool values' '\n \t: >expect &&\n \ttest_must_fail \\\n \t\tgit difftool --no-prompt --tool=bad-tool branch >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool forwards arguments to diff' '\n+test_expect_success 'difftool forwards arguments to diff' '\n \tdifftool_test_setup &&\n \t>for-diff &&\n \tgit add for-diff &&\n@@ -78,40 +76,40 @@ test_expect_success PERL 'difftool forwards arguments to diff' '\n \trm for-diff\n '\n \n-test_expect_success PERL 'difftool ignores exit code' '\n+test_expect_success 'difftool ignores exit code' '\n \ttest_config difftool.error.cmd false &&\n \tgit difftool -y -t error branch\n '\n \n-test_expect_success PERL 'difftool forwards exit code with --trust-exit-code' '\n+test_expect_success 'difftool forwards exit code with --trust-exit-code' '\n \ttest_config difftool.error.cmd false &&\n \ttest_must_fail git difftool -y --trust-exit-code -t error branch\n '\n \n-test_expect_success PERL 'difftool forwards exit code with --trust-exit-code for built-ins' '\n+test_expect_success 'difftool forwards exit code with --trust-exit-code for built-ins' '\n \ttest_config difftool.vimdiff.path false &&\n \ttest_must_fail git difftool -y --trust-exit-code -t vimdiff branch\n '\n \n-test_expect_success PERL 'difftool honors difftool.trustExitCode = true' '\n+test_expect_success 'difftool honors difftool.trustExitCode = true' '\n \ttest_config difftool.error.cmd false &&\n \ttest_config difftool.trustExitCode true &&\n \ttest_must_fail git difftool -y -t error branch\n '\n \n-test_expect_success PERL 'difftool honors difftool.trustExitCode = false' '\n+test_expect_success 'difftool honors difftool.trustExitCode = false' '\n \ttest_config difftool.error.cmd false &&\n \ttest_config difftool.trustExitCode false &&\n \tgit difftool -y -t error branch\n '\n \n-test_expect_success PERL 'difftool ignores exit code with --no-trust-exit-code' '\n+test_expect_success 'difftool ignores exit code with --no-trust-exit-code' '\n \ttest_config difftool.error.cmd false &&\n \ttest_config difftool.trustExitCode true &&\n \tgit difftool -y --no-trust-exit-code -t error branch\n '\n \n-test_expect_success PERL 'difftool stops on error with --trust-exit-code' '\n+test_expect_success 'difftool stops on error with --trust-exit-code' '\n \ttest_when_finished \"rm -f for-diff .git/fail-right-file\" &&\n \ttest_when_finished \"git reset -- for-diff\" &&\n \twrite_script .git/fail-right-file <<-\\EOF &&\n@@ -126,13 +124,13 @@ test_expect_success PERL 'difftool stops on error with --trust-exit-code' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool honors exit status if command not found' '\n+test_expect_success 'difftool honors exit status if command not found' '\n \ttest_config difftool.nonexistent.cmd i-dont-exist &&\n \ttest_config difftool.trustExitCode false &&\n \ttest_must_fail git difftool -y -t nonexistent branch\n '\n \n-test_expect_success PERL 'difftool honors --gui' '\n+test_expect_success 'difftool honors --gui' '\n \tdifftool_test_setup &&\n \ttest_config merge.tool bogus-tool &&\n \ttest_config diff.tool bogus-tool &&\n@@ -143,7 +141,7 @@ test_expect_success PERL 'difftool honors --gui' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool --gui last setting wins' '\n+test_expect_success 'difftool --gui last setting wins' '\n \tdifftool_test_setup &&\n \t: >expect &&\n \tgit difftool --no-prompt --gui --no-gui >actual &&\n@@ -157,7 +155,7 @@ test_expect_success PERL 'difftool --gui last setting wins' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool --gui works without configured diff.guitool' '\n+test_expect_success 'difftool --gui works without configured diff.guitool' '\n \tdifftool_test_setup &&\n \techo branch >expect &&\n \tgit difftool --no-prompt --gui branch >actual &&\n@@ -165,7 +163,7 @@ test_expect_success PERL 'difftool --gui works without configured diff.guitool'\n '\n \n # Specify the diff tool using $GIT_DIFF_TOOL\n-test_expect_success PERL 'GIT_DIFF_TOOL variable' '\n+test_expect_success 'GIT_DIFF_TOOL variable' '\n \tdifftool_test_setup &&\n \tgit config --unset diff.tool &&\n \techo branch >expect &&\n@@ -175,7 +173,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL variable' '\n \n # Test the $GIT_*_TOOL variables and ensure\n # that $GIT_DIFF_TOOL always wins unless --tool is specified\n-test_expect_success PERL 'GIT_DIFF_TOOL overrides' '\n+test_expect_success 'GIT_DIFF_TOOL overrides' '\n \tdifftool_test_setup &&\n \ttest_config diff.tool bogus-tool &&\n \ttest_config merge.tool bogus-tool &&\n@@ -193,7 +191,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL overrides' '\n \n # Test that we don't have to pass --no-prompt to difftool\n # when $GIT_DIFFTOOL_NO_PROMPT is true\n-test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' '\n+test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' '\n \tdifftool_test_setup &&\n \techo branch >expect &&\n \tGIT_DIFFTOOL_NO_PROMPT=true git difftool branch >actual &&\n@@ -202,7 +200,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' '\n \n # git-difftool supports the difftool.prompt variable.\n # Test that GIT_DIFFTOOL_PROMPT can override difftool.prompt = false\n-test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' '\n+test_expect_success 'GIT_DIFFTOOL_PROMPT variable' '\n \tdifftool_test_setup &&\n \ttest_config difftool.prompt false &&\n \techo >input &&\n@@ -212,7 +210,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' '\n '\n \n # Test that we don't have to pass --no-prompt when difftool.prompt is false\n-test_expect_success PERL 'difftool.prompt config variable is false' '\n+test_expect_success 'difftool.prompt config variable is false' '\n \tdifftool_test_setup &&\n \ttest_config difftool.prompt false &&\n \techo branch >expect &&\n@@ -221,7 +219,7 @@ test_expect_success PERL 'difftool.prompt config variable is false' '\n '\n \n # Test that we don't have to pass --no-prompt when mergetool.prompt is false\n-test_expect_success PERL 'difftool merge.prompt = false' '\n+test_expect_success 'difftool merge.prompt = false' '\n \tdifftool_test_setup &&\n \ttest_might_fail git config --unset difftool.prompt &&\n \ttest_config mergetool.prompt false &&\n@@ -231,7 +229,7 @@ test_expect_success PERL 'difftool merge.prompt = false' '\n '\n \n # Test that the -y flag can override difftool.prompt = true\n-test_expect_success PERL 'difftool.prompt can overridden with -y' '\n+test_expect_success 'difftool.prompt can overridden with -y' '\n \tdifftool_test_setup &&\n \ttest_config difftool.prompt true &&\n \techo branch >expect &&\n@@ -240,7 +238,7 @@ test_expect_success PERL 'difftool.prompt can overridden with -y' '\n '\n \n # Test that the --prompt flag can override difftool.prompt = false\n-test_expect_success PERL 'difftool.prompt can overridden with --prompt' '\n+test_expect_success 'difftool.prompt can overridden with --prompt' '\n \tdifftool_test_setup &&\n \ttest_config difftool.prompt false &&\n \techo >input &&\n@@ -250,7 +248,7 @@ test_expect_success PERL 'difftool.prompt can overridden with --prompt' '\n '\n \n # Test that the last flag passed on the command-line wins\n-test_expect_success PERL 'difftool last flag wins' '\n+test_expect_success 'difftool last flag wins' '\n \tdifftool_test_setup &&\n \techo branch >expect &&\n \tgit difftool --prompt --no-prompt branch >actual &&\n@@ -263,7 +261,7 @@ test_expect_success PERL 'difftool last flag wins' '\n \n # git-difftool falls back to git-mergetool config variables\n # so test that behavior here\n-test_expect_success PERL 'difftool + mergetool config variables' '\n+test_expect_success 'difftool + mergetool config variables' '\n \ttest_config merge.tool test-tool &&\n \ttest_config mergetool.test-tool.cmd \"cat \\$LOCAL\" &&\n \techo branch >expect &&\n@@ -277,49 +275,49 @@ test_expect_success PERL 'difftool + mergetool config variables' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool.<tool>.path' '\n+test_expect_success 'difftool.<tool>.path' '\n \ttest_config difftool.tkdiff.path echo &&\n \tgit difftool --tool=tkdiff --no-prompt branch >output &&\n \tlines=$(grep file output | wc -l) &&\n \ttest \"$lines\" -eq 1\n '\n \n-test_expect_success PERL 'difftool --extcmd=cat' '\n+test_expect_success 'difftool --extcmd=cat' '\n \techo branch >expect &&\n \techo master >>expect &&\n \tgit difftool --no-prompt --extcmd=cat branch >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool --extcmd cat' '\n+test_expect_success 'difftool --extcmd cat' '\n \techo branch >expect &&\n \techo master >>expect &&\n \tgit difftool --no-prompt --extcmd=cat branch >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool -x cat' '\n+test_expect_success 'difftool -x cat' '\n \techo branch >expect &&\n \techo master >>expect &&\n \tgit difftool --no-prompt -x cat branch >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool --extcmd echo arg1' '\n+test_expect_success 'difftool --extcmd echo arg1' '\n \techo file >expect &&\n \tgit difftool --no-prompt \\\n \t\t--extcmd sh\\ -c\\ \\\"echo\\ \\$1\\\" branch >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool --extcmd cat arg1' '\n+test_expect_success 'difftool --extcmd cat arg1' '\n \techo master >expect &&\n \tgit difftool --no-prompt \\\n \t\t--extcmd sh\\ -c\\ \\\"cat\\ \\$1\\\" branch >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool --extcmd cat arg2' '\n+test_expect_success 'difftool --extcmd cat arg2' '\n \techo branch >expect &&\n \tgit difftool --no-prompt \\\n \t\t--extcmd sh\\ -c\\ \\\"cat\\ \\$2\\\" branch >actual &&\n@@ -327,7 +325,7 @@ test_expect_success PERL 'difftool --extcmd cat arg2' '\n '\n \n # Create a second file on master and a different version on branch\n-test_expect_success PERL 'setup with 2 files different' '\n+test_expect_success 'setup with 2 files different' '\n \techo m2 >file2 &&\n \tgit add file2 &&\n \tgit commit -m \"added file2\" &&\n@@ -339,7 +337,7 @@ test_expect_success PERL 'setup with 2 files different' '\n \tgit checkout master\n '\n \n-test_expect_success PERL 'say no to the first file' '\n+test_expect_success 'say no to the first file' '\n \t(echo n && echo) >input &&\n \tgit difftool -x cat branch <input >output &&\n \tgrep m2 output &&\n@@ -348,7 +346,7 @@ test_expect_success PERL 'say no to the first file' '\n \t! grep branch output\n '\n \n-test_expect_success PERL 'say no to the second file' '\n+test_expect_success 'say no to the second file' '\n \t(echo && echo n) >input &&\n \tgit difftool -x cat branch <input >output &&\n \tgrep master output &&\n@@ -357,7 +355,7 @@ test_expect_success PERL 'say no to the second file' '\n \t! grep br2 output\n '\n \n-test_expect_success PERL 'ending prompt input with EOF' '\n+test_expect_success 'ending prompt input with EOF' '\n \tgit difftool -x cat branch </dev/null >output &&\n \t! grep master output &&\n \t! grep branch output &&\n@@ -365,12 +363,12 @@ test_expect_success PERL 'ending prompt input with EOF' '\n \t! grep br2 output\n '\n \n-test_expect_success PERL 'difftool --tool-help' '\n+test_expect_success 'difftool --tool-help' '\n \tgit difftool --tool-help >output &&\n \tgrep tool output\n '\n \n-test_expect_success PERL 'setup change in subdirectory' '\n+test_expect_success 'setup change in subdirectory' '\n \tgit checkout master &&\n \tmkdir sub &&\n \techo master >sub/sub &&\n@@ -384,11 +382,11 @@ test_expect_success PERL 'setup change in subdirectory' '\n '\n \n run_dir_diff_test () {\n-\ttest_expect_success PERL \"$1 --no-symlinks\" \"\n+\ttest_expect_success \"$1 --no-symlinks\" \"\n \t\tsymlinks=--no-symlinks &&\n \t\t$2\n \t\"\n-\ttest_expect_success PERL,SYMLINKS \"$1 --symlinks\" \"\n+\ttest_expect_success SYMLINKS \"$1 --symlinks\" \"\n \t\tsymlinks=--symlinks &&\n \t\t$2\n \t\"\n@@ -510,7 +508,7 @@ do\n done >actual\n EOF\n \n-test_expect_success PERL,SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' '\n+test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' '\n \tcat >expect <<-EOF &&\n \tfile\n \t$PWD/file\n@@ -547,7 +545,7 @@ write_script modify-file <<\\EOF\n echo \"new content\" >file\n EOF\n \n-test_expect_success PERL 'difftool --no-symlinks does not overwrite working tree file ' '\n+test_expect_success 'difftool --no-symlinks does not overwrite working tree file ' '\n \techo \"orig content\" >file &&\n \tgit difftool --dir-diff --no-symlinks --extcmd \"$PWD/modify-file\" branch &&\n \techo \"new content\" >expect &&\n@@ -560,7 +558,7 @@ echo \"tmp content\" >\"$2/file\" &&\n echo \"$2\" >tmpdir\n EOF\n \n-test_expect_success PERL 'difftool --no-symlinks detects conflict ' '\n+test_expect_success 'difftool --no-symlinks detects conflict ' '\n \t(\n \t\tTMPDIR=$TRASH_DIRECTORY &&\n \t\texport TMPDIR &&\n@@ -573,7 +571,7 @@ test_expect_success PERL 'difftool --no-symlinks detects conflict ' '\n \t)\n '\n \n-test_expect_success PERL 'difftool properly honors gitlink and core.worktree' '\n+test_expect_success 'difftool properly honors gitlink and core.worktree' '\n \tgit submodule add ./. submod/ule &&\n \ttest_config -C submod/ule diff.tool checktrees &&\n \ttest_config -C submod/ule difftool.checktrees.cmd '\\''\n@@ -587,7 +585,7 @@ test_expect_success PERL 'difftool properly honors gitlink and core.worktree' '\n \t)\n '\n \n-test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' '\n+test_expect_success SYMLINKS 'difftool --dir-diff symlinked directories' '\n \tgit init dirlinks &&\n \t(\n \t\tcd dirlinks &&\n-- \n2.11.0.windows.3\n"},{"id":"309522","messageId":"88ec8b33311299c41b3b29c71e6150a47ce70377.1484668473.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1484668473.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 1/3] difftool: add a skeleton for the upcoming builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-01-17T15:54:57Z","receivedAt":"2017-01-17T16:02:59Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This adds a builtin difftool that still falls back to the legacy Perl\nversion, which has been renamed to `legacy-difftool`.\n\nThe idea is that the new, experimental, builtin difftool immediately hands\noff to the legacy difftool for now, unless the config variable\ndifftool.useBuiltin is set to true.\n\nThis feature flag will be used in the upcoming Git for Windows v2.11.0\nrelease, to allow early testers to opt-in to use the builtin difftool and\nflesh out any bugs.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n .gitignore                                    |  1 +\n Makefile                                      |  3 +-\n builtin.h                                     |  1 +\n builtin/difftool.c                            | 63 +++++++++++++++++++++++++++\n git-difftool.perl => git-legacy-difftool.perl |  0\n git.c                                         |  6 +++\n t/t7800-difftool.sh                           |  2 +\n 7 files changed, 75 insertions(+), 1 deletion(-)\n create mode 100644 builtin/difftool.c\n rename git-difftool.perl => git-legacy-difftool.perl (100%)\n\ndiff --git a/.gitignore b/.gitignore\nindex 6722f78f9a..5555ae025b 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -76,6 +76,7 @@\n /git-init-db\n /git-interpret-trailers\n /git-instaweb\n+/git-legacy-difftool\n /git-log\n /git-ls-files\n /git-ls-remote\ndiff --git a/Makefile b/Makefile\nindex d861bd9985..8cf5bef034 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -522,7 +522,7 @@ SCRIPT_LIB += git-sh-setup\n SCRIPT_LIB += git-sh-i18n\n \n SCRIPT_PERL += git-add--interactive.perl\n-SCRIPT_PERL += git-difftool.perl\n+SCRIPT_PERL += git-legacy-difftool.perl\n SCRIPT_PERL += git-archimport.perl\n SCRIPT_PERL += git-cvsexportcommit.perl\n SCRIPT_PERL += git-cvsimport.perl\n@@ -883,6 +883,7 @@ BUILTIN_OBJS += builtin/diff-files.o\n BUILTIN_OBJS += builtin/diff-index.o\n BUILTIN_OBJS += builtin/diff-tree.o\n BUILTIN_OBJS += builtin/diff.o\n+BUILTIN_OBJS += builtin/difftool.o\n BUILTIN_OBJS += builtin/fast-export.o\n BUILTIN_OBJS += builtin/fetch-pack.o\n BUILTIN_OBJS += builtin/fetch.o\ndiff --git a/builtin.h b/builtin.h\nindex b9122bc5f4..67f80519da 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix);\n extern int cmd_diff_index(int argc, const char **argv, const char *prefix);\n extern int cmd_diff(int argc, const char **argv, const char *prefix);\n extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);\n+extern int cmd_difftool(int argc, const char **argv, const char *prefix);\n extern int cmd_fast_export(int argc, const char **argv, const char *prefix);\n extern int cmd_fetch(int argc, const char **argv, const char *prefix);\n extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nnew file mode 100644\nindex 0000000000..53870bbaf7\n--- /dev/null\n+++ b/builtin/difftool.c\n@@ -0,0 +1,63 @@\n+/*\n+ * \"git difftool\" builtin command\n+ *\n+ * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible\n+ * git-difftool--helper script.\n+ *\n+ * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.\n+ * The GIT_DIFF* variables are exported for use by git-difftool--helper.\n+ *\n+ * Any arguments that are unknown to this script are forwarded to 'git diff'.\n+ *\n+ * Copyright (C) 2016 Johannes Schindelin\n+ */\n+#include \"builtin.h\"\n+#include \"run-command.h\"\n+#include \"exec_cmd.h\"\n+\n+/*\n+ * NEEDSWORK: this function can go once the legacy-difftool Perl script is\n+ * retired.\n+ *\n+ * We intentionally avoid reading the config directly here, to avoid messing up\n+ * the GIT_* environment variables when we need to fall back to exec()ing the\n+ * Perl script.\n+ */\n+static int use_builtin_difftool(void) {\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tstruct strbuf out = STRBUF_INIT;\n+\tint ret;\n+\n+\targv_array_pushl(&cp.args,\n+\t\t\t \"config\", \"--bool\", \"difftool.usebuiltin\", NULL);\n+\tcp.git_cmd = 1;\n+\tif (capture_command(&cp, &out, 6))\n+\t\treturn 0;\n+\tstrbuf_trim(&out);\n+\tret = !strcmp(\"true\", out.buf);\n+\tstrbuf_release(&out);\n+\treturn ret;\n+}\n+\n+int cmd_difftool(int argc, const char **argv, const char *prefix)\n+{\n+\t/*\n+\t * NEEDSWORK: Once the builtin difftool has been tested enough\n+\t * and git-legacy-difftool.perl is retired to contrib/, this preamble\n+\t * can be removed.\n+\t */\n+\tif (!use_builtin_difftool()) {\n+\t\tconst char *path = mkpath(\"%s/git-legacy-difftool\",\n+\t\t\t\t\t  git_exec_path());\n+\n+\t\tif (sane_execvp(path, (char **)argv) < 0)\n+\t\t\tdie_errno(\"could not exec %s\", path);\n+\n+\t\treturn 0;\n+\t}\n+\tprefix = setup_git_directory();\n+\ttrace_repo_setup(prefix);\n+\tsetup_work_tree();\n+\n+\tdie(\"TODO\");\n+}\ndiff --git a/git-difftool.perl b/git-legacy-difftool.perl\nsimilarity index 100%\nrename from git-difftool.perl\nrename to git-legacy-difftool.perl\ndiff --git a/git.c b/git.c\nindex bbaa949e9c..c58181e5ef 100644\n--- a/git.c\n+++ b/git.c\n@@ -424,6 +424,12 @@ static struct cmd_struct commands[] = {\n \t{ \"diff-files\", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"diff-index\", cmd_diff_index, RUN_SETUP },\n \t{ \"diff-tree\", cmd_diff_tree, RUN_SETUP },\n+\t/*\n+\t * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in\n+\t * builtin/difftool.c has been removed, this entry should be changed to\n+\t * RUN_SETUP | NEED_WORK_TREE\n+\t */\n+\t{ \"difftool\", cmd_difftool },\n \t{ \"fast-export\", cmd_fast_export, RUN_SETUP },\n \t{ \"fetch\", cmd_fetch, RUN_SETUP },\n \t{ \"fetch-pack\", cmd_fetch_pack, RUN_SETUP },\ndiff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh\nindex 99d4123461..e94910c563 100755\n--- a/t/t7800-difftool.sh\n+++ b/t/t7800-difftool.sh\n@@ -23,6 +23,8 @@ prompt_given ()\n \ttest \"$prompt\" = \"Launch 'test-tool' [Y/n]? branch\"\n }\n \n+# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.\n+\n # Create a file on master and change it on branch\n test_expect_success PERL 'setup' '\n \techo master >file &&\n-- \n2.11.0.windows.3\n\n\n"},{"id":"309523","messageId":"a86f9b175f97c0983c86599469243f11ebc32d19.1484668473.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1484668473.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 2/3] difftool: implement the functionality in the builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-01-17T15:55:00Z","receivedAt":"2017-01-17T16:03:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch gives life to the skeleton added in the previous patch.\n\nThe motivation for converting the difftool is that Perl scripts are not at\nall native on Windows, and that `git difftool` therefore is pretty slow on\nthat platform, when there is no good reason for it to be slow.\n\nIn addition, Perl does not really have access to Git's internals. That\nmeans that any script will always have to jump through unnecessary\nhoops.\n\nThe current version of the builtin difftool does not, however, make full\nuse of the internals but instead chooses to spawn a couple of Git\nprocesses, still, to make for an easier conversion. There remains a lot\nof room for improvement, left for a later date.\n\nNote: to play it safe, the original difftool is still called unless the\nconfig setting difftool.useBuiltin is set to true.\n\nThe reason: this new, experimental, builtin difftool will be shipped as\npart of Git for Windows v2.11.0, to allow for easier large-scale\ntesting, but of course as an opt-in feature.\n\nSadly, the speedup is more noticable on Linux than on Windows: a quick\ntest shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s)\n(real/user/sys) in a Linux VM, down from  (6.529s/3.112s/0.644s), while\non Windows, it is (36.064s/2.730s/7.194s), down from\n(47.637s/2.407s/6.863s). The culprit is most likely the overhead\nincurred from *still* having to shell out to mergetool-lib.sh and\ndifftool--helper.sh.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/difftool.c | 672 ++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 671 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex 53870bbaf7..2115e548a5 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -11,9 +11,610 @@\n  *\n  * Copyright (C) 2016 Johannes Schindelin\n  */\n+#include \"cache.h\"\n #include \"builtin.h\"\n #include \"run-command.h\"\n #include \"exec_cmd.h\"\n+#include \"parse-options.h\"\n+#include \"argv-array.h\"\n+#include \"strbuf.h\"\n+#include \"lockfile.h\"\n+#include \"dir.h\"\n+\n+static char *diff_gui_tool;\n+static int trust_exit_code;\n+\n+static const char *const builtin_difftool_usage[] = {\n+\tN_(\"git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]\"),\n+\tNULL\n+};\n+\n+static int difftool_config(const char *var, const char *value, void *cb)\n+{\n+\tif (!strcmp(var, \"diff.guitool\")) {\n+\t\tdiff_gui_tool = xstrdup(value);\n+\t\treturn 0;\n+\t}\n+\n+\tif (!strcmp(var, \"difftool.trustexitcode\")) {\n+\t\ttrust_exit_code = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n+\treturn git_default_config(var, value, cb);\n+}\n+\n+static int print_tool_help(void)\n+{\n+\tconst char *argv[] = { \"mergetool\", \"--tool-help=diff\", NULL };\n+\treturn run_command_v_opt(argv, RUN_GIT_CMD);\n+}\n+\n+static int parse_index_info(char *p, int *mode1, int *mode2,\n+\t\t\t    struct object_id *oid1, struct object_id *oid2,\n+\t\t\t    char *status)\n+{\n+\tif (*p != ':')\n+\t\treturn error(\"expected ':', got '%c'\", *p);\n+\t*mode1 = (int)strtol(p + 1, &p, 8);\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\t*mode2 = (int)strtol(p + 1, &p, 8);\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\tif (get_oid_hex(++p, oid1))\n+\t\treturn error(\"expected object ID, got '%s'\", p + 1);\n+\tp += GIT_SHA1_HEXSZ;\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\tif (get_oid_hex(++p, oid2))\n+\t\treturn error(\"expected object ID, got '%s'\", p + 1);\n+\tp += GIT_SHA1_HEXSZ;\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\t*status = *++p;\n+\tif (!*status)\n+\t\treturn error(\"missing status\");\n+\tif (p[1] && !isdigit(p[1]))\n+\t\treturn error(\"unexpected trailer: '%s'\", p + 1);\n+\treturn 0;\n+}\n+\n+/*\n+ * Remove any trailing slash from $workdir\n+ * before starting to avoid double slashes in symlink targets.\n+ */\n+static void add_path(struct strbuf *buf, size_t base_len, const char *path)\n+{\n+\tstrbuf_setlen(buf, base_len);\n+\tif (buf->len && buf->buf[buf->len - 1] != '/')\n+\t\tstrbuf_addch(buf, '/');\n+\tstrbuf_addstr(buf, path);\n+}\n+\n+/*\n+ * Determine whether we can simply reuse the file in the worktree.\n+ */\n+static int use_wt_file(const char *workdir, const char *name,\n+\t\t       struct object_id *oid)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct stat st;\n+\tint use = 0;\n+\n+\tstrbuf_addstr(&buf, workdir);\n+\tadd_path(&buf, buf.len, name);\n+\n+\tif (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) {\n+\t\tstruct object_id wt_oid;\n+\t\tint fd = open(buf.buf, O_RDONLY);\n+\n+\t\tif (fd >= 0 &&\n+\t\t    !index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {\n+\t\t\tif (is_null_oid(oid)) {\n+\t\t\t\toidcpy(oid, &wt_oid);\n+\t\t\t\tuse = 1;\n+\t\t\t} else if (!oidcmp(oid, &wt_oid))\n+\t\t\t\tuse = 1;\n+\t\t}\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn use;\n+}\n+\n+struct working_tree_entry {\n+\tstruct hashmap_entry entry;\n+\tchar path[FLEX_ARRAY];\n+};\n+\n+static int working_tree_entry_cmp(struct working_tree_entry *a,\n+\t\t\t\t  struct working_tree_entry *b, void *keydata)\n+{\n+\treturn strcmp(a->path, b->path);\n+}\n+\n+/*\n+ * The `left` and `right` entries hold paths for the symlinks hashmap,\n+ * and a SHA-1 surrounded by brief text for submodules.\n+ */\n+struct pair_entry {\n+\tstruct hashmap_entry entry;\n+\tchar left[PATH_MAX], right[PATH_MAX];\n+\tconst char path[FLEX_ARRAY];\n+};\n+\n+static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata)\n+{\n+\treturn strcmp(a->path, b->path);\n+}\n+\n+static void add_left_or_right(struct hashmap *map, const char *path,\n+\t\t\t      const char *content, int is_right)\n+{\n+\tstruct pair_entry *e, *existing;\n+\n+\tFLEX_ALLOC_STR(e, path, path);\n+\thashmap_entry_init(e, strhash(path));\n+\texisting = hashmap_get(map, e, NULL);\n+\tif (existing) {\n+\t\tfree(e);\n+\t\te = existing;\n+\t} else {\n+\t\te->left[0] = e->right[0] = '\\0';\n+\t\thashmap_add(map, e);\n+\t}\n+\tstrlcpy(is_right ? e->right : e->left, content, PATH_MAX);\n+}\n+\n+struct path_entry {\n+\tstruct hashmap_entry entry;\n+\tchar path[FLEX_ARRAY];\n+};\n+\n+static int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key)\n+{\n+\treturn strcmp(a->path, key ? key : b->path);\n+}\n+\n+static void changed_files(struct hashmap *result, const char *index_path,\n+\t\t\t  const char *workdir)\n+{\n+\tstruct child_process update_index = CHILD_PROCESS_INIT;\n+\tstruct child_process diff_files = CHILD_PROCESS_INIT;\n+\tstruct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT;\n+\tconst char *git_dir = absolute_path(get_git_dir()), *env[] = {\n+\t\tNULL, NULL\n+\t};\n+\tFILE *fp;\n+\n+\tstrbuf_addf(&index_env, \"GIT_INDEX_FILE=%s\", index_path);\n+\tenv[0] = index_env.buf;\n+\n+\targv_array_pushl(&update_index.args,\n+\t\t\t \"--git-dir\", git_dir, \"--work-tree\", workdir,\n+\t\t\t \"update-index\", \"--really-refresh\", \"-q\",\n+\t\t\t \"--unmerged\", NULL);\n+\tupdate_index.no_stdin = 1;\n+\tupdate_index.no_stdout = 1;\n+\tupdate_index.no_stderr = 1;\n+\tupdate_index.git_cmd = 1;\n+\tupdate_index.use_shell = 0;\n+\tupdate_index.clean_on_exit = 1;\n+\tupdate_index.dir = workdir;\n+\tupdate_index.env = env;\n+\t/* Ignore any errors of update-index */\n+\trun_command(&update_index);\n+\n+\targv_array_pushl(&diff_files.args,\n+\t\t\t \"--git-dir\", git_dir, \"--work-tree\", workdir,\n+\t\t\t \"diff-files\", \"--name-only\", \"-z\", NULL);\n+\tdiff_files.no_stdin = 1;\n+\tdiff_files.git_cmd = 1;\n+\tdiff_files.use_shell = 0;\n+\tdiff_files.clean_on_exit = 1;\n+\tdiff_files.out = -1;\n+\tdiff_files.dir = workdir;\n+\tdiff_files.env = env;\n+\tif (start_command(&diff_files))\n+\t\tdie(\"could not obtain raw diff\");\n+\tfp = xfdopen(diff_files.out, \"r\");\n+\twhile (!strbuf_getline_nul(&buf, fp)) {\n+\t\tstruct path_entry *entry;\n+\t\tFLEX_ALLOC_STR(entry, path, buf.buf);\n+\t\thashmap_entry_init(entry, strhash(buf.buf));\n+\t\thashmap_add(result, entry);\n+\t}\n+\tif (finish_command(&diff_files))\n+\t\tdie(\"diff-files did not exit properly\");\n+\tstrbuf_release(&index_env);\n+\tstrbuf_release(&buf);\n+}\n+\n+static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstrbuf_addstr(&buf, tmpdir);\n+\tremove_dir_recursively(&buf, 0);\n+\tif (exit_code)\n+\t\twarning(_(\"failed: %d\"), exit_code);\n+\texit(exit_code);\n+}\n+\n+static int ensure_leading_directories(char *path)\n+{\n+\tswitch (safe_create_leading_directories(path)) {\n+\t\tcase SCLD_OK:\n+\t\tcase SCLD_EXISTS:\n+\t\t\treturn 0;\n+\t\tdefault:\n+\t\t\treturn error(_(\"could not create leading directories \"\n+\t\t\t\t       \"of '%s'\"), path);\n+\t}\n+}\n+\n+static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,\n+\t\t\tint argc, const char **argv)\n+{\n+\tchar tmpdir[PATH_MAX];\n+\tstruct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;\n+\tstruct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;\n+\tstruct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;\n+\tstruct strbuf wtdir = STRBUF_INIT;\n+\tsize_t ldir_len, rdir_len, wtdir_len;\n+\tstruct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1);\n+\tconst char *workdir, *tmp;\n+\tint ret = 0, i;\n+\tFILE *fp;\n+\tstruct hashmap working_tree_dups, submodules, symlinks2;\n+\tstruct hashmap_iter iter;\n+\tstruct pair_entry *entry;\n+\tenum object_type type;\n+\tunsigned long size;\n+\tstruct index_state wtindex;\n+\tstruct checkout lstate, rstate;\n+\tint rc, flags = RUN_GIT_CMD, err = 0;\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\tconst char *helper_argv[] = { \"difftool--helper\", NULL, NULL, NULL };\n+\tstruct hashmap wt_modified, tmp_modified;\n+\tint indices_loaded = 0;\n+\n+\tworkdir = get_git_work_tree();\n+\n+\t/* Setup temp directories */\n+\ttmp = getenv(\"TMPDIR\");\n+\txsnprintf(tmpdir, sizeof(tmpdir), \"%s/git-difftool.XXXXXX\", tmp ? tmp : \"/tmp\");\n+\tif (!mkdtemp(tmpdir))\n+\t\treturn error(\"could not create '%s'\", tmpdir);\n+\tstrbuf_addf(&ldir, \"%s/left/\", tmpdir);\n+\tstrbuf_addf(&rdir, \"%s/right/\", tmpdir);\n+\tstrbuf_addstr(&wtdir, workdir);\n+\tif (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))\n+\t\tstrbuf_addch(&wtdir, '/');\n+\tmkdir(ldir.buf, 0700);\n+\tmkdir(rdir.buf, 0700);\n+\n+\tmemset(&wtindex, 0, sizeof(wtindex));\n+\n+\tmemset(&lstate, 0, sizeof(lstate));\n+\tlstate.base_dir = ldir.buf;\n+\tlstate.base_dir_len = ldir.len;\n+\tlstate.force = 1;\n+\tmemset(&rstate, 0, sizeof(rstate));\n+\trstate.base_dir = rdir.buf;\n+\trstate.base_dir_len = rdir.len;\n+\trstate.force = 1;\n+\n+\tldir_len = ldir.len;\n+\trdir_len = rdir.len;\n+\twtdir_len = wtdir.len;\n+\n+\thashmap_init(&working_tree_dups,\n+\t\t     (hashmap_cmp_fn)working_tree_entry_cmp, 0);\n+\thashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0);\n+\thashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0);\n+\n+\tchild.no_stdin = 1;\n+\tchild.git_cmd = 1;\n+\tchild.use_shell = 0;\n+\tchild.clean_on_exit = 1;\n+\tchild.dir = prefix;\n+\tchild.out = -1;\n+\targv_array_pushl(&child.args, \"diff\", \"--raw\", \"--no-abbrev\", \"-z\",\n+\t\t\t NULL);\n+\tfor (i = 0; i < argc; i++)\n+\t\targv_array_push(&child.args, argv[i]);\n+\tif (start_command(&child))\n+\t\tdie(\"could not obtain raw diff\");\n+\tfp = xfdopen(child.out, \"r\");\n+\n+\t/* Build index info for left and right sides of the diff */\n+\ti = 0;\n+\twhile (!strbuf_getline_nul(&info, fp)) {\n+\t\tint lmode, rmode;\n+\t\tstruct object_id loid, roid;\n+\t\tchar status;\n+\t\tconst char *src_path, *dst_path;\n+\t\tsize_t src_path_len, dst_path_len;\n+\n+\t\tif (starts_with(info.buf, \"::\"))\n+\t\t\tdie(N_(\"combined diff formats('-c' and '--cc') are \"\n+\t\t\t       \"not supported in\\n\"\n+\t\t\t       \"directory diff mode('-d' and '--dir-diff').\"));\n+\n+\t\tif (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,\n+\t\t\t\t     &status))\n+\t\t\tbreak;\n+\t\tif (strbuf_getline_nul(&lpath, fp))\n+\t\t\tbreak;\n+\t\tsrc_path = lpath.buf;\n+\t\tsrc_path_len = lpath.len;\n+\n+\t\ti++;\n+\t\tif (status != 'C' && status != 'R') {\n+\t\t\tdst_path = src_path;\n+\t\t\tdst_path_len = src_path_len;\n+\t\t} else {\n+\t\t\tif (strbuf_getline_nul(&rpath, fp))\n+\t\t\t\tbreak;\n+\t\t\tdst_path = rpath.buf;\n+\t\t\tdst_path_len = rpath.len;\n+\t\t}\n+\n+\t\tif (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) {\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"Subproject commit %s\",\n+\t\t\t\t    oid_to_hex(&loid));\n+\t\t\tadd_left_or_right(&submodules, src_path, buf.buf, 0);\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"Subproject commit %s\",\n+\t\t\t\t    oid_to_hex(&roid));\n+\t\t\tif (!oidcmp(&loid, &roid))\n+\t\t\t\tstrbuf_addstr(&buf, \"-dirty\");\n+\t\t\tadd_left_or_right(&submodules, dst_path, buf.buf, 1);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (S_ISLNK(lmode)) {\n+\t\t\tchar *content = read_sha1_file(loid.hash, &type, &size);\n+\t\t\tadd_left_or_right(&symlinks2, src_path, content, 0);\n+\t\t\tfree(content);\n+\t\t}\n+\n+\t\tif (S_ISLNK(rmode)) {\n+\t\t\tchar *content = read_sha1_file(roid.hash, &type, &size);\n+\t\t\tadd_left_or_right(&symlinks2, dst_path, content, 1);\n+\t\t\tfree(content);\n+\t\t}\n+\n+\t\tif (lmode && status != 'C') {\n+\t\t\tce->ce_mode = lmode;\n+\t\t\toidcpy(&ce->oid, &loid);\n+\t\t\tstrcpy(ce->name, src_path);\n+\t\t\tce->ce_namelen = src_path_len;\n+\t\t\tif (checkout_entry(ce, &lstate, NULL))\n+\t\t\t\treturn error(\"could not write '%s'\", src_path);\n+\t\t}\n+\n+\t\tif (rmode) {\n+\t\t\tstruct working_tree_entry *entry;\n+\n+\t\t\t/* Avoid duplicate working_tree entries */\n+\t\t\tFLEX_ALLOC_STR(entry, path, dst_path);\n+\t\t\thashmap_entry_init(entry, strhash(dst_path));\n+\t\t\tif (hashmap_get(&working_tree_dups, entry, NULL)) {\n+\t\t\t\tfree(entry);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\thashmap_add(&working_tree_dups, entry);\n+\n+\t\t\tif (!use_wt_file(workdir, dst_path, &roid)) {\n+\t\t\t\tce->ce_mode = rmode;\n+\t\t\t\toidcpy(&ce->oid, &roid);\n+\t\t\t\tstrcpy(ce->name, dst_path);\n+\t\t\t\tce->ce_namelen = dst_path_len;\n+\t\t\t\tif (checkout_entry(ce, &rstate, NULL))\n+\t\t\t\t\treturn error(\"could not write '%s'\",\n+\t\t\t\t\t\t     dst_path);\n+\t\t\t} else if (!is_null_oid(&roid)) {\n+\t\t\t\t/*\n+\t\t\t\t * Changes in the working tree need special\n+\t\t\t\t * treatment since they are not part of the\n+\t\t\t\t * index.\n+\t\t\t\t */\n+\t\t\t\tstruct cache_entry *ce2 =\n+\t\t\t\t\tmake_cache_entry(rmode, roid.hash,\n+\t\t\t\t\t\t\t dst_path, 0, 0);\n+\n+\t\t\t\tadd_index_entry(&wtindex, ce2,\n+\t\t\t\t\t\tADD_CACHE_JUST_APPEND);\n+\n+\t\t\t\tadd_path(&rdir, rdir_len, dst_path);\n+\t\t\t\tif (ensure_leading_directories(rdir.buf))\n+\t\t\t\t\treturn error(\"could not create \"\n+\t\t\t\t\t\t     \"directory for '%s'\",\n+\t\t\t\t\t\t     dst_path);\n+\t\t\t\tadd_path(&wtdir, wtdir_len, dst_path);\n+\t\t\t\tif (symlinks) {\n+\t\t\t\t\tif (symlink(wtdir.buf, rdir.buf)) {\n+\t\t\t\t\t\tret = error_errno(\"could not symlink '%s' to '%s'\", wtdir.buf, rdir.buf);\n+\t\t\t\t\t\tgoto finish;\n+\t\t\t\t\t}\n+\t\t\t\t} else {\n+\t\t\t\t\tstruct stat st;\n+\t\t\t\t\tif (stat(wtdir.buf, &st))\n+\t\t\t\t\t\tst.st_mode = 0644;\n+\t\t\t\t\tif (copy_file(rdir.buf, wtdir.buf,\n+\t\t\t\t\t\t      st.st_mode)) {\n+\t\t\t\t\t\tret = error(\"could not copy '%s' to '%s'\", wtdir.buf, rdir.buf);\n+\t\t\t\t\t\tgoto finish;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (finish_command(&child)) {\n+\t\tret = error(\"error occurred running diff --raw\");\n+\t\tgoto finish;\n+\t}\n+\n+\tif (!i)\n+\t\treturn 0;\n+\n+\t/*\n+\t * Changes to submodules require special treatment.This loop writes a\n+\t * temporary file to both the left and right directories to show the\n+\t * change in the recorded SHA1 for the submodule.\n+\t */\n+\thashmap_iter_init(&submodules, &iter);\n+\twhile ((entry = hashmap_iter_next(&iter))) {\n+\t\tif (*entry->left) {\n+\t\t\tadd_path(&ldir, ldir_len, entry->path);\n+\t\t\tensure_leading_directories(ldir.buf);\n+\t\t\twrite_file(ldir.buf, \"%s\", entry->left);\n+\t\t}\n+\t\tif (*entry->right) {\n+\t\t\tadd_path(&rdir, rdir_len, entry->path);\n+\t\t\tensure_leading_directories(rdir.buf);\n+\t\t\twrite_file(rdir.buf, \"%s\", entry->right);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Symbolic links require special treatment.The standard \"git diff\"\n+\t * shows only the link itself, not the contents of the link target.\n+\t * This loop replicates that behavior.\n+\t */\n+\thashmap_iter_init(&symlinks2, &iter);\n+\twhile ((entry = hashmap_iter_next(&iter))) {\n+\t\tif (*entry->left) {\n+\t\t\tadd_path(&ldir, ldir_len, entry->path);\n+\t\t\tensure_leading_directories(ldir.buf);\n+\t\t\twrite_file(ldir.buf, \"%s\", entry->left);\n+\t\t}\n+\t\tif (*entry->right) {\n+\t\t\tadd_path(&rdir, rdir_len, entry->path);\n+\t\t\tensure_leading_directories(rdir.buf);\n+\t\t\twrite_file(rdir.buf, \"%s\", entry->right);\n+\t\t}\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\tstrbuf_setlen(&ldir, ldir_len);\n+\thelper_argv[1] = ldir.buf;\n+\tstrbuf_setlen(&rdir, rdir_len);\n+\thelper_argv[2] = rdir.buf;\n+\n+\tif (extcmd) {\n+\t\thelper_argv[0] = extcmd;\n+\t\tflags = 0;\n+\t} else\n+\t\tsetenv(\"GIT_DIFFTOOL_DIRDIFF\", \"true\", 1);\n+\trc = run_command_v_opt(helper_argv, flags);\n+\n+\t/*\n+\t * If the diff includes working copy files and those\n+\t * files were modified during the diff, then the changes\n+\t * should be copied back to the working tree.\n+\t * Do not copy back files when symlinks are used and the\n+\t * external tool did not replace the original link with a file.\n+\t *\n+\t * These hashes are loaded lazily since they aren't needed\n+\t * in the common case of --symlinks and the difftool updating\n+\t * files through the symlink.\n+\t */\n+\thashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp,\n+\t\t     wtindex.cache_nr);\n+\thashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp,\n+\t\t     wtindex.cache_nr);\n+\n+\tfor (i = 0; i < wtindex.cache_nr; i++) {\n+\t\tstruct hashmap_entry dummy;\n+\t\tconst char *name = wtindex.cache[i]->name;\n+\t\tstruct stat st;\n+\n+\t\tadd_path(&rdir, rdir_len, name);\n+\t\tif (lstat(rdir.buf, &st))\n+\t\t\tcontinue;\n+\n+\t\tif ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode))\n+\t\t\tcontinue;\n+\n+\t\tif (!indices_loaded) {\n+\t\t\tstatic struct lock_file lock;\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"%s/wtindex\", tmpdir);\n+\t\t\tif (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||\n+\t\t\t    write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {\n+\t\t\t\tret = error(\"could not write %s\", buf.buf);\n+\t\t\t\trollback_lock_file(&lock);\n+\t\t\t\tgoto finish;\n+\t\t\t}\n+\t\t\tchanged_files(&wt_modified, buf.buf, workdir);\n+\t\t\tstrbuf_setlen(&rdir, rdir_len);\n+\t\t\tchanged_files(&tmp_modified, buf.buf, rdir.buf);\n+\t\t\tadd_path(&rdir, rdir_len, name);\n+\t\t\tindices_loaded = 1;\n+\t\t}\n+\n+\t\thashmap_entry_init(&dummy, strhash(name));\n+\t\tif (hashmap_get(&tmp_modified, &dummy, name)) {\n+\t\t\tadd_path(&wtdir, wtdir_len, name);\n+\t\t\tif (hashmap_get(&wt_modified, &dummy, name)) {\n+\t\t\t\twarning(_(\"both files modified: '%s' and '%s'.\"),\n+\t\t\t\t\twtdir.buf, rdir.buf);\n+\t\t\t\twarning(_(\"working tree file has been left.\"));\n+\t\t\t\twarning(\"\");\n+\t\t\t\terr = 1;\n+\t\t\t} else if (unlink(wtdir.buf) ||\n+\t\t\t\t   copy_file(wtdir.buf, rdir.buf, st.st_mode))\n+\t\t\t\twarning_errno(_(\"could not copy '%s' to '%s'\"),\n+\t\t\t\t\t      rdir.buf, wtdir.buf);\n+\t\t}\n+\t}\n+\n+\tif (err) {\n+\t\twarning(_(\"temporary files exist in '%s'.\"), tmpdir);\n+\t\twarning(_(\"you may want to cleanup or recover these.\"));\n+\t\texit(1);\n+\t} else\n+\t\texit_cleanup(tmpdir, rc);\n+\n+finish:\n+\tfree(ce);\n+\tstrbuf_release(&ldir);\n+\tstrbuf_release(&rdir);\n+\tstrbuf_release(&wtdir);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+static int run_file_diff(int prompt, const char *prefix,\n+\t\t\t int argc, const char **argv)\n+{\n+\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\tconst char *env[] = {\n+\t\t\"GIT_PAGER=\", \"GIT_EXTERNAL_DIFF=git-difftool--helper\", NULL,\n+\t\tNULL\n+\t};\n+\tint ret = 0, i;\n+\n+\tif (prompt > 0)\n+\t\tenv[2] = \"GIT_DIFFTOOL_PROMPT=true\";\n+\telse if (!prompt)\n+\t\tenv[2] = \"GIT_DIFFTOOL_NO_PROMPT=true\";\n+\n+\n+\targv_array_push(&args, \"diff\");\n+\tfor (i = 0; i < argc; i++)\n+\t\targv_array_push(&args, argv[i]);\n+\tret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env);\n+\texit(ret);\n+}\n \n /*\n  * NEEDSWORK: this function can go once the legacy-difftool Perl script is\n@@ -41,6 +642,35 @@ static int use_builtin_difftool(void) {\n \n int cmd_difftool(int argc, const char **argv, const char *prefix)\n {\n+\tint use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,\n+\t    tool_help = 0;\n+\tstatic char *difftool_cmd = NULL, *extcmd = NULL;\n+\tstruct option builtin_difftool_options[] = {\n+\t\tOPT_BOOL('g', \"gui\", &use_gui_tool,\n+\t\t\t N_(\"use `diff.guitool` instead of `diff.tool`\")),\n+\t\tOPT_BOOL('d', \"dir-diff\", &dir_diff,\n+\t\t\t N_(\"perform a full-directory diff\")),\n+\t\t{ OPTION_SET_INT, 'y', \"no-prompt\", &prompt, NULL,\n+\t\t\tN_(\"do not prompt before launching a diff tool\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},\n+\t\t{ OPTION_SET_INT, 0, \"prompt\", &prompt, NULL, NULL,\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n+\t\t\tNULL, 1 },\n+\t\tOPT_BOOL(0, \"symlinks\", &symlinks,\n+\t\t\t N_(\"use symlinks in dir-diff mode\")),\n+\t\tOPT_STRING('t', \"tool\", &difftool_cmd, N_(\"<tool>\"),\n+\t\t\t   N_(\"use the specified diff tool\")),\n+\t\tOPT_BOOL(0, \"tool-help\", &tool_help,\n+\t\t\t N_(\"print a list of diff tools that may be used with \"\n+\t\t\t    \"`--tool`\")),\n+\t\tOPT_BOOL(0, \"trust-exit-code\", &trust_exit_code,\n+\t\t\t N_(\"make 'git-difftool' exit when an invoked diff \"\n+\t\t\t    \"tool returns a non - zero exit code\")),\n+\t\tOPT_STRING('x', \"extcmd\", &extcmd, N_(\"<command>\"),\n+\t\t\t   N_(\"specify a custom command for viewing diffs\")),\n+\t\tOPT_END()\n+\t};\n+\n \t/*\n \t * NEEDSWORK: Once the builtin difftool has been tested enough\n \t * and git-legacy-difftool.perl is retired to contrib/, this preamble\n@@ -58,6 +688,46 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)\n \tprefix = setup_git_directory();\n \ttrace_repo_setup(prefix);\n \tsetup_work_tree();\n+\t/* NEEDSWORK: once we no longer spawn anything, remove this */\n+\tsetenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);\n+\tsetenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);\n+\n+\tgit_config(difftool_config, NULL);\n+\tsymlinks = has_symlinks;\n+\n+\targc = parse_options(argc, argv, prefix, builtin_difftool_options,\n+\t\t\t     builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n \n-\tdie(\"TODO\");\n+\tif (tool_help)\n+\t\treturn print_tool_help();\n+\n+\tif (use_gui_tool && diff_gui_tool && *diff_gui_tool)\n+\t\tsetenv(\"GIT_DIFF_TOOL\", diff_gui_tool, 1);\n+\telse if (difftool_cmd) {\n+\t\tif (*difftool_cmd)\n+\t\t\tsetenv(\"GIT_DIFF_TOOL\", difftool_cmd, 1);\n+\t\telse\n+\t\t\tdie(_(\"no <tool> given for --tool=<tool>\"));\n+\t}\n+\n+\tif (extcmd) {\n+\t\tif (*extcmd)\n+\t\t\tsetenv(\"GIT_DIFFTOOL_EXTCMD\", extcmd, 1);\n+\t\telse\n+\t\t\tdie(_(\"no <cmd> given for --extcmd=<cmd>\"));\n+\t}\n+\n+\tsetenv(\"GIT_DIFFTOOL_TRUST_EXIT_CODE\",\n+\t       trust_exit_code ? \"true\" : \"false\", 1);\n+\n+\t/*\n+\t * In directory diff mode, 'git-difftool--helper' is called once\n+\t * to compare the a / b directories. In file diff mode, 'git diff'\n+\t * will invoke a separate instance of 'git-difftool--helper' for\n+\t * each file that changed.\n+\t */\n+\tif (dir_diff)\n+\t\treturn run_dir_diff(extcmd, symlinks, prefix, argc, argv);\n+\treturn run_file_diff(prompt, prefix, argc, argv);\n }\n-- \n2.11.0.windows.3\n\n\n"},{"id":"309524","messageId":"cover.1484668473.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1483373635.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 0/3] Turn the difftool into a builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-01-17T15:54:53Z","receivedAt":"2017-01-17T16:03:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch series converts the difftool from a Perl script into a\nbuiltin, for three reasons:\n\n1. Perl is really not native on Windows. Not only is there a performance\n   penalty to be paid just for running Perl scripts, we also have to deal\n   with the fact that users may have different Perl installations, with\n   different options, and some other Perl installation may decide to set\n   PERL5LIB globally, wreaking havoc with Git for Windows' Perl (which we\n   have to use because almost all other Perl distributions lack the\n   Subversion bindings we need for `git svn`).\n\n2. As the Perl script uses Unix-y paths that are not native to Windows,\n   the Perl interpreter has to go through a POSIX emulation layer (the\n   MSYS2 runtime). This means that paths have to be converted from\n   Unix-y paths to Windows-y paths (and vice versa) whenever crossing\n   the POSIX emulation barrier, leading to quite possibly surprising path\n   translation errors.\n\n3. Perl makes for a rather large reason that Git for Windows' installer\n   weighs in with >30MB. While one Perl script less does not relieve us\n   of that burden, it is one step in the right direction.\n\nChanges since v4:\n\n- skipped the unrelated Coverity-appeasing patch.\n\n- replaced the cross-validation with the Perl script by a patch that\n  retires the Perl script instead.\n\n\nJohannes Schindelin (3):\n  difftool: add a skeleton for the upcoming builtin\n  difftool: implement the functionality in the builtin\n  Retire the scripted difftool\n\n Makefile                                           |   2 +-\n builtin.h                                          |   1 +\n builtin/difftool.c                                 | 692 +++++++++++++++++++++\n .../examples/git-difftool.perl                     |   0\n git.c                                              |   1 +\n t/t7800-difftool.sh                                |  92 +--\n 6 files changed, 741 insertions(+), 47 deletions(-)\n create mode 100644 builtin/difftool.c\n rename git-difftool.perl => contrib/examples/git-difftool.perl (100%)\n\n\nbase-commit: d7dffce1cebde29a0c4b309a79e4345450bf352a\nPublished-As: https://github.com/dscho/git/releases/tag/builtin-difftool-v5\nFetch-It-Via: git fetch https://github.com/dscho/git builtin-difftool-v5\n\nInterdiff vs v4:\n\n diff --git a/.gitignore b/.gitignore\n index 5555ae025b..6722f78f9a 100644\n --- a/.gitignore\n +++ b/.gitignore\n @@ -76,7 +76,6 @@\n  /git-init-db\n  /git-interpret-trailers\n  /git-instaweb\n -/git-legacy-difftool\n  /git-log\n  /git-ls-files\n  /git-ls-remote\n diff --git a/Makefile b/Makefile\n index 8cf5bef034..e9aa6ae57c 100644\n --- a/Makefile\n +++ b/Makefile\n @@ -522,7 +522,6 @@ SCRIPT_LIB += git-sh-setup\n  SCRIPT_LIB += git-sh-i18n\n  \n  SCRIPT_PERL += git-add--interactive.perl\n -SCRIPT_PERL += git-legacy-difftool.perl\n  SCRIPT_PERL += git-archimport.perl\n  SCRIPT_PERL += git-cvsexportcommit.perl\n  SCRIPT_PERL += git-cvsimport.perl\n diff --git a/builtin/difftool.c b/builtin/difftool.c\n index 2115e548a5..42ad9e804a 100644\n --- a/builtin/difftool.c\n +++ b/builtin/difftool.c\n @@ -616,30 +616,6 @@ static int run_file_diff(int prompt, const char *prefix,\n  \texit(ret);\n  }\n  \n -/*\n - * NEEDSWORK: this function can go once the legacy-difftool Perl script is\n - * retired.\n - *\n - * We intentionally avoid reading the config directly here, to avoid messing up\n - * the GIT_* environment variables when we need to fall back to exec()ing the\n - * Perl script.\n - */\n -static int use_builtin_difftool(void) {\n -\tstruct child_process cp = CHILD_PROCESS_INIT;\n -\tstruct strbuf out = STRBUF_INIT;\n -\tint ret;\n -\n -\targv_array_pushl(&cp.args,\n -\t\t\t \"config\", \"--bool\", \"difftool.usebuiltin\", NULL);\n -\tcp.git_cmd = 1;\n -\tif (capture_command(&cp, &out, 6))\n -\t\treturn 0;\n -\tstrbuf_trim(&out);\n -\tret = !strcmp(\"true\", out.buf);\n -\tstrbuf_release(&out);\n -\treturn ret;\n -}\n -\n  int cmd_difftool(int argc, const char **argv, const char *prefix)\n  {\n  \tint use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,\n @@ -671,23 +647,6 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)\n  \t\tOPT_END()\n  \t};\n  \n -\t/*\n -\t * NEEDSWORK: Once the builtin difftool has been tested enough\n -\t * and git-legacy-difftool.perl is retired to contrib/, this preamble\n -\t * can be removed.\n -\t */\n -\tif (!use_builtin_difftool()) {\n -\t\tconst char *path = mkpath(\"%s/git-legacy-difftool\",\n -\t\t\t\t\t  git_exec_path());\n -\n -\t\tif (sane_execvp(path, (char **)argv) < 0)\n -\t\t\tdie_errno(\"could not exec %s\", path);\n -\n -\t\treturn 0;\n -\t}\n -\tprefix = setup_git_directory();\n -\ttrace_repo_setup(prefix);\n -\tsetup_work_tree();\n  \t/* NEEDSWORK: once we no longer spawn anything, remove this */\n  \tsetenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);\n  \tsetenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);\n diff --git a/git-legacy-difftool.perl b/contrib/examples/git-difftool.perl\n similarity index 100%\n rename from git-legacy-difftool.perl\n rename to contrib/examples/git-difftool.perl\n diff --git a/exec_cmd.c b/exec_cmd.c\n index 587bd7eb48..19ac2146d0 100644\n --- a/exec_cmd.c\n +++ b/exec_cmd.c\n @@ -65,7 +65,6 @@ void git_set_argv_exec_path(const char *exec_path)\n  const char *git_exec_path(void)\n  {\n  \tconst char *env;\n -\tstatic char *system_exec_path;\n  \n  \tif (argv_exec_path)\n  \t\treturn argv_exec_path;\n @@ -75,9 +74,7 @@ const char *git_exec_path(void)\n  \t\treturn env;\n  \t}\n  \n -\tif (!system_exec_path)\n -\t\tsystem_exec_path = system_path(GIT_EXEC_PATH);\n -\treturn system_exec_path;\n +\treturn system_path(GIT_EXEC_PATH);\n  }\n  \n  static void add_path(struct strbuf *out, const char *path)\n diff --git a/git.c b/git.c\n index c58181e5ef..bd4d668a21 100644\n --- a/git.c\n +++ b/git.c\n @@ -424,12 +424,7 @@ static struct cmd_struct commands[] = {\n  \t{ \"diff-files\", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },\n  \t{ \"diff-index\", cmd_diff_index, RUN_SETUP },\n  \t{ \"diff-tree\", cmd_diff_tree, RUN_SETUP },\n -\t/*\n -\t * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in\n -\t * builtin/difftool.c has been removed, this entry should be changed to\n -\t * RUN_SETUP | NEED_WORK_TREE\n -\t */\n -\t{ \"difftool\", cmd_difftool },\n +\t{ \"difftool\", cmd_difftool, RUN_SETUP | NEED_WORK_TREE },\n  \t{ \"fast-export\", cmd_fast_export, RUN_SETUP },\n  \t{ \"fetch\", cmd_fetch, RUN_SETUP },\n  \t{ \"fetch-pack\", cmd_fetch_pack, RUN_SETUP },\n diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh\n index 273ab55723..aa0ef02597 100755\n --- a/t/t7800-difftool.sh\n +++ b/t/t7800-difftool.sh\n @@ -23,24 +23,8 @@ prompt_given ()\n  \ttest \"$prompt\" = \"Launch 'test-tool' [Y/n]? branch\"\n  }\n  \n -for use_builtin_difftool in false true\n -do\n -\n -test_expect_success 'verify we are running the correct difftool' '\n -\tif test true = '$use_builtin_difftool'\n -\tthen\n -\t\ttest_must_fail ok=129 git difftool -h >help &&\n -\t\tgrep \"g, --gui\" help\n -\telse\n -\t\tgit difftool -h >help &&\n -\t\tgrep \"g|--gui\" help\n -\tfi\n -'\n -\n -# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.\n -\n  # Create a file on master and change it on branch\n -test_expect_success PERL 'setup' '\n +test_expect_success 'setup' '\n  \techo master >file &&\n  \tgit add file &&\n  \tgit commit -m \"added file\" &&\n @@ -52,7 +36,7 @@ test_expect_success PERL 'setup' '\n  '\n  \n  # Configure a custom difftool.<tool>.cmd and use it\n -test_expect_success PERL 'custom commands' '\n +test_expect_success 'custom commands' '\n  \tdifftool_test_setup &&\n  \ttest_config difftool.test-tool.cmd \"cat \\\"\\$REMOTE\\\"\" &&\n  \techo master >expect &&\n @@ -65,21 +49,21 @@ test_expect_success PERL 'custom commands' '\n  \ttest_cmp expect actual\n  '\n  \n -test_expect_success PERL 'custom tool commands override built-ins' '\n +test_expect_success 'custom tool commands override built-ins' '\n  \ttest_config difftool.vimdiff.cmd \"cat \\\"\\$REMOTE\\\"\" &&\n  \techo master >expect &&\n  \tgit difftool --tool vimdiff --no-prompt branch >actual &&\n  \ttest_cmp expect actual\n  '\n  \n -test_expect_success PERL 'difftool ignores bad --tool values' '\n +test_expect_success 'difftool ignores bad --tool values' '\n  \t: >expect &&\n  \ttest_must_fail \\\n  \t\tgit difftool --no-prompt --tool=bad-tool branch >actual &&\n  \ttest_cmp expect actual\n  '\n  \n -test_expect_success PERL 'difftool forwards arguments to diff' '\n +test_expect_success 'difftool forwards arguments to diff' '\n  \tdifftool_test_setup &&\n  \t>for-diff &&\n  \tgit add for-diff &&\n @@ -92,40 +76,40 @@ test_expect_success PERL 'difftool forwards arguments to diff' '\n  \trm for-diff\n  '\n  \n -test_expect_success PERL 'difftool ignores exit code' '\n +test_expect_success 'difftool ignores exit code' '\n  \ttest_config difftool.error.cmd false &&\n  \tgit difftool -y -t error branch\n  '\n  \n -test_expect_success PERL 'difftool forwards exit code with --trust-exit-code' '\n +test_expect_success 'difftool forwards exit code with --trust-exit-code' '\n  \ttest_config difftool.error.cmd false &&\n  \ttest_must_fail git difftool -y --trust-exit-code -t error branch\n  '\n  \n -test_expect_success PERL 'difftool forwards exit code with --trust-exit-code for built-ins' '\n +test_expect_success 'difftool forwards exit code with --trust-exit-code for built-ins' '\n  \ttest_config difftool.vimdiff.path false &&\n  \ttest_must_fail git difftool -y --trust-exit-code -t vimdiff branch\n  '\n  \n -test_expect_success PERL 'difftool honors difftool.trustExitCode = true' '\n +test_expect_success 'difftool honors difftool.trustExitCode = true' '\n  \ttest_config difftool.error.cmd false &&\n  \ttest_config difftool.trustExitCode true &&\n  \ttest_must_fail git difftool -y -t error branch\n  '\n  \n -test_expect_success PERL 'difftool honors difftool.trustExitCode = false' '\n +test_expect_success 'difftool honors difftool.trustExitCode = false' '\n  \ttest_config difftool.error.cmd false &&\n  \ttest_config difftool.trustExitCode false &&\n  \tgit difftool -y -t error branch\n  '\n  \n -test_expect_success PERL 'difftool ignores exit code with --no-trust-exit-code' '\n +test_expect_success 'difftool ignores exit code with --no-trust-exit-code' '\n  \ttest_config difftool.error.cmd false &&\n  \ttest_config difftool.trustExitCode true &&\n  \tgit difftool -y --no-trust-exit-code -t error branch\n  '\n  \n -test_expect_success PERL 'difftool stops on error with --trust-exit-code' '\n +test_expect_success 'difftool stops on error with --trust-exit-code' '\n  \ttest_when_finished \"rm -f for-diff .git/fail-right-file\" &&\n  \ttest_when_finished \"git reset -- for-diff\" &&\n  \twrite_script .git/fail-right-file <<-\\EOF &&\n @@ -140,13 +124,13 @@ test_expect_success PERL 'difftool stops on error with --trust-exit-code' '\n  \ttest_cmp expect actual\n  '\n  \n -test_expect_success PERL 'difftool honors exit status if command not found' '\n +test_expect_success 'difftool honors exit status if command not found' '\n  \ttest_config difftool.nonexistent.cmd i-dont-exist &&\n  \ttest_config difftool.trustExitCode false &&\n  \ttest_must_fail git difftool -y -t nonexistent branch\n  '\n  \n -test_expect_success PERL 'difftool honors --gui' '\n +test_expect_success 'difftool honors --gui' '\n  \tdifftool_test_setup &&\n  \ttest_config merge.tool bogus-tool &&\n  \ttest_config diff.tool bogus-tool &&\n @@ -157,7 +141,7 @@ test_expect_success PERL 'difftool honors --gui' '\n  \ttest_cmp expect actual\n  '\n  \n -test_expect_success PERL 'difftool --gui last setting wins' '\n +test_expect_success 'difftool --gui last setting wins' '\n  \tdifftool_test_setup &&\n  \t: >expect &&\n  \tgit difftool --no-prompt --gui --no-gui >actual &&\n @@ -171,7 +155,7 @@ test_expect_success PERL 'difftool --gui last setting wins' '\n  \ttest_cmp expect actual\n  '\n  \n -test_expect_success PERL 'difftool --gui works without configured diff.guitool' '\n +test_expect_success 'difftool --gui works without configured diff.guitool' '\n  \tdifftool_test_setup &&\n  \techo branch >expect &&\n  \tgit difftool --no-prompt --gui branch >actual &&\n @@ -179,7 +163,7 @@ test_expect_success PERL 'difftool --gui works without configured diff.guitool'\n  '\n  \n  # Specify the diff tool using $GIT_DIFF_TOOL\n -test_expect_success PERL 'GIT_DIFF_TOOL variable' '\n +test_expect_success 'GIT_DIFF_TOOL variable' '\n  \tdifftool_test_setup &&\n  \tgit config --unset diff.tool &&\n  \techo branch >expect &&\n @@ -189,7 +173,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL variable' '\n  \n  # Test the $GIT_*_TOOL variables and ensure\n  # that $GIT_DIFF_TOOL always wins unless --tool is specified\n -test_expect_success PERL 'GIT_DIFF_TOOL overrides' '\n +test_expect_success 'GIT_DIFF_TOOL overrides' '\n  \tdifftool_test_setup &&\n  \ttest_config diff.tool bogus-tool &&\n  \ttest_config merge.tool bogus-tool &&\n @@ -207,7 +191,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL overrides' '\n  \n  # Test that we don't have to pass --no-prompt to difftool\n  # when $GIT_DIFFTOOL_NO_PROMPT is true\n -test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' '\n +test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' '\n  \tdifftool_test_setup &&\n  \techo branch >expect &&\n  \tGIT_DIFFTOOL_NO_PROMPT=true git difftool branch >actual &&\n @@ -216,7 +200,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' '\n  \n  # git-difftool supports the difftool.prompt variable.\n  # Test that GIT_DIFFTOOL_PROMPT can override difftool.prompt = false\n -test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' '\n +test_expect_success 'GIT_DIFFTOOL_PROMPT variable' '\n  \tdifftool_test_setup &&\n  \ttest_config difftool.prompt false &&\n  \techo >input &&\n @@ -226,7 +210,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' '\n  '\n  \n  # Test that we don't have to pass --no-prompt when difftool.prompt is false\n -test_expect_success PERL 'difftool.prompt config variable is false' '\n +test_expect_success 'difftool.prompt config variable is false' '\n  \tdifftool_test_setup &&\n  \ttest_config difftool.prompt false &&\n  \techo branch >expect &&\n @@ -235,7 +219,7 @@ test_expect_success PERL 'difftool.prompt config variable is false' '\n  '\n  \n  # Test that we don't have to pass --no-prompt when mergetool.prompt is false\n -test_expect_success PERL 'difftool merge.prompt = false' '\n +test_expect_success 'difftool merge.prompt = false' '\n  \tdifftool_test_setup &&\n  \ttest_might_fail git config --unset difftool.prompt &&\n  \ttest_config mergetool.prompt false &&\n @@ -245,7 +229,7 @@ test_expect_success PERL 'difftool merge.prompt = false' '\n  '\n  \n  # Test that the -y flag can override difftool.prompt = true\n -test_expect_success PERL 'difftool.prompt can overridden with -y' '\n +test_expect_success 'difftool.prompt can overridden with -y' '\n  \tdifftool_test_setup &&\n  \ttest_config difftool.prompt true &&\n  \techo branch >expect &&\n @@ -254,7 +238,7 @@ test_expect_success PERL 'difftool.prompt can overridden with -y' '\n  '\n  \n  # Test that the --prompt flag can override difftool.prompt = false\n -test_expect_success PERL 'difftool.prompt can overridden with --prompt' '\n +test_expect_success 'difftool.prompt can overridden with --prompt' '\n  \tdifftool_test_setup &&\n  \ttest_config difftool.prompt false &&\n  \techo >input &&\n @@ -264,7 +248,7 @@ test_expect_success PERL 'difftool.prompt can overridden with --prompt' '\n  '\n  \n  # Test that the last flag passed on the command-line wins\n -test_expect_success PERL 'difftool last flag wins' '\n +test_expect_success 'difftool last flag wins' '\n  \tdifftool_test_setup &&\n  \techo branch >expect &&\n  \tgit difftool --prompt --no-prompt branch >actual &&\n @@ -277,7 +261,7 @@ test_expect_success PERL 'difftool last flag wins' '\n  \n  # git-difftool falls back to git-mergetool config variables\n  # so test that behavior here\n -test_expect_success PERL 'difftool + mergetool config variables' '\n +test_expect_success 'difftool + mergetool config variables' '\n  \ttest_config merge.tool test-tool &&\n  \ttest_config mergetool.test-tool.cmd \"cat \\$LOCAL\" &&\n  \techo branch >expect &&\n @@ -291,49 +275,49 @@ test_expect_success PERL 'difftool + mergetool config variables' '\n  \ttest_cmp expect actual\n  '\n  \n -test_expect_success PERL 'difftool.<tool>.path' '\n +test_expect_success 'difftool.<tool>.path' '\n  \ttest_config difftool.tkdiff.path echo &&\n  \tgit difftool --tool=tkdiff --no-prompt branch >output &&\n  \tlines=$(grep file output | wc -l) &&\n  \ttest \"$lines\" -eq 1\n  '\n  \n -test_expect_success PERL 'difftool --extcmd=cat' '\n +test_expect_success 'difftool --extcmd=cat' '\n  \techo branch >expect &&\n  \techo master >>expect &&\n  \tgit difftool --no-prompt --extcmd=cat branch >actual &&\n  \ttest_cmp expect actual\n  '\n  \n -test_expect_success PERL 'difftool --extcmd cat' '\n +test_expect_success 'difftool --extcmd cat' '\n  \techo branch >expect &&\n  \techo master >>expect &&\n  \tgit difftool --no-prompt --extcmd=cat branch >actual &&\n  \ttest_cmp expect actual\n  '\n  \n -test_expect_success PERL 'difftool -x cat' '\n +test_expect_success 'difftool -x cat' '\n  \techo branch >expect &&\n  \techo master >>expect &&\n  \tgit difftool --no-prompt -x cat branch >actual &&\n  \ttest_cmp expect actual\n  '\n  \n -test_expect_success PERL 'difftool --extcmd echo arg1' '\n +test_expect_success 'difftool --extcmd echo arg1' '\n  \techo file >expect &&\n  \tgit difftool --no-prompt \\\n  \t\t--extcmd sh\\ -c\\ \\\"echo\\ \\$1\\\" branch >actual &&\n  \ttest_cmp expect actual\n  '\n  \n -test_expect_success PERL 'difftool --extcmd cat arg1' '\n +test_expect_success 'difftool --extcmd cat arg1' '\n  \techo master >expect &&\n  \tgit difftool --no-prompt \\\n  \t\t--extcmd sh\\ -c\\ \\\"cat\\ \\$1\\\" branch >actual &&\n  \ttest_cmp expect actual\n  '\n  \n -test_expect_success PERL 'difftool --extcmd cat arg2' '\n +test_expect_success 'difftool --extcmd cat arg2' '\n  \techo branch >expect &&\n  \tgit difftool --no-prompt \\\n  \t\t--extcmd sh\\ -c\\ \\\"cat\\ \\$2\\\" branch >actual &&\n @@ -341,7 +325,7 @@ test_expect_success PERL 'difftool --extcmd cat arg2' '\n  '\n  \n  # Create a second file on master and a different version on branch\n -test_expect_success PERL 'setup with 2 files different' '\n +test_expect_success 'setup with 2 files different' '\n  \techo m2 >file2 &&\n  \tgit add file2 &&\n  \tgit commit -m \"added file2\" &&\n @@ -353,7 +337,7 @@ test_expect_success PERL 'setup with 2 files different' '\n  \tgit checkout master\n  '\n  \n -test_expect_success PERL 'say no to the first file' '\n +test_expect_success 'say no to the first file' '\n  \t(echo n && echo) >input &&\n  \tgit difftool -x cat branch <input >output &&\n  \tgrep m2 output &&\n @@ -362,7 +346,7 @@ test_expect_success PERL 'say no to the first file' '\n  \t! grep branch output\n  '\n  \n -test_expect_success PERL 'say no to the second file' '\n +test_expect_success 'say no to the second file' '\n  \t(echo && echo n) >input &&\n  \tgit difftool -x cat branch <input >output &&\n  \tgrep master output &&\n @@ -371,7 +355,7 @@ test_expect_success PERL 'say no to the second file' '\n  \t! grep br2 output\n  '\n  \n -test_expect_success PERL 'ending prompt input with EOF' '\n +test_expect_success 'ending prompt input with EOF' '\n  \tgit difftool -x cat branch </dev/null >output &&\n  \t! grep master output &&\n  \t! grep branch output &&\n @@ -379,12 +363,12 @@ test_expect_success PERL 'ending prompt input with EOF' '\n  \t! grep br2 output\n  '\n  \n -test_expect_success PERL 'difftool --tool-help' '\n +test_expect_success 'difftool --tool-help' '\n  \tgit difftool --tool-help >output &&\n  \tgrep tool output\n  '\n  \n -test_expect_success PERL 'setup change in subdirectory' '\n +test_expect_success 'setup change in subdirectory' '\n  \tgit checkout master &&\n  \tmkdir sub &&\n  \techo master >sub/sub &&\n @@ -398,11 +382,11 @@ test_expect_success PERL 'setup change in subdirectory' '\n  '\n  \n  run_dir_diff_test () {\n -\ttest_expect_success PERL \"$1 --no-symlinks\" \"\n +\ttest_expect_success \"$1 --no-symlinks\" \"\n  \t\tsymlinks=--no-symlinks &&\n  \t\t$2\n  \t\"\n -\ttest_expect_success PERL,SYMLINKS \"$1 --symlinks\" \"\n +\ttest_expect_success SYMLINKS \"$1 --symlinks\" \"\n  \t\tsymlinks=--symlinks &&\n  \t\t$2\n  \t\"\n @@ -524,7 +508,7 @@ do\n  done >actual\n  EOF\n  \n -test_expect_success PERL,SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' '\n +test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' '\n  \tcat >expect <<-EOF &&\n  \tfile\n  \t$PWD/file\n @@ -561,7 +545,7 @@ write_script modify-file <<\\EOF\n  echo \"new content\" >file\n  EOF\n  \n -test_expect_success PERL 'difftool --no-symlinks does not overwrite working tree file ' '\n +test_expect_success 'difftool --no-symlinks does not overwrite working tree file ' '\n  \techo \"orig content\" >file &&\n  \tgit difftool --dir-diff --no-symlinks --extcmd \"$PWD/modify-file\" branch &&\n  \techo \"new content\" >expect &&\n @@ -574,7 +558,7 @@ echo \"tmp content\" >\"$2/file\" &&\n  echo \"$2\" >tmpdir\n  EOF\n  \n -test_expect_success PERL 'difftool --no-symlinks detects conflict ' '\n +test_expect_success 'difftool --no-symlinks detects conflict ' '\n  \t(\n  \t\tTMPDIR=$TRASH_DIRECTORY &&\n  \t\texport TMPDIR &&\n @@ -587,7 +571,7 @@ test_expect_success PERL 'difftool --no-symlinks detects conflict ' '\n  \t)\n  '\n  \n -test_expect_success PERL 'difftool properly honors gitlink and core.worktree' '\n +test_expect_success 'difftool properly honors gitlink and core.worktree' '\n  \tgit submodule add ./. submod/ule &&\n  \ttest_config -C submod/ule diff.tool checktrees &&\n  \ttest_config -C submod/ule difftool.checktrees.cmd '\\''\n @@ -601,7 +585,7 @@ test_expect_success PERL 'difftool properly honors gitlink and core.worktree' '\n  \t)\n  '\n  \n -test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' '\n +test_expect_success SYMLINKS 'difftool --dir-diff symlinked directories' '\n  \tgit init dirlinks &&\n  \t(\n  \t\tcd dirlinks &&\n @@ -620,17 +604,4 @@ test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' '\n  \t)\n  '\n  \n -test true != $use_builtin_difftool || break\n -\n -test_expect_success 'tear down for re-run' '\n -\trm -rf * .[a-z]* &&\n -\tgit init\n -'\n -\n -# run as builtin difftool now\n -GIT_CONFIG_PARAMETERS=\"'difftool.usebuiltin=true'\"\n -export GIT_CONFIG_PARAMETERS\n -\n -done\n -\n  test_done\n\n-- \n2.11.0.windows.3\n\n"},{"id":"309561","messageId":"xmqqwpdtcr02.fsf@gitster.mtv.corp.google.com","threadId":"44532","inReplyTo":"cover.1484668473.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v5 0/3] Turn the difftool into a builtin","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-01-17T21:31:57Z","receivedAt":"2017-01-17T21:34:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> - replaced the cross-validation with the Perl script by a patch that\n>   retires the Perl script instead.\n\nYup.  That makes things a lot simpler.  While we try to be careful\nduring major rewrite, we usually do not go extra careful to leave\ntwo competing implementations in-tree.\n\nWill replace js/difftool-builtin topic.  Thanks.\n"},{"id":"309566","messageId":"xmqqk29tcqb8.fsf@gitster.mtv.corp.google.com","threadId":"44532","inReplyTo":"8238bba389c031b091a37396fed43cac94d944e7.1484668473.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v5 3/3] Retire the scripted difftool","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-01-17T21:46:51Z","receivedAt":"2017-01-17T21:47:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> It served its purpose, but now we have a builtin difftool. Time for the\n> Perl script to enjoy Florida.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n\nThe endgame makes a lot of sense.  Both in the cover letter and in\nthe previous patch you talk about having both in the released\nversion, so do you want this step to proceed slower than the other\ntwo?  I.e. merge all three to 'next' but graduate only the first two\nto 'master' and after a while make this last step graduate?\n\n"},{"id":"309620","messageId":"alpine.DEB.2.20.1701181332230.3469@virtualbox","threadId":"44532","inReplyTo":"xmqqk29tcqb8.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v5 3/3] Retire the scripted difftool","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-01-18T12:33:39Z","receivedAt":"2017-01-18T12:33:56Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Tue, 17 Jan 2017, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> > It served its purpose, but now we have a builtin difftool. Time for the\n> > Perl script to enjoy Florida.\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> \n> The endgame makes a lot of sense.  Both in the cover letter and in\n> the previous patch you talk about having both in the released\n> version, so do you want this step to proceed slower than the other\n> two?\n\nI did proceed that slowly. Already three Git for Windows versions have\nbeen released with both.\n\nBut I submitted this iteration with this patch, so my intent is clearly to\nretire the Perl script.\n\nCiao,\nJohannes\n"},{"id":"309648","messageId":"xmqqbmv49o3s.fsf@gitster.mtv.corp.google.com","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1701181332230.3469@virtualbox","subject":"Re: [PATCH v5 3/3] Retire the scripted difftool","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-01-18T19:15:03Z","receivedAt":"2017-01-18T19:16:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Hi Junio,\n>\n> On Tue, 17 Jan 2017, Junio C Hamano wrote:\n>\n>> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n>> \n>> > It served its purpose, but now we have a builtin difftool. Time for the\n>> > Perl script to enjoy Florida.\n>> >\n>> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n>> > ---\n>> \n>> The endgame makes a lot of sense.  Both in the cover letter and in\n>> the previous patch you talk about having both in the released\n>> version, so do you want this step to proceed slower than the other\n>> two?\n>\n> I did proceed that slowly. Already three Git for Windows versions have\n> been released with both.\n>\n> But I submitted this iteration with this patch, so my intent is clearly to\n> retire the Perl script.\n\nOk, I was mostly reacting to 2/3 while I am reading it:\n\n    The reason: this new, experimental, builtin difftool will be shipped as\n    part of Git for Windows v2.11.0, to allow for easier large-scale\n    testing, but of course as an opt-in feature.\n\nas there is no longer an opportunity to participate in this opt-in\ntesting, unless 3/3 is special cased and delayed.\n\n\n"},{"id":"309687","messageId":"20170118223817.GD10641@google.com","threadId":"44532","inReplyTo":"20161206183527.t6rjkkff7fxv5i5c@sigill.intra.peff.net","subject":"Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin","fromName":"Brandon Williams","fromEmail":"bmwill@google.com","sentAt":"2017-01-18T22:38:17Z","receivedAt":"2017-01-18T22:39:48Z","isPatch":true,"sender":{"key":"bwilliams.eng@gmail.com","avatar":null},"body":"On 12/06, Jeff King wrote:\n> On Tue, Dec 06, 2016 at 10:22:21AM -0800, Stefan Beller wrote:\n> \n> > >> Maybe even go a step further and say that the config code needs a context\n> > >> \"object\".\n> > >\n> > > If I were writing git from scratch, I'd consider making a \"struct\n> > > repository\" object. I'm not sure how painful it would be to retro-fit it\n> > > at this point.\n> > \n> > Would it be possible to introduce \"the repo\" struct similar to \"the index\"\n> > in cache.h?\n> > \n> > From a submodule perspective I would very much welcome this\n> > object oriented approach to repositories.\n> \n> I think it may be more complicated, because there's some implicit global\n> state in \"the repo\", like where files are relative to our cwd. All of\n> those low-level functions would have to start caring about which repo\n> we're talking about so they can prefix the appropriate working tree\n> path, etc.\n> \n> For some operations that would be fine, but there are things that would\n> subtly fail for submodules. I'm thinking we'd end up with some code\n> state like:\n> \n>   /* finding a repo does not modify global state; good */\n>   struct repository *repo = repo_discover(\".\");\n> \n>   /* obvious repo-level operations like looking up refs can be done with\n>    * a repository object; good */\n>   repo_for_each_ref(repo, callback, NULL);\n> \n>   /*\n>    * \"enter\" the repo so that we are at the top-level of the working\n>    * tree, etc. After this you can actually look at the index without\n>    * things breaking.\n>    */\n>   repo_enter(repo);\n> \n> That would be enough to implement a lot of submodule-level stuff, but it\n> would break pretty subtly as soon as you asked the submodule about its\n> working tree. The solution is to make everything that accesses the\n> working tree aware of the idea of a working tree root besides the cwd.\n> But that's a pretty invasive change.\n> \n> -Peff\n\nSome other challenges would be how to address people setting environment\nvariables like GIT_DIR that indicate the location of a repositories git\ndirectory, which wouldn't work if you have multiple repos open.\n\nI do agree that having a repo object of some sort would aid in\nsimplifying submodule operations but may require too many invasive\nchanges to basic low-level functions.\n\n-- \nBrandon Williams\n"},{"id":"309734","messageId":"alpine.DEB.2.20.1701191730040.3469@virtualbox","threadId":"44532","inReplyTo":"xmqqbmv49o3s.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v5 3/3] Retire the scripted difftool","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-01-19T16:30:48Z","receivedAt":"2017-01-19T16:34:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Wed, 18 Jan 2017, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > Hi Junio,\n> >\n> > On Tue, 17 Jan 2017, Junio C Hamano wrote:\n> >\n> >> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> >> \n> >> > It served its purpose, but now we have a builtin difftool. Time for the\n> >> > Perl script to enjoy Florida.\n> >> >\n> >> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> >> > ---\n> >> \n> >> The endgame makes a lot of sense.  Both in the cover letter and in\n> >> the previous patch you talk about having both in the released\n> >> version, so do you want this step to proceed slower than the other\n> >> two?\n> >\n> > I did proceed that slowly. Already three Git for Windows versions have\n> > been released with both.\n> >\n> > But I submitted this iteration with this patch, so my intent is clearly to\n> > retire the Perl script.\n> \n> Ok, I was mostly reacting to 2/3 while I am reading it:\n> \n>     The reason: this new, experimental, builtin difftool will be shipped as\n>     part of Git for Windows v2.11.0, to allow for easier large-scale\n>     testing, but of course as an opt-in feature.\n> \n> as there is no longer an opportunity to participate in this opt-in\n> testing, unless 3/3 is special cased and delayed.\n\nYep, as Git for Windows v2.11.0 is yesteryear's news, it was probably\nobvious to you that I simply failed to spot and fix that oversight.\n\nCiao,\nJohannes\n"},{"id":"309742","messageId":"xmqqlgu72asr.fsf@gitster.mtv.corp.google.com","threadId":"44532","inReplyTo":"alpine.DEB.2.20.1701191730040.3469@virtualbox","subject":"Re: [PATCH v5 3/3] Retire the scripted difftool","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-01-19T17:56:36Z","receivedAt":"2017-01-19T18:13:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n>> Ok, I was mostly reacting to 2/3 while I am reading it:\n>> \n>>     The reason: this new, experimental, builtin difftool will be shipped as\n>>     part of Git for Windows v2.11.0, to allow for easier large-scale\n>>     testing, but of course as an opt-in feature.\n>> \n>> as there is no longer an opportunity to participate in this opt-in\n>> testing, unless 3/3 is special cased and delayed.\n>\n> Yep, as Git for Windows v2.11.0 is yesteryear's news, it was probably\n> obvious to you that I simply failed to spot and fix that oversight.\n\nOK, if you want to tweak log message of either 2/3 or 3/3 to\ncorrect, there is still time, as they are still outside 'next'.  \n\nI am hoping we can merge it and others to 'next' by the end of the\nweek, though.\n\n\n"},{"id":"309771","messageId":"cover.1484857756.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1484668473.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 0/3] Turn the difftool into a builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-01-19T20:30:27Z","receivedAt":"2017-01-19T20:32:11Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch series converts the difftool from a Perl script into a\nbuiltin, for three reasons:\n\n1. Perl is really not native on Windows. Not only is there a performance\n   penalty to be paid just for running Perl scripts, we also have to deal\n   with the fact that users may have different Perl installations, with\n   different options, and some other Perl installation may decide to set\n   PERL5LIB globally, wreaking havoc with Git for Windows' Perl (which we\n   have to use because almost all other Perl distributions lack the\n   Subversion bindings we need for `git svn`).\n\n2. As the Perl script uses Unix-y paths that are not native to Windows,\n   the Perl interpreter has to go through a POSIX emulation layer (the\n   MSYS2 runtime). This means that paths have to be converted from\n   Unix-y paths to Windows-y paths (and vice versa) whenever crossing\n   the POSIX emulation barrier, leading to quite possibly surprising path\n   translation errors.\n\n3. Perl makes for a rather large reason that Git for Windows' installer\n   weighs in with >30MB. While one Perl script less does not relieve us\n   of that burden, it is one step in the right direction.\n\nChanges since v5:\n\n- reworded the commit message of 2/3 to account for the change in v4\n  where we no longer keep both scripted and builtin difftool working\n  (with the switch difftool.useBuiltin deciding which one is used).\n\n\nJohannes Schindelin (3):\n  difftool: add a skeleton for the upcoming builtin\n  difftool: implement the functionality in the builtin\n  Retire the scripted difftool\n\n Makefile                                           |   2 +-\n builtin.h                                          |   1 +\n builtin/difftool.c                                 | 692 +++++++++++++++++++++\n .../examples/git-difftool.perl                     |   0\n git.c                                              |   1 +\n t/t7800-difftool.sh                                |  92 +--\n 6 files changed, 741 insertions(+), 47 deletions(-)\n create mode 100644 builtin/difftool.c\n rename git-difftool.perl => contrib/examples/git-difftool.perl (100%)\n\n\nbase-commit: ffac48d093d4b518a0cc0e8bf1b7cb53e0c3d7a2\nPublished-As: https://github.com/dscho/git/releases/tag/builtin-difftool-v6\nFetch-It-Via: git fetch https://github.com/dscho/git builtin-difftool-v6\n\n-- \n2.11.0.windows.3\n\n"},{"id":"309772","messageId":"0f51573f770cbef32d71b188c3bb0b941529eaed.1484857756.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1484857756.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 1/3] difftool: add a skeleton for the upcoming builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-01-19T20:30:32Z","receivedAt":"2017-01-19T20:32:14Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This adds a builtin difftool that still falls back to the legacy Perl\nversion, which has been renamed to `legacy-difftool`.\n\nThe idea is that the new, experimental, builtin difftool immediately hands\noff to the legacy difftool for now, unless the config variable\ndifftool.useBuiltin is set to true.\n\nThis feature flag will be used in the upcoming Git for Windows v2.11.0\nrelease, to allow early testers to opt-in to use the builtin difftool and\nflesh out any bugs.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n .gitignore                                    |  1 +\n Makefile                                      |  3 +-\n builtin.h                                     |  1 +\n builtin/difftool.c                            | 63 +++++++++++++++++++++++++++\n git-difftool.perl => git-legacy-difftool.perl |  0\n git.c                                         |  6 +++\n t/t7800-difftool.sh                           |  2 +\n 7 files changed, 75 insertions(+), 1 deletion(-)\n create mode 100644 builtin/difftool.c\n rename git-difftool.perl => git-legacy-difftool.perl (100%)\n\ndiff --git a/.gitignore b/.gitignore\nindex 6722f78f9a..5555ae025b 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -76,6 +76,7 @@\n /git-init-db\n /git-interpret-trailers\n /git-instaweb\n+/git-legacy-difftool\n /git-log\n /git-ls-files\n /git-ls-remote\ndiff --git a/Makefile b/Makefile\nindex d861bd9985..8cf5bef034 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -522,7 +522,7 @@ SCRIPT_LIB += git-sh-setup\n SCRIPT_LIB += git-sh-i18n\n \n SCRIPT_PERL += git-add--interactive.perl\n-SCRIPT_PERL += git-difftool.perl\n+SCRIPT_PERL += git-legacy-difftool.perl\n SCRIPT_PERL += git-archimport.perl\n SCRIPT_PERL += git-cvsexportcommit.perl\n SCRIPT_PERL += git-cvsimport.perl\n@@ -883,6 +883,7 @@ BUILTIN_OBJS += builtin/diff-files.o\n BUILTIN_OBJS += builtin/diff-index.o\n BUILTIN_OBJS += builtin/diff-tree.o\n BUILTIN_OBJS += builtin/diff.o\n+BUILTIN_OBJS += builtin/difftool.o\n BUILTIN_OBJS += builtin/fast-export.o\n BUILTIN_OBJS += builtin/fetch-pack.o\n BUILTIN_OBJS += builtin/fetch.o\ndiff --git a/builtin.h b/builtin.h\nindex b9122bc5f4..67f80519da 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix);\n extern int cmd_diff_index(int argc, const char **argv, const char *prefix);\n extern int cmd_diff(int argc, const char **argv, const char *prefix);\n extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);\n+extern int cmd_difftool(int argc, const char **argv, const char *prefix);\n extern int cmd_fast_export(int argc, const char **argv, const char *prefix);\n extern int cmd_fetch(int argc, const char **argv, const char *prefix);\n extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nnew file mode 100644\nindex 0000000000..53870bbaf7\n--- /dev/null\n+++ b/builtin/difftool.c\n@@ -0,0 +1,63 @@\n+/*\n+ * \"git difftool\" builtin command\n+ *\n+ * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible\n+ * git-difftool--helper script.\n+ *\n+ * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.\n+ * The GIT_DIFF* variables are exported for use by git-difftool--helper.\n+ *\n+ * Any arguments that are unknown to this script are forwarded to 'git diff'.\n+ *\n+ * Copyright (C) 2016 Johannes Schindelin\n+ */\n+#include \"builtin.h\"\n+#include \"run-command.h\"\n+#include \"exec_cmd.h\"\n+\n+/*\n+ * NEEDSWORK: this function can go once the legacy-difftool Perl script is\n+ * retired.\n+ *\n+ * We intentionally avoid reading the config directly here, to avoid messing up\n+ * the GIT_* environment variables when we need to fall back to exec()ing the\n+ * Perl script.\n+ */\n+static int use_builtin_difftool(void) {\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tstruct strbuf out = STRBUF_INIT;\n+\tint ret;\n+\n+\targv_array_pushl(&cp.args,\n+\t\t\t \"config\", \"--bool\", \"difftool.usebuiltin\", NULL);\n+\tcp.git_cmd = 1;\n+\tif (capture_command(&cp, &out, 6))\n+\t\treturn 0;\n+\tstrbuf_trim(&out);\n+\tret = !strcmp(\"true\", out.buf);\n+\tstrbuf_release(&out);\n+\treturn ret;\n+}\n+\n+int cmd_difftool(int argc, const char **argv, const char *prefix)\n+{\n+\t/*\n+\t * NEEDSWORK: Once the builtin difftool has been tested enough\n+\t * and git-legacy-difftool.perl is retired to contrib/, this preamble\n+\t * can be removed.\n+\t */\n+\tif (!use_builtin_difftool()) {\n+\t\tconst char *path = mkpath(\"%s/git-legacy-difftool\",\n+\t\t\t\t\t  git_exec_path());\n+\n+\t\tif (sane_execvp(path, (char **)argv) < 0)\n+\t\t\tdie_errno(\"could not exec %s\", path);\n+\n+\t\treturn 0;\n+\t}\n+\tprefix = setup_git_directory();\n+\ttrace_repo_setup(prefix);\n+\tsetup_work_tree();\n+\n+\tdie(\"TODO\");\n+}\ndiff --git a/git-difftool.perl b/git-legacy-difftool.perl\nsimilarity index 100%\nrename from git-difftool.perl\nrename to git-legacy-difftool.perl\ndiff --git a/git.c b/git.c\nindex bbaa949e9c..c58181e5ef 100644\n--- a/git.c\n+++ b/git.c\n@@ -424,6 +424,12 @@ static struct cmd_struct commands[] = {\n \t{ \"diff-files\", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"diff-index\", cmd_diff_index, RUN_SETUP },\n \t{ \"diff-tree\", cmd_diff_tree, RUN_SETUP },\n+\t/*\n+\t * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in\n+\t * builtin/difftool.c has been removed, this entry should be changed to\n+\t * RUN_SETUP | NEED_WORK_TREE\n+\t */\n+\t{ \"difftool\", cmd_difftool },\n \t{ \"fast-export\", cmd_fast_export, RUN_SETUP },\n \t{ \"fetch\", cmd_fetch, RUN_SETUP },\n \t{ \"fetch-pack\", cmd_fetch_pack, RUN_SETUP },\ndiff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh\nindex 99d4123461..e94910c563 100755\n--- a/t/t7800-difftool.sh\n+++ b/t/t7800-difftool.sh\n@@ -23,6 +23,8 @@ prompt_given ()\n \ttest \"$prompt\" = \"Launch 'test-tool' [Y/n]? branch\"\n }\n \n+# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.\n+\n # Create a file on master and change it on branch\n test_expect_success PERL 'setup' '\n \techo master >file &&\n-- \n2.11.0.windows.3\n\n\n"},{"id":"309773","messageId":"8ab75685f718cfeb571409830ae3c6ee14ac5158.1484857756.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1484857756.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 2/3] difftool: implement the functionality in the builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-01-19T20:30:36Z","receivedAt":"2017-01-19T20:32:28Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch gives life to the skeleton added in the previous patch.\n\nThe motivation for converting the difftool is that Perl scripts are not at\nall native on Windows, and that `git difftool` therefore is pretty slow on\nthat platform, when there is no good reason for it to be slow.\n\nIn addition, Perl does not really have access to Git's internals. That\nmeans that any script will always have to jump through unnecessary\nhoops, and it will often need to perform unnecessary work (e.g. when\nreading the entire config every time `git config` is called to query a\nsingle config value).\n\nThe current version of the builtin difftool does not, however, make full\nuse of the internals but instead chooses to spawn a couple of Git\nprocesses, still, to make for an easier conversion. There remains a lot\nof room for improvement, left later.\n\nNote: to play it safe, the original difftool is still called unless the\nconfig setting difftool.useBuiltin is set to true.\n\nThe reason: this new, experimental, builtin difftool was shipped as part\nof Git for Windows v2.11.0, to allow for easier large-scale testing, but\nof course as an opt-in feature.\n\nThe speedup is actually more noticable on Linux than on Windows: a quick\ntest shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s)\n(real/user/sys) in a Linux VM, down from  (6.529s/3.112s/0.644s), while on\nWindows, it is (36.064s/2.730s/7.194s), down from (47.637s/2.407s/6.863s).\nThe culprit is most likely the overhead incurred from *still* having to\nshell out to mergetool-lib.sh and difftool--helper.sh.\n\nStill, it is an improvement.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/difftool.c | 672 ++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 671 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex 53870bbaf7..2115e548a5 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -11,9 +11,610 @@\n  *\n  * Copyright (C) 2016 Johannes Schindelin\n  */\n+#include \"cache.h\"\n #include \"builtin.h\"\n #include \"run-command.h\"\n #include \"exec_cmd.h\"\n+#include \"parse-options.h\"\n+#include \"argv-array.h\"\n+#include \"strbuf.h\"\n+#include \"lockfile.h\"\n+#include \"dir.h\"\n+\n+static char *diff_gui_tool;\n+static int trust_exit_code;\n+\n+static const char *const builtin_difftool_usage[] = {\n+\tN_(\"git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]\"),\n+\tNULL\n+};\n+\n+static int difftool_config(const char *var, const char *value, void *cb)\n+{\n+\tif (!strcmp(var, \"diff.guitool\")) {\n+\t\tdiff_gui_tool = xstrdup(value);\n+\t\treturn 0;\n+\t}\n+\n+\tif (!strcmp(var, \"difftool.trustexitcode\")) {\n+\t\ttrust_exit_code = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n+\treturn git_default_config(var, value, cb);\n+}\n+\n+static int print_tool_help(void)\n+{\n+\tconst char *argv[] = { \"mergetool\", \"--tool-help=diff\", NULL };\n+\treturn run_command_v_opt(argv, RUN_GIT_CMD);\n+}\n+\n+static int parse_index_info(char *p, int *mode1, int *mode2,\n+\t\t\t    struct object_id *oid1, struct object_id *oid2,\n+\t\t\t    char *status)\n+{\n+\tif (*p != ':')\n+\t\treturn error(\"expected ':', got '%c'\", *p);\n+\t*mode1 = (int)strtol(p + 1, &p, 8);\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\t*mode2 = (int)strtol(p + 1, &p, 8);\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\tif (get_oid_hex(++p, oid1))\n+\t\treturn error(\"expected object ID, got '%s'\", p + 1);\n+\tp += GIT_SHA1_HEXSZ;\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\tif (get_oid_hex(++p, oid2))\n+\t\treturn error(\"expected object ID, got '%s'\", p + 1);\n+\tp += GIT_SHA1_HEXSZ;\n+\tif (*p != ' ')\n+\t\treturn error(\"expected ' ', got '%c'\", *p);\n+\t*status = *++p;\n+\tif (!*status)\n+\t\treturn error(\"missing status\");\n+\tif (p[1] && !isdigit(p[1]))\n+\t\treturn error(\"unexpected trailer: '%s'\", p + 1);\n+\treturn 0;\n+}\n+\n+/*\n+ * Remove any trailing slash from $workdir\n+ * before starting to avoid double slashes in symlink targets.\n+ */\n+static void add_path(struct strbuf *buf, size_t base_len, const char *path)\n+{\n+\tstrbuf_setlen(buf, base_len);\n+\tif (buf->len && buf->buf[buf->len - 1] != '/')\n+\t\tstrbuf_addch(buf, '/');\n+\tstrbuf_addstr(buf, path);\n+}\n+\n+/*\n+ * Determine whether we can simply reuse the file in the worktree.\n+ */\n+static int use_wt_file(const char *workdir, const char *name,\n+\t\t       struct object_id *oid)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct stat st;\n+\tint use = 0;\n+\n+\tstrbuf_addstr(&buf, workdir);\n+\tadd_path(&buf, buf.len, name);\n+\n+\tif (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) {\n+\t\tstruct object_id wt_oid;\n+\t\tint fd = open(buf.buf, O_RDONLY);\n+\n+\t\tif (fd >= 0 &&\n+\t\t    !index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {\n+\t\t\tif (is_null_oid(oid)) {\n+\t\t\t\toidcpy(oid, &wt_oid);\n+\t\t\t\tuse = 1;\n+\t\t\t} else if (!oidcmp(oid, &wt_oid))\n+\t\t\t\tuse = 1;\n+\t\t}\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn use;\n+}\n+\n+struct working_tree_entry {\n+\tstruct hashmap_entry entry;\n+\tchar path[FLEX_ARRAY];\n+};\n+\n+static int working_tree_entry_cmp(struct working_tree_entry *a,\n+\t\t\t\t  struct working_tree_entry *b, void *keydata)\n+{\n+\treturn strcmp(a->path, b->path);\n+}\n+\n+/*\n+ * The `left` and `right` entries hold paths for the symlinks hashmap,\n+ * and a SHA-1 surrounded by brief text for submodules.\n+ */\n+struct pair_entry {\n+\tstruct hashmap_entry entry;\n+\tchar left[PATH_MAX], right[PATH_MAX];\n+\tconst char path[FLEX_ARRAY];\n+};\n+\n+static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata)\n+{\n+\treturn strcmp(a->path, b->path);\n+}\n+\n+static void add_left_or_right(struct hashmap *map, const char *path,\n+\t\t\t      const char *content, int is_right)\n+{\n+\tstruct pair_entry *e, *existing;\n+\n+\tFLEX_ALLOC_STR(e, path, path);\n+\thashmap_entry_init(e, strhash(path));\n+\texisting = hashmap_get(map, e, NULL);\n+\tif (existing) {\n+\t\tfree(e);\n+\t\te = existing;\n+\t} else {\n+\t\te->left[0] = e->right[0] = '\\0';\n+\t\thashmap_add(map, e);\n+\t}\n+\tstrlcpy(is_right ? e->right : e->left, content, PATH_MAX);\n+}\n+\n+struct path_entry {\n+\tstruct hashmap_entry entry;\n+\tchar path[FLEX_ARRAY];\n+};\n+\n+static int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key)\n+{\n+\treturn strcmp(a->path, key ? key : b->path);\n+}\n+\n+static void changed_files(struct hashmap *result, const char *index_path,\n+\t\t\t  const char *workdir)\n+{\n+\tstruct child_process update_index = CHILD_PROCESS_INIT;\n+\tstruct child_process diff_files = CHILD_PROCESS_INIT;\n+\tstruct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT;\n+\tconst char *git_dir = absolute_path(get_git_dir()), *env[] = {\n+\t\tNULL, NULL\n+\t};\n+\tFILE *fp;\n+\n+\tstrbuf_addf(&index_env, \"GIT_INDEX_FILE=%s\", index_path);\n+\tenv[0] = index_env.buf;\n+\n+\targv_array_pushl(&update_index.args,\n+\t\t\t \"--git-dir\", git_dir, \"--work-tree\", workdir,\n+\t\t\t \"update-index\", \"--really-refresh\", \"-q\",\n+\t\t\t \"--unmerged\", NULL);\n+\tupdate_index.no_stdin = 1;\n+\tupdate_index.no_stdout = 1;\n+\tupdate_index.no_stderr = 1;\n+\tupdate_index.git_cmd = 1;\n+\tupdate_index.use_shell = 0;\n+\tupdate_index.clean_on_exit = 1;\n+\tupdate_index.dir = workdir;\n+\tupdate_index.env = env;\n+\t/* Ignore any errors of update-index */\n+\trun_command(&update_index);\n+\n+\targv_array_pushl(&diff_files.args,\n+\t\t\t \"--git-dir\", git_dir, \"--work-tree\", workdir,\n+\t\t\t \"diff-files\", \"--name-only\", \"-z\", NULL);\n+\tdiff_files.no_stdin = 1;\n+\tdiff_files.git_cmd = 1;\n+\tdiff_files.use_shell = 0;\n+\tdiff_files.clean_on_exit = 1;\n+\tdiff_files.out = -1;\n+\tdiff_files.dir = workdir;\n+\tdiff_files.env = env;\n+\tif (start_command(&diff_files))\n+\t\tdie(\"could not obtain raw diff\");\n+\tfp = xfdopen(diff_files.out, \"r\");\n+\twhile (!strbuf_getline_nul(&buf, fp)) {\n+\t\tstruct path_entry *entry;\n+\t\tFLEX_ALLOC_STR(entry, path, buf.buf);\n+\t\thashmap_entry_init(entry, strhash(buf.buf));\n+\t\thashmap_add(result, entry);\n+\t}\n+\tif (finish_command(&diff_files))\n+\t\tdie(\"diff-files did not exit properly\");\n+\tstrbuf_release(&index_env);\n+\tstrbuf_release(&buf);\n+}\n+\n+static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstrbuf_addstr(&buf, tmpdir);\n+\tremove_dir_recursively(&buf, 0);\n+\tif (exit_code)\n+\t\twarning(_(\"failed: %d\"), exit_code);\n+\texit(exit_code);\n+}\n+\n+static int ensure_leading_directories(char *path)\n+{\n+\tswitch (safe_create_leading_directories(path)) {\n+\t\tcase SCLD_OK:\n+\t\tcase SCLD_EXISTS:\n+\t\t\treturn 0;\n+\t\tdefault:\n+\t\t\treturn error(_(\"could not create leading directories \"\n+\t\t\t\t       \"of '%s'\"), path);\n+\t}\n+}\n+\n+static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,\n+\t\t\tint argc, const char **argv)\n+{\n+\tchar tmpdir[PATH_MAX];\n+\tstruct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;\n+\tstruct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;\n+\tstruct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;\n+\tstruct strbuf wtdir = STRBUF_INIT;\n+\tsize_t ldir_len, rdir_len, wtdir_len;\n+\tstruct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1);\n+\tconst char *workdir, *tmp;\n+\tint ret = 0, i;\n+\tFILE *fp;\n+\tstruct hashmap working_tree_dups, submodules, symlinks2;\n+\tstruct hashmap_iter iter;\n+\tstruct pair_entry *entry;\n+\tenum object_type type;\n+\tunsigned long size;\n+\tstruct index_state wtindex;\n+\tstruct checkout lstate, rstate;\n+\tint rc, flags = RUN_GIT_CMD, err = 0;\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\tconst char *helper_argv[] = { \"difftool--helper\", NULL, NULL, NULL };\n+\tstruct hashmap wt_modified, tmp_modified;\n+\tint indices_loaded = 0;\n+\n+\tworkdir = get_git_work_tree();\n+\n+\t/* Setup temp directories */\n+\ttmp = getenv(\"TMPDIR\");\n+\txsnprintf(tmpdir, sizeof(tmpdir), \"%s/git-difftool.XXXXXX\", tmp ? tmp : \"/tmp\");\n+\tif (!mkdtemp(tmpdir))\n+\t\treturn error(\"could not create '%s'\", tmpdir);\n+\tstrbuf_addf(&ldir, \"%s/left/\", tmpdir);\n+\tstrbuf_addf(&rdir, \"%s/right/\", tmpdir);\n+\tstrbuf_addstr(&wtdir, workdir);\n+\tif (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))\n+\t\tstrbuf_addch(&wtdir, '/');\n+\tmkdir(ldir.buf, 0700);\n+\tmkdir(rdir.buf, 0700);\n+\n+\tmemset(&wtindex, 0, sizeof(wtindex));\n+\n+\tmemset(&lstate, 0, sizeof(lstate));\n+\tlstate.base_dir = ldir.buf;\n+\tlstate.base_dir_len = ldir.len;\n+\tlstate.force = 1;\n+\tmemset(&rstate, 0, sizeof(rstate));\n+\trstate.base_dir = rdir.buf;\n+\trstate.base_dir_len = rdir.len;\n+\trstate.force = 1;\n+\n+\tldir_len = ldir.len;\n+\trdir_len = rdir.len;\n+\twtdir_len = wtdir.len;\n+\n+\thashmap_init(&working_tree_dups,\n+\t\t     (hashmap_cmp_fn)working_tree_entry_cmp, 0);\n+\thashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0);\n+\thashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0);\n+\n+\tchild.no_stdin = 1;\n+\tchild.git_cmd = 1;\n+\tchild.use_shell = 0;\n+\tchild.clean_on_exit = 1;\n+\tchild.dir = prefix;\n+\tchild.out = -1;\n+\targv_array_pushl(&child.args, \"diff\", \"--raw\", \"--no-abbrev\", \"-z\",\n+\t\t\t NULL);\n+\tfor (i = 0; i < argc; i++)\n+\t\targv_array_push(&child.args, argv[i]);\n+\tif (start_command(&child))\n+\t\tdie(\"could not obtain raw diff\");\n+\tfp = xfdopen(child.out, \"r\");\n+\n+\t/* Build index info for left and right sides of the diff */\n+\ti = 0;\n+\twhile (!strbuf_getline_nul(&info, fp)) {\n+\t\tint lmode, rmode;\n+\t\tstruct object_id loid, roid;\n+\t\tchar status;\n+\t\tconst char *src_path, *dst_path;\n+\t\tsize_t src_path_len, dst_path_len;\n+\n+\t\tif (starts_with(info.buf, \"::\"))\n+\t\t\tdie(N_(\"combined diff formats('-c' and '--cc') are \"\n+\t\t\t       \"not supported in\\n\"\n+\t\t\t       \"directory diff mode('-d' and '--dir-diff').\"));\n+\n+\t\tif (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,\n+\t\t\t\t     &status))\n+\t\t\tbreak;\n+\t\tif (strbuf_getline_nul(&lpath, fp))\n+\t\t\tbreak;\n+\t\tsrc_path = lpath.buf;\n+\t\tsrc_path_len = lpath.len;\n+\n+\t\ti++;\n+\t\tif (status != 'C' && status != 'R') {\n+\t\t\tdst_path = src_path;\n+\t\t\tdst_path_len = src_path_len;\n+\t\t} else {\n+\t\t\tif (strbuf_getline_nul(&rpath, fp))\n+\t\t\t\tbreak;\n+\t\t\tdst_path = rpath.buf;\n+\t\t\tdst_path_len = rpath.len;\n+\t\t}\n+\n+\t\tif (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) {\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"Subproject commit %s\",\n+\t\t\t\t    oid_to_hex(&loid));\n+\t\t\tadd_left_or_right(&submodules, src_path, buf.buf, 0);\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"Subproject commit %s\",\n+\t\t\t\t    oid_to_hex(&roid));\n+\t\t\tif (!oidcmp(&loid, &roid))\n+\t\t\t\tstrbuf_addstr(&buf, \"-dirty\");\n+\t\t\tadd_left_or_right(&submodules, dst_path, buf.buf, 1);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (S_ISLNK(lmode)) {\n+\t\t\tchar *content = read_sha1_file(loid.hash, &type, &size);\n+\t\t\tadd_left_or_right(&symlinks2, src_path, content, 0);\n+\t\t\tfree(content);\n+\t\t}\n+\n+\t\tif (S_ISLNK(rmode)) {\n+\t\t\tchar *content = read_sha1_file(roid.hash, &type, &size);\n+\t\t\tadd_left_or_right(&symlinks2, dst_path, content, 1);\n+\t\t\tfree(content);\n+\t\t}\n+\n+\t\tif (lmode && status != 'C') {\n+\t\t\tce->ce_mode = lmode;\n+\t\t\toidcpy(&ce->oid, &loid);\n+\t\t\tstrcpy(ce->name, src_path);\n+\t\t\tce->ce_namelen = src_path_len;\n+\t\t\tif (checkout_entry(ce, &lstate, NULL))\n+\t\t\t\treturn error(\"could not write '%s'\", src_path);\n+\t\t}\n+\n+\t\tif (rmode) {\n+\t\t\tstruct working_tree_entry *entry;\n+\n+\t\t\t/* Avoid duplicate working_tree entries */\n+\t\t\tFLEX_ALLOC_STR(entry, path, dst_path);\n+\t\t\thashmap_entry_init(entry, strhash(dst_path));\n+\t\t\tif (hashmap_get(&working_tree_dups, entry, NULL)) {\n+\t\t\t\tfree(entry);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\thashmap_add(&working_tree_dups, entry);\n+\n+\t\t\tif (!use_wt_file(workdir, dst_path, &roid)) {\n+\t\t\t\tce->ce_mode = rmode;\n+\t\t\t\toidcpy(&ce->oid, &roid);\n+\t\t\t\tstrcpy(ce->name, dst_path);\n+\t\t\t\tce->ce_namelen = dst_path_len;\n+\t\t\t\tif (checkout_entry(ce, &rstate, NULL))\n+\t\t\t\t\treturn error(\"could not write '%s'\",\n+\t\t\t\t\t\t     dst_path);\n+\t\t\t} else if (!is_null_oid(&roid)) {\n+\t\t\t\t/*\n+\t\t\t\t * Changes in the working tree need special\n+\t\t\t\t * treatment since they are not part of the\n+\t\t\t\t * index.\n+\t\t\t\t */\n+\t\t\t\tstruct cache_entry *ce2 =\n+\t\t\t\t\tmake_cache_entry(rmode, roid.hash,\n+\t\t\t\t\t\t\t dst_path, 0, 0);\n+\n+\t\t\t\tadd_index_entry(&wtindex, ce2,\n+\t\t\t\t\t\tADD_CACHE_JUST_APPEND);\n+\n+\t\t\t\tadd_path(&rdir, rdir_len, dst_path);\n+\t\t\t\tif (ensure_leading_directories(rdir.buf))\n+\t\t\t\t\treturn error(\"could not create \"\n+\t\t\t\t\t\t     \"directory for '%s'\",\n+\t\t\t\t\t\t     dst_path);\n+\t\t\t\tadd_path(&wtdir, wtdir_len, dst_path);\n+\t\t\t\tif (symlinks) {\n+\t\t\t\t\tif (symlink(wtdir.buf, rdir.buf)) {\n+\t\t\t\t\t\tret = error_errno(\"could not symlink '%s' to '%s'\", wtdir.buf, rdir.buf);\n+\t\t\t\t\t\tgoto finish;\n+\t\t\t\t\t}\n+\t\t\t\t} else {\n+\t\t\t\t\tstruct stat st;\n+\t\t\t\t\tif (stat(wtdir.buf, &st))\n+\t\t\t\t\t\tst.st_mode = 0644;\n+\t\t\t\t\tif (copy_file(rdir.buf, wtdir.buf,\n+\t\t\t\t\t\t      st.st_mode)) {\n+\t\t\t\t\t\tret = error(\"could not copy '%s' to '%s'\", wtdir.buf, rdir.buf);\n+\t\t\t\t\t\tgoto finish;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (finish_command(&child)) {\n+\t\tret = error(\"error occurred running diff --raw\");\n+\t\tgoto finish;\n+\t}\n+\n+\tif (!i)\n+\t\treturn 0;\n+\n+\t/*\n+\t * Changes to submodules require special treatment.This loop writes a\n+\t * temporary file to both the left and right directories to show the\n+\t * change in the recorded SHA1 for the submodule.\n+\t */\n+\thashmap_iter_init(&submodules, &iter);\n+\twhile ((entry = hashmap_iter_next(&iter))) {\n+\t\tif (*entry->left) {\n+\t\t\tadd_path(&ldir, ldir_len, entry->path);\n+\t\t\tensure_leading_directories(ldir.buf);\n+\t\t\twrite_file(ldir.buf, \"%s\", entry->left);\n+\t\t}\n+\t\tif (*entry->right) {\n+\t\t\tadd_path(&rdir, rdir_len, entry->path);\n+\t\t\tensure_leading_directories(rdir.buf);\n+\t\t\twrite_file(rdir.buf, \"%s\", entry->right);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Symbolic links require special treatment.The standard \"git diff\"\n+\t * shows only the link itself, not the contents of the link target.\n+\t * This loop replicates that behavior.\n+\t */\n+\thashmap_iter_init(&symlinks2, &iter);\n+\twhile ((entry = hashmap_iter_next(&iter))) {\n+\t\tif (*entry->left) {\n+\t\t\tadd_path(&ldir, ldir_len, entry->path);\n+\t\t\tensure_leading_directories(ldir.buf);\n+\t\t\twrite_file(ldir.buf, \"%s\", entry->left);\n+\t\t}\n+\t\tif (*entry->right) {\n+\t\t\tadd_path(&rdir, rdir_len, entry->path);\n+\t\t\tensure_leading_directories(rdir.buf);\n+\t\t\twrite_file(rdir.buf, \"%s\", entry->right);\n+\t\t}\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\tstrbuf_setlen(&ldir, ldir_len);\n+\thelper_argv[1] = ldir.buf;\n+\tstrbuf_setlen(&rdir, rdir_len);\n+\thelper_argv[2] = rdir.buf;\n+\n+\tif (extcmd) {\n+\t\thelper_argv[0] = extcmd;\n+\t\tflags = 0;\n+\t} else\n+\t\tsetenv(\"GIT_DIFFTOOL_DIRDIFF\", \"true\", 1);\n+\trc = run_command_v_opt(helper_argv, flags);\n+\n+\t/*\n+\t * If the diff includes working copy files and those\n+\t * files were modified during the diff, then the changes\n+\t * should be copied back to the working tree.\n+\t * Do not copy back files when symlinks are used and the\n+\t * external tool did not replace the original link with a file.\n+\t *\n+\t * These hashes are loaded lazily since they aren't needed\n+\t * in the common case of --symlinks and the difftool updating\n+\t * files through the symlink.\n+\t */\n+\thashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp,\n+\t\t     wtindex.cache_nr);\n+\thashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp,\n+\t\t     wtindex.cache_nr);\n+\n+\tfor (i = 0; i < wtindex.cache_nr; i++) {\n+\t\tstruct hashmap_entry dummy;\n+\t\tconst char *name = wtindex.cache[i]->name;\n+\t\tstruct stat st;\n+\n+\t\tadd_path(&rdir, rdir_len, name);\n+\t\tif (lstat(rdir.buf, &st))\n+\t\t\tcontinue;\n+\n+\t\tif ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode))\n+\t\t\tcontinue;\n+\n+\t\tif (!indices_loaded) {\n+\t\t\tstatic struct lock_file lock;\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"%s/wtindex\", tmpdir);\n+\t\t\tif (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||\n+\t\t\t    write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {\n+\t\t\t\tret = error(\"could not write %s\", buf.buf);\n+\t\t\t\trollback_lock_file(&lock);\n+\t\t\t\tgoto finish;\n+\t\t\t}\n+\t\t\tchanged_files(&wt_modified, buf.buf, workdir);\n+\t\t\tstrbuf_setlen(&rdir, rdir_len);\n+\t\t\tchanged_files(&tmp_modified, buf.buf, rdir.buf);\n+\t\t\tadd_path(&rdir, rdir_len, name);\n+\t\t\tindices_loaded = 1;\n+\t\t}\n+\n+\t\thashmap_entry_init(&dummy, strhash(name));\n+\t\tif (hashmap_get(&tmp_modified, &dummy, name)) {\n+\t\t\tadd_path(&wtdir, wtdir_len, name);\n+\t\t\tif (hashmap_get(&wt_modified, &dummy, name)) {\n+\t\t\t\twarning(_(\"both files modified: '%s' and '%s'.\"),\n+\t\t\t\t\twtdir.buf, rdir.buf);\n+\t\t\t\twarning(_(\"working tree file has been left.\"));\n+\t\t\t\twarning(\"\");\n+\t\t\t\terr = 1;\n+\t\t\t} else if (unlink(wtdir.buf) ||\n+\t\t\t\t   copy_file(wtdir.buf, rdir.buf, st.st_mode))\n+\t\t\t\twarning_errno(_(\"could not copy '%s' to '%s'\"),\n+\t\t\t\t\t      rdir.buf, wtdir.buf);\n+\t\t}\n+\t}\n+\n+\tif (err) {\n+\t\twarning(_(\"temporary files exist in '%s'.\"), tmpdir);\n+\t\twarning(_(\"you may want to cleanup or recover these.\"));\n+\t\texit(1);\n+\t} else\n+\t\texit_cleanup(tmpdir, rc);\n+\n+finish:\n+\tfree(ce);\n+\tstrbuf_release(&ldir);\n+\tstrbuf_release(&rdir);\n+\tstrbuf_release(&wtdir);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+static int run_file_diff(int prompt, const char *prefix,\n+\t\t\t int argc, const char **argv)\n+{\n+\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\tconst char *env[] = {\n+\t\t\"GIT_PAGER=\", \"GIT_EXTERNAL_DIFF=git-difftool--helper\", NULL,\n+\t\tNULL\n+\t};\n+\tint ret = 0, i;\n+\n+\tif (prompt > 0)\n+\t\tenv[2] = \"GIT_DIFFTOOL_PROMPT=true\";\n+\telse if (!prompt)\n+\t\tenv[2] = \"GIT_DIFFTOOL_NO_PROMPT=true\";\n+\n+\n+\targv_array_push(&args, \"diff\");\n+\tfor (i = 0; i < argc; i++)\n+\t\targv_array_push(&args, argv[i]);\n+\tret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env);\n+\texit(ret);\n+}\n \n /*\n  * NEEDSWORK: this function can go once the legacy-difftool Perl script is\n@@ -41,6 +642,35 @@ static int use_builtin_difftool(void) {\n \n int cmd_difftool(int argc, const char **argv, const char *prefix)\n {\n+\tint use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,\n+\t    tool_help = 0;\n+\tstatic char *difftool_cmd = NULL, *extcmd = NULL;\n+\tstruct option builtin_difftool_options[] = {\n+\t\tOPT_BOOL('g', \"gui\", &use_gui_tool,\n+\t\t\t N_(\"use `diff.guitool` instead of `diff.tool`\")),\n+\t\tOPT_BOOL('d', \"dir-diff\", &dir_diff,\n+\t\t\t N_(\"perform a full-directory diff\")),\n+\t\t{ OPTION_SET_INT, 'y', \"no-prompt\", &prompt, NULL,\n+\t\t\tN_(\"do not prompt before launching a diff tool\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},\n+\t\t{ OPTION_SET_INT, 0, \"prompt\", &prompt, NULL, NULL,\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n+\t\t\tNULL, 1 },\n+\t\tOPT_BOOL(0, \"symlinks\", &symlinks,\n+\t\t\t N_(\"use symlinks in dir-diff mode\")),\n+\t\tOPT_STRING('t', \"tool\", &difftool_cmd, N_(\"<tool>\"),\n+\t\t\t   N_(\"use the specified diff tool\")),\n+\t\tOPT_BOOL(0, \"tool-help\", &tool_help,\n+\t\t\t N_(\"print a list of diff tools that may be used with \"\n+\t\t\t    \"`--tool`\")),\n+\t\tOPT_BOOL(0, \"trust-exit-code\", &trust_exit_code,\n+\t\t\t N_(\"make 'git-difftool' exit when an invoked diff \"\n+\t\t\t    \"tool returns a non - zero exit code\")),\n+\t\tOPT_STRING('x', \"extcmd\", &extcmd, N_(\"<command>\"),\n+\t\t\t   N_(\"specify a custom command for viewing diffs\")),\n+\t\tOPT_END()\n+\t};\n+\n \t/*\n \t * NEEDSWORK: Once the builtin difftool has been tested enough\n \t * and git-legacy-difftool.perl is retired to contrib/, this preamble\n@@ -58,6 +688,46 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)\n \tprefix = setup_git_directory();\n \ttrace_repo_setup(prefix);\n \tsetup_work_tree();\n+\t/* NEEDSWORK: once we no longer spawn anything, remove this */\n+\tsetenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);\n+\tsetenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);\n+\n+\tgit_config(difftool_config, NULL);\n+\tsymlinks = has_symlinks;\n+\n+\targc = parse_options(argc, argv, prefix, builtin_difftool_options,\n+\t\t\t     builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n \n-\tdie(\"TODO\");\n+\tif (tool_help)\n+\t\treturn print_tool_help();\n+\n+\tif (use_gui_tool && diff_gui_tool && *diff_gui_tool)\n+\t\tsetenv(\"GIT_DIFF_TOOL\", diff_gui_tool, 1);\n+\telse if (difftool_cmd) {\n+\t\tif (*difftool_cmd)\n+\t\t\tsetenv(\"GIT_DIFF_TOOL\", difftool_cmd, 1);\n+\t\telse\n+\t\t\tdie(_(\"no <tool> given for --tool=<tool>\"));\n+\t}\n+\n+\tif (extcmd) {\n+\t\tif (*extcmd)\n+\t\t\tsetenv(\"GIT_DIFFTOOL_EXTCMD\", extcmd, 1);\n+\t\telse\n+\t\t\tdie(_(\"no <cmd> given for --extcmd=<cmd>\"));\n+\t}\n+\n+\tsetenv(\"GIT_DIFFTOOL_TRUST_EXIT_CODE\",\n+\t       trust_exit_code ? \"true\" : \"false\", 1);\n+\n+\t/*\n+\t * In directory diff mode, 'git-difftool--helper' is called once\n+\t * to compare the a / b directories. In file diff mode, 'git diff'\n+\t * will invoke a separate instance of 'git-difftool--helper' for\n+\t * each file that changed.\n+\t */\n+\tif (dir_diff)\n+\t\treturn run_dir_diff(extcmd, symlinks, prefix, argc, argv);\n+\treturn run_file_diff(prompt, prefix, argc, argv);\n }\n-- \n2.11.0.windows.3\n\n\n"},{"id":"309775","messageId":"alpine.DEB.2.20.1701192131300.3469@virtualbox","threadId":"44532","inReplyTo":"xmqqlgu72asr.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v5 3/3] Retire the scripted difftool","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-01-19T20:32:03Z","receivedAt":"2017-01-19T20:40:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Thu, 19 Jan 2017, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > Yep, as Git for Windows v2.11.0 is yesteryear's news, it was probably\n> > obvious to you that I simply failed to spot and fix that oversight.\n> \n> OK, if you want to tweak log message of either 2/3 or 3/3 to correct,\n> there is still time, as they are still outside 'next'.  \n\nSent out v6 with the commit message reworded.\n\nCiao,\nJohannes\n"},{"id":"309776","messageId":"1524ca797a20370fb3568dcd43116b01dda1d588.1484857756.git.johannes.schindelin@gmx.de","threadId":"44532","inReplyTo":"cover.1484857756.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 3/3] Retire the scripted difftool","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-01-19T20:30:40Z","receivedAt":"2017-01-19T20:40:51Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"It served its purpose, but now we have a builtin difftool. Time for the\nPerl script to enjoy Florida.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n .gitignore                                         |  1 -\n Makefile                                           |  1 -\n builtin/difftool.c                                 | 41 ----------\n .../examples/git-difftool.perl                     |  0\n git.c                                              |  7 +-\n t/t7800-difftool.sh                                | 94 +++++++++++-----------\n 6 files changed, 47 insertions(+), 97 deletions(-)\n rename git-legacy-difftool.perl => contrib/examples/git-difftool.perl (100%)\n\ndiff --git a/.gitignore b/.gitignore\nindex 5555ae025b..6722f78f9a 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -76,7 +76,6 @@\n /git-init-db\n /git-interpret-trailers\n /git-instaweb\n-/git-legacy-difftool\n /git-log\n /git-ls-files\n /git-ls-remote\ndiff --git a/Makefile b/Makefile\nindex 8cf5bef034..e9aa6ae57c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -522,7 +522,6 @@ SCRIPT_LIB += git-sh-setup\n SCRIPT_LIB += git-sh-i18n\n \n SCRIPT_PERL += git-add--interactive.perl\n-SCRIPT_PERL += git-legacy-difftool.perl\n SCRIPT_PERL += git-archimport.perl\n SCRIPT_PERL += git-cvsexportcommit.perl\n SCRIPT_PERL += git-cvsimport.perl\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex 2115e548a5..42ad9e804a 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -616,30 +616,6 @@ static int run_file_diff(int prompt, const char *prefix,\n \texit(ret);\n }\n \n-/*\n- * NEEDSWORK: this function can go once the legacy-difftool Perl script is\n- * retired.\n- *\n- * We intentionally avoid reading the config directly here, to avoid messing up\n- * the GIT_* environment variables when we need to fall back to exec()ing the\n- * Perl script.\n- */\n-static int use_builtin_difftool(void) {\n-\tstruct child_process cp = CHILD_PROCESS_INIT;\n-\tstruct strbuf out = STRBUF_INIT;\n-\tint ret;\n-\n-\targv_array_pushl(&cp.args,\n-\t\t\t \"config\", \"--bool\", \"difftool.usebuiltin\", NULL);\n-\tcp.git_cmd = 1;\n-\tif (capture_command(&cp, &out, 6))\n-\t\treturn 0;\n-\tstrbuf_trim(&out);\n-\tret = !strcmp(\"true\", out.buf);\n-\tstrbuf_release(&out);\n-\treturn ret;\n-}\n-\n int cmd_difftool(int argc, const char **argv, const char *prefix)\n {\n \tint use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,\n@@ -671,23 +647,6 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n-\t/*\n-\t * NEEDSWORK: Once the builtin difftool has been tested enough\n-\t * and git-legacy-difftool.perl is retired to contrib/, this preamble\n-\t * can be removed.\n-\t */\n-\tif (!use_builtin_difftool()) {\n-\t\tconst char *path = mkpath(\"%s/git-legacy-difftool\",\n-\t\t\t\t\t  git_exec_path());\n-\n-\t\tif (sane_execvp(path, (char **)argv) < 0)\n-\t\t\tdie_errno(\"could not exec %s\", path);\n-\n-\t\treturn 0;\n-\t}\n-\tprefix = setup_git_directory();\n-\ttrace_repo_setup(prefix);\n-\tsetup_work_tree();\n \t/* NEEDSWORK: once we no longer spawn anything, remove this */\n \tsetenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);\n \tsetenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);\ndiff --git a/git-legacy-difftool.perl b/contrib/examples/git-difftool.perl\nsimilarity index 100%\nrename from git-legacy-difftool.perl\nrename to contrib/examples/git-difftool.perl\ndiff --git a/git.c b/git.c\nindex c58181e5ef..bd4d668a21 100644\n--- a/git.c\n+++ b/git.c\n@@ -424,12 +424,7 @@ static struct cmd_struct commands[] = {\n \t{ \"diff-files\", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"diff-index\", cmd_diff_index, RUN_SETUP },\n \t{ \"diff-tree\", cmd_diff_tree, RUN_SETUP },\n-\t/*\n-\t * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in\n-\t * builtin/difftool.c has been removed, this entry should be changed to\n-\t * RUN_SETUP | NEED_WORK_TREE\n-\t */\n-\t{ \"difftool\", cmd_difftool },\n+\t{ \"difftool\", cmd_difftool, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"fast-export\", cmd_fast_export, RUN_SETUP },\n \t{ \"fetch\", cmd_fetch, RUN_SETUP },\n \t{ \"fetch-pack\", cmd_fetch_pack, RUN_SETUP },\ndiff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh\nindex e94910c563..aa0ef02597 100755\n--- a/t/t7800-difftool.sh\n+++ b/t/t7800-difftool.sh\n@@ -23,10 +23,8 @@ prompt_given ()\n \ttest \"$prompt\" = \"Launch 'test-tool' [Y/n]? branch\"\n }\n \n-# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired.\n-\n # Create a file on master and change it on branch\n-test_expect_success PERL 'setup' '\n+test_expect_success 'setup' '\n \techo master >file &&\n \tgit add file &&\n \tgit commit -m \"added file\" &&\n@@ -38,7 +36,7 @@ test_expect_success PERL 'setup' '\n '\n \n # Configure a custom difftool.<tool>.cmd and use it\n-test_expect_success PERL 'custom commands' '\n+test_expect_success 'custom commands' '\n \tdifftool_test_setup &&\n \ttest_config difftool.test-tool.cmd \"cat \\\"\\$REMOTE\\\"\" &&\n \techo master >expect &&\n@@ -51,21 +49,21 @@ test_expect_success PERL 'custom commands' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'custom tool commands override built-ins' '\n+test_expect_success 'custom tool commands override built-ins' '\n \ttest_config difftool.vimdiff.cmd \"cat \\\"\\$REMOTE\\\"\" &&\n \techo master >expect &&\n \tgit difftool --tool vimdiff --no-prompt branch >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool ignores bad --tool values' '\n+test_expect_success 'difftool ignores bad --tool values' '\n \t: >expect &&\n \ttest_must_fail \\\n \t\tgit difftool --no-prompt --tool=bad-tool branch >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool forwards arguments to diff' '\n+test_expect_success 'difftool forwards arguments to diff' '\n \tdifftool_test_setup &&\n \t>for-diff &&\n \tgit add for-diff &&\n@@ -78,40 +76,40 @@ test_expect_success PERL 'difftool forwards arguments to diff' '\n \trm for-diff\n '\n \n-test_expect_success PERL 'difftool ignores exit code' '\n+test_expect_success 'difftool ignores exit code' '\n \ttest_config difftool.error.cmd false &&\n \tgit difftool -y -t error branch\n '\n \n-test_expect_success PERL 'difftool forwards exit code with --trust-exit-code' '\n+test_expect_success 'difftool forwards exit code with --trust-exit-code' '\n \ttest_config difftool.error.cmd false &&\n \ttest_must_fail git difftool -y --trust-exit-code -t error branch\n '\n \n-test_expect_success PERL 'difftool forwards exit code with --trust-exit-code for built-ins' '\n+test_expect_success 'difftool forwards exit code with --trust-exit-code for built-ins' '\n \ttest_config difftool.vimdiff.path false &&\n \ttest_must_fail git difftool -y --trust-exit-code -t vimdiff branch\n '\n \n-test_expect_success PERL 'difftool honors difftool.trustExitCode = true' '\n+test_expect_success 'difftool honors difftool.trustExitCode = true' '\n \ttest_config difftool.error.cmd false &&\n \ttest_config difftool.trustExitCode true &&\n \ttest_must_fail git difftool -y -t error branch\n '\n \n-test_expect_success PERL 'difftool honors difftool.trustExitCode = false' '\n+test_expect_success 'difftool honors difftool.trustExitCode = false' '\n \ttest_config difftool.error.cmd false &&\n \ttest_config difftool.trustExitCode false &&\n \tgit difftool -y -t error branch\n '\n \n-test_expect_success PERL 'difftool ignores exit code with --no-trust-exit-code' '\n+test_expect_success 'difftool ignores exit code with --no-trust-exit-code' '\n \ttest_config difftool.error.cmd false &&\n \ttest_config difftool.trustExitCode true &&\n \tgit difftool -y --no-trust-exit-code -t error branch\n '\n \n-test_expect_success PERL 'difftool stops on error with --trust-exit-code' '\n+test_expect_success 'difftool stops on error with --trust-exit-code' '\n \ttest_when_finished \"rm -f for-diff .git/fail-right-file\" &&\n \ttest_when_finished \"git reset -- for-diff\" &&\n \twrite_script .git/fail-right-file <<-\\EOF &&\n@@ -126,13 +124,13 @@ test_expect_success PERL 'difftool stops on error with --trust-exit-code' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool honors exit status if command not found' '\n+test_expect_success 'difftool honors exit status if command not found' '\n \ttest_config difftool.nonexistent.cmd i-dont-exist &&\n \ttest_config difftool.trustExitCode false &&\n \ttest_must_fail git difftool -y -t nonexistent branch\n '\n \n-test_expect_success PERL 'difftool honors --gui' '\n+test_expect_success 'difftool honors --gui' '\n \tdifftool_test_setup &&\n \ttest_config merge.tool bogus-tool &&\n \ttest_config diff.tool bogus-tool &&\n@@ -143,7 +141,7 @@ test_expect_success PERL 'difftool honors --gui' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool --gui last setting wins' '\n+test_expect_success 'difftool --gui last setting wins' '\n \tdifftool_test_setup &&\n \t: >expect &&\n \tgit difftool --no-prompt --gui --no-gui >actual &&\n@@ -157,7 +155,7 @@ test_expect_success PERL 'difftool --gui last setting wins' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool --gui works without configured diff.guitool' '\n+test_expect_success 'difftool --gui works without configured diff.guitool' '\n \tdifftool_test_setup &&\n \techo branch >expect &&\n \tgit difftool --no-prompt --gui branch >actual &&\n@@ -165,7 +163,7 @@ test_expect_success PERL 'difftool --gui works without configured diff.guitool'\n '\n \n # Specify the diff tool using $GIT_DIFF_TOOL\n-test_expect_success PERL 'GIT_DIFF_TOOL variable' '\n+test_expect_success 'GIT_DIFF_TOOL variable' '\n \tdifftool_test_setup &&\n \tgit config --unset diff.tool &&\n \techo branch >expect &&\n@@ -175,7 +173,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL variable' '\n \n # Test the $GIT_*_TOOL variables and ensure\n # that $GIT_DIFF_TOOL always wins unless --tool is specified\n-test_expect_success PERL 'GIT_DIFF_TOOL overrides' '\n+test_expect_success 'GIT_DIFF_TOOL overrides' '\n \tdifftool_test_setup &&\n \ttest_config diff.tool bogus-tool &&\n \ttest_config merge.tool bogus-tool &&\n@@ -193,7 +191,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL overrides' '\n \n # Test that we don't have to pass --no-prompt to difftool\n # when $GIT_DIFFTOOL_NO_PROMPT is true\n-test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' '\n+test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' '\n \tdifftool_test_setup &&\n \techo branch >expect &&\n \tGIT_DIFFTOOL_NO_PROMPT=true git difftool branch >actual &&\n@@ -202,7 +200,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' '\n \n # git-difftool supports the difftool.prompt variable.\n # Test that GIT_DIFFTOOL_PROMPT can override difftool.prompt = false\n-test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' '\n+test_expect_success 'GIT_DIFFTOOL_PROMPT variable' '\n \tdifftool_test_setup &&\n \ttest_config difftool.prompt false &&\n \techo >input &&\n@@ -212,7 +210,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' '\n '\n \n # Test that we don't have to pass --no-prompt when difftool.prompt is false\n-test_expect_success PERL 'difftool.prompt config variable is false' '\n+test_expect_success 'difftool.prompt config variable is false' '\n \tdifftool_test_setup &&\n \ttest_config difftool.prompt false &&\n \techo branch >expect &&\n@@ -221,7 +219,7 @@ test_expect_success PERL 'difftool.prompt config variable is false' '\n '\n \n # Test that we don't have to pass --no-prompt when mergetool.prompt is false\n-test_expect_success PERL 'difftool merge.prompt = false' '\n+test_expect_success 'difftool merge.prompt = false' '\n \tdifftool_test_setup &&\n \ttest_might_fail git config --unset difftool.prompt &&\n \ttest_config mergetool.prompt false &&\n@@ -231,7 +229,7 @@ test_expect_success PERL 'difftool merge.prompt = false' '\n '\n \n # Test that the -y flag can override difftool.prompt = true\n-test_expect_success PERL 'difftool.prompt can overridden with -y' '\n+test_expect_success 'difftool.prompt can overridden with -y' '\n \tdifftool_test_setup &&\n \ttest_config difftool.prompt true &&\n \techo branch >expect &&\n@@ -240,7 +238,7 @@ test_expect_success PERL 'difftool.prompt can overridden with -y' '\n '\n \n # Test that the --prompt flag can override difftool.prompt = false\n-test_expect_success PERL 'difftool.prompt can overridden with --prompt' '\n+test_expect_success 'difftool.prompt can overridden with --prompt' '\n \tdifftool_test_setup &&\n \ttest_config difftool.prompt false &&\n \techo >input &&\n@@ -250,7 +248,7 @@ test_expect_success PERL 'difftool.prompt can overridden with --prompt' '\n '\n \n # Test that the last flag passed on the command-line wins\n-test_expect_success PERL 'difftool last flag wins' '\n+test_expect_success 'difftool last flag wins' '\n \tdifftool_test_setup &&\n \techo branch >expect &&\n \tgit difftool --prompt --no-prompt branch >actual &&\n@@ -263,7 +261,7 @@ test_expect_success PERL 'difftool last flag wins' '\n \n # git-difftool falls back to git-mergetool config variables\n # so test that behavior here\n-test_expect_success PERL 'difftool + mergetool config variables' '\n+test_expect_success 'difftool + mergetool config variables' '\n \ttest_config merge.tool test-tool &&\n \ttest_config mergetool.test-tool.cmd \"cat \\$LOCAL\" &&\n \techo branch >expect &&\n@@ -277,49 +275,49 @@ test_expect_success PERL 'difftool + mergetool config variables' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool.<tool>.path' '\n+test_expect_success 'difftool.<tool>.path' '\n \ttest_config difftool.tkdiff.path echo &&\n \tgit difftool --tool=tkdiff --no-prompt branch >output &&\n \tlines=$(grep file output | wc -l) &&\n \ttest \"$lines\" -eq 1\n '\n \n-test_expect_success PERL 'difftool --extcmd=cat' '\n+test_expect_success 'difftool --extcmd=cat' '\n \techo branch >expect &&\n \techo master >>expect &&\n \tgit difftool --no-prompt --extcmd=cat branch >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool --extcmd cat' '\n+test_expect_success 'difftool --extcmd cat' '\n \techo branch >expect &&\n \techo master >>expect &&\n \tgit difftool --no-prompt --extcmd=cat branch >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool -x cat' '\n+test_expect_success 'difftool -x cat' '\n \techo branch >expect &&\n \techo master >>expect &&\n \tgit difftool --no-prompt -x cat branch >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool --extcmd echo arg1' '\n+test_expect_success 'difftool --extcmd echo arg1' '\n \techo file >expect &&\n \tgit difftool --no-prompt \\\n \t\t--extcmd sh\\ -c\\ \\\"echo\\ \\$1\\\" branch >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool --extcmd cat arg1' '\n+test_expect_success 'difftool --extcmd cat arg1' '\n \techo master >expect &&\n \tgit difftool --no-prompt \\\n \t\t--extcmd sh\\ -c\\ \\\"cat\\ \\$1\\\" branch >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success PERL 'difftool --extcmd cat arg2' '\n+test_expect_success 'difftool --extcmd cat arg2' '\n \techo branch >expect &&\n \tgit difftool --no-prompt \\\n \t\t--extcmd sh\\ -c\\ \\\"cat\\ \\$2\\\" branch >actual &&\n@@ -327,7 +325,7 @@ test_expect_success PERL 'difftool --extcmd cat arg2' '\n '\n \n # Create a second file on master and a different version on branch\n-test_expect_success PERL 'setup with 2 files different' '\n+test_expect_success 'setup with 2 files different' '\n \techo m2 >file2 &&\n \tgit add file2 &&\n \tgit commit -m \"added file2\" &&\n@@ -339,7 +337,7 @@ test_expect_success PERL 'setup with 2 files different' '\n \tgit checkout master\n '\n \n-test_expect_success PERL 'say no to the first file' '\n+test_expect_success 'say no to the first file' '\n \t(echo n && echo) >input &&\n \tgit difftool -x cat branch <input >output &&\n \tgrep m2 output &&\n@@ -348,7 +346,7 @@ test_expect_success PERL 'say no to the first file' '\n \t! grep branch output\n '\n \n-test_expect_success PERL 'say no to the second file' '\n+test_expect_success 'say no to the second file' '\n \t(echo && echo n) >input &&\n \tgit difftool -x cat branch <input >output &&\n \tgrep master output &&\n@@ -357,7 +355,7 @@ test_expect_success PERL 'say no to the second file' '\n \t! grep br2 output\n '\n \n-test_expect_success PERL 'ending prompt input with EOF' '\n+test_expect_success 'ending prompt input with EOF' '\n \tgit difftool -x cat branch </dev/null >output &&\n \t! grep master output &&\n \t! grep branch output &&\n@@ -365,12 +363,12 @@ test_expect_success PERL 'ending prompt input with EOF' '\n \t! grep br2 output\n '\n \n-test_expect_success PERL 'difftool --tool-help' '\n+test_expect_success 'difftool --tool-help' '\n \tgit difftool --tool-help >output &&\n \tgrep tool output\n '\n \n-test_expect_success PERL 'setup change in subdirectory' '\n+test_expect_success 'setup change in subdirectory' '\n \tgit checkout master &&\n \tmkdir sub &&\n \techo master >sub/sub &&\n@@ -384,11 +382,11 @@ test_expect_success PERL 'setup change in subdirectory' '\n '\n \n run_dir_diff_test () {\n-\ttest_expect_success PERL \"$1 --no-symlinks\" \"\n+\ttest_expect_success \"$1 --no-symlinks\" \"\n \t\tsymlinks=--no-symlinks &&\n \t\t$2\n \t\"\n-\ttest_expect_success PERL,SYMLINKS \"$1 --symlinks\" \"\n+\ttest_expect_success SYMLINKS \"$1 --symlinks\" \"\n \t\tsymlinks=--symlinks &&\n \t\t$2\n \t\"\n@@ -510,7 +508,7 @@ do\n done >actual\n EOF\n \n-test_expect_success PERL,SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' '\n+test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' '\n \tcat >expect <<-EOF &&\n \tfile\n \t$PWD/file\n@@ -547,7 +545,7 @@ write_script modify-file <<\\EOF\n echo \"new content\" >file\n EOF\n \n-test_expect_success PERL 'difftool --no-symlinks does not overwrite working tree file ' '\n+test_expect_success 'difftool --no-symlinks does not overwrite working tree file ' '\n \techo \"orig content\" >file &&\n \tgit difftool --dir-diff --no-symlinks --extcmd \"$PWD/modify-file\" branch &&\n \techo \"new content\" >expect &&\n@@ -560,7 +558,7 @@ echo \"tmp content\" >\"$2/file\" &&\n echo \"$2\" >tmpdir\n EOF\n \n-test_expect_success PERL 'difftool --no-symlinks detects conflict ' '\n+test_expect_success 'difftool --no-symlinks detects conflict ' '\n \t(\n \t\tTMPDIR=$TRASH_DIRECTORY &&\n \t\texport TMPDIR &&\n@@ -573,7 +571,7 @@ test_expect_success PERL 'difftool --no-symlinks detects conflict ' '\n \t)\n '\n \n-test_expect_success PERL 'difftool properly honors gitlink and core.worktree' '\n+test_expect_success 'difftool properly honors gitlink and core.worktree' '\n \tgit submodule add ./. submod/ule &&\n \ttest_config -C submod/ule diff.tool checktrees &&\n \ttest_config -C submod/ule difftool.checktrees.cmd '\\''\n@@ -587,7 +585,7 @@ test_expect_success PERL 'difftool properly honors gitlink and core.worktree' '\n \t)\n '\n \n-test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' '\n+test_expect_success SYMLINKS 'difftool --dir-diff symlinked directories' '\n \tgit init dirlinks &&\n \t(\n \t\tcd dirlinks &&\n-- \n2.11.0.windows.3\n"}]}