* [PATCH 0/2] Show Git Mailing List: a builtin difftool @ 2016-11-22 17:01 Johannes Schindelin 2016-11-22 17:01 ` [PATCH 1/2] difftool: add the builtin Johannes Schindelin ` (2 more replies) 0 siblings, 3 replies; 86+ messages in thread From: Johannes Schindelin @ 2016-11-22 17:01 UTC (permalink / raw) To: git; +Cc: Junio C Hamano I have been working on the builtin difftool for a little over a week, for two reasons: 1. Perl is really not native on Windows. Not only is there a performance penalty to be paid just for running Perl scripts, we also have to deal with the fact that users may have different Perl installations, with different options, and some other Perl installation may decide to set PERL5LIB globally, wreaking havoc with Git for Windows' Perl (which we have to use because almost all other Perl distributions lack the Subversion bindings we need for `git svn`). 2. Perl makes for a rather large reason that Git for Windows' installer weighs in with >30MB. While one Perl script less does not relieve us of that burden, it is one step in the right direction. This pair of patches serves two purposes: to ask for reviews, and to show what I plan to release as part of Git for Windows v2.11.0 (which is due this Thursday, if Git v2.11.0 is released tomorrow, as planned). The second patch really only explains how I will make sure that the builtin difftool will only affect users who want to opt in to testing. Johannes Schindelin (2): difftool: add the builtin difftool: add a feature flag for the builtin vs scripted version .gitignore | 2 + Makefile | 1 + builtin.h | 1 + builtin/builtin-difftool.c | 680 +++++++++++++++++++++++++++++++++++++++++++++ git-difftool.perl | 7 + git.c | 21 ++ 6 files changed, 712 insertions(+) create mode 100644 builtin/builtin-difftool.c base-commit: 1310affe024fba407bff55dbe65cd6d670c8a32d Published-As: https://github.com/dscho/git/releases/tag/builtin-difftool-v1 Fetch-It-Via: git fetch https://github.com/dscho/git builtin-difftool-v1 -- 2.10.1.583.g721a9e0 ^ permalink raw reply [flat|nested] 86+ messages in thread
* [PATCH 1/2] difftool: add the builtin 2016-11-22 17:01 [PATCH 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin @ 2016-11-22 17:01 ` Johannes Schindelin 2016-11-23 8:08 ` David Aguilar 2016-11-22 17:01 ` [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version Johannes Schindelin 2016-11-23 22:03 ` [PATCH v2 0/1] Show Git Mailing List: a builtin difftool Johannes Schindelin 2 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2016-11-22 17:01 UTC (permalink / raw) To: git; +Cc: Junio C Hamano This adds a builtin difftool that represents a conversion of the current Perl script version of the difftool. The motivation is that Perl scripts are not at all native on Windows, and that `git difftool` therefore is pretty slow on that platform, when there is no good reason for it to be slow. In addition, Perl does not really have access to Git's internals. That means that any script will always have to jump through unnecessary hoops. The current version of the builtin difftool does not, however, make full use of the internals but instead chooses to spawn a couple of Git processes, still, to make for an easier conversion. There remains a lot of room for improvement, left for a later date. Note: the original difftool is still called by `git difftool`. To get the new, experimental version, call `git builtin-difftool`. The reason: this new, experimental, builtin difftool will be shipped as part of Git for Windows v2.11.0, to allow for easier large-scale testing. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- .gitignore | 1 + Makefile | 1 + builtin.h | 1 + builtin/builtin-difftool.c | 680 +++++++++++++++++++++++++++++++++++++++++++++ git.c | 1 + 5 files changed, 684 insertions(+) create mode 100644 builtin/builtin-difftool.c diff --git a/.gitignore b/.gitignore index 05cb58a..4f54531 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,7 @@ /git-diff-tree /git-difftool /git-difftool--helper +/git-builtin-difftool /git-describe /git-fast-export /git-fast-import diff --git a/Makefile b/Makefile index f53fcc9..f764174 100644 --- a/Makefile +++ b/Makefile @@ -888,6 +888,7 @@ BUILTIN_OBJS += builtin/diff-files.o BUILTIN_OBJS += builtin/diff-index.o BUILTIN_OBJS += builtin/diff-tree.o BUILTIN_OBJS += builtin/diff.o +BUILTIN_OBJS += builtin/builtin-difftool.o BUILTIN_OBJS += builtin/fast-export.o BUILTIN_OBJS += builtin/fetch-pack.o BUILTIN_OBJS += builtin/fetch.o diff --git a/builtin.h b/builtin.h index b9122bc..409a61e 100644 --- a/builtin.h +++ b/builtin.h @@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix); extern int cmd_diff_index(int argc, const char **argv, const char *prefix); extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); +extern int cmd_builtin_difftool(int argc, const char **argv, const char *prefix); extern int cmd_fast_export(int argc, const char **argv, const char *prefix); extern int cmd_fetch(int argc, const char **argv, const char *prefix); extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix); diff --git a/builtin/builtin-difftool.c b/builtin/builtin-difftool.c new file mode 100644 index 0000000..9feefcd --- /dev/null +++ b/builtin/builtin-difftool.c @@ -0,0 +1,680 @@ +/* + * "git difftool" builtin command + * + * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible + * git-difftool--helper script. + * + * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git. + * The GIT_DIFF* variables are exported for use by git-difftool--helper. + * + * Any arguments that are unknown to this script are forwarded to 'git diff'. + * + * Copyright (C) 2016 Johannes Schindelin + */ +#include "cache.h" +#include "builtin.h" +#include "parse-options.h" +#include "run-command.h" +#include "argv-array.h" +#include "strbuf.h" +#include "lockfile.h" + +static char *diff_gui_tool; +static int trust_exit_code; + +static const char * const builtin_difftool_usage[] = { + N_("git add [<options>] [--] <pathspec>..."), + NULL +}; + +static int difftool_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "diff.guitool")) { + diff_gui_tool = xstrdup(value); + return 0; + } + + if (!strcmp(var, "difftool.trustexitcode")) { + trust_exit_code = git_config_bool(var, value); + return 0; + } + + return git_default_config(var, value, cb); +} + +static int print_tool_help(void) +{ + const char *argv[] = { "mergetool", "--tool-help=diff", NULL }; + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int parse_index_info(char *p, int *mode1, int *mode2, + struct object_id *oid1, struct object_id *oid2, + char *status) +{ + if (*p != ':') + return error("expected ':', got '%c'", *p); + *mode1 = (int)strtol(p + 1, &p, 8); + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + *mode2 = (int)strtol(p + 1, &p, 8); + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + if (get_oid_hex(++p, oid1)) + return error("expected object ID, got '%s'", p + 1); + p += GIT_SHA1_HEXSZ; + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + if (get_oid_hex(++p, oid2)) + return error("expected object ID, got '%s'", p + 1); + p += GIT_SHA1_HEXSZ; + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + *status = *++p; + if (!status || p[1]) + return error("unexpected trailer: '%s'", p); + return 0; +} + +/* + * Remove any trailing slash from $workdir + * before starting to avoid double slashes in symlink targets. + */ +static void add_path(struct strbuf *buf, size_t base_len, const char *path) +{ + strbuf_setlen(buf, base_len); + if (buf->len && buf->buf[buf->len - 1] != '/') + strbuf_addch(buf, '/'); + strbuf_addstr(buf, path); +} + +/* + * Determine whether we can simply reuse the file in the worktree. + */ +static int use_wt_file(const char *workdir, const char *name, + struct object_id *oid) +{ + struct strbuf buf = STRBUF_INIT; + struct stat st; + int use = 0; + + strbuf_addstr(&buf, workdir); + add_path(&buf, buf.len, name); + + if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) { + struct object_id wt_oid; + int fd = open(buf.buf, O_RDONLY); + + if (!index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) { + if (is_null_oid(oid)) { + oidcpy(oid, &wt_oid); + use = 1; + } else if (!oidcmp(oid, &wt_oid)) + use = 1; + } + } + + strbuf_release(&buf); + + return use; +} + +struct working_tree_entry { + struct hashmap_entry entry; + char path[FLEX_ARRAY]; +}; + +static int working_tree_entry_cmp(struct working_tree_entry *a, + struct working_tree_entry *b, void *keydata) +{ + return strcmp(a->path, b->path); +} + +/* + * The `left` and `right` entries hold paths for the symlinks hashmap, + * and a SHA-1 surrounded by brief text for submodules. + */ +struct pair_entry { + struct hashmap_entry entry; + char left[PATH_MAX], right[PATH_MAX]; + const char path[FLEX_ARRAY]; +}; + +static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata) +{ + return strcmp(a->path, b->path); +} + +static void add_left_or_right(struct hashmap *map, const char *path, + const char *content, int is_right) +{ + struct pair_entry *e, *existing; + + FLEX_ALLOC_STR(e, path, path); + hashmap_entry_init(e, strhash(path)); + existing = hashmap_get(map, e, NULL); + if (existing) { + free(e); + e = existing; + } else { + e->left[0] = e->right[0] = '\0'; + hashmap_add(map, e); + } + strcpy(is_right ? e->right : e->left, content); +} + +struct path_entry { + struct hashmap_entry entry; + char path[FLEX_ARRAY]; +}; + +int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key) +{ + return strcmp(a->path, key ? key : b->path); +} + +static void changed_files(struct hashmap *result, const char *index_path, + const char *workdir) +{ + struct child_process update_index = CHILD_PROCESS_INIT; + struct child_process diff_files = CHILD_PROCESS_INIT; + struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT; + const char *git_dir = absolute_path(get_git_dir()), *env[] = { + NULL, NULL + }; + FILE *fp; + + strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path); + env[0] = index_env.buf; + + argv_array_pushl(&update_index.args, + "--git-dir", git_dir, "--work-tree", workdir, + "update-index", "--really-refresh", "-q", + "--unmerged", NULL); + update_index.no_stdin = 1; + update_index.no_stdout = 1; + update_index.no_stderr = 1; + update_index.git_cmd = 1; + update_index.use_shell = 0; + update_index.clean_on_exit = 1; + update_index.dir = workdir; + update_index.env = env; + /* Ignore any errors of update-index */ + run_command(&update_index); + + argv_array_pushl(&diff_files.args, + "--git-dir", git_dir, "--work-tree", workdir, + "diff-files", "--name-only", "-z", NULL); + diff_files.no_stdin = 1; + diff_files.git_cmd = 1; + diff_files.use_shell = 0; + diff_files.clean_on_exit = 1; + diff_files.out = -1; + diff_files.dir = workdir; + diff_files.env = env; + if (start_command(&diff_files)) + die("could not obtain raw diff"); + fp = xfdopen(diff_files.out, "r"); + while (!strbuf_getline_nul(&buf, fp)) { + struct path_entry *entry; + FLEX_ALLOC_STR(entry, path, buf.buf); + hashmap_entry_init(entry, strhash(buf.buf)); + hashmap_add(result, entry); + } + if (finish_command(&diff_files)) + die("diff-files did not exit properly"); + strbuf_release(&index_env); + strbuf_release(&buf); +} + +#include "dir.h" + +static NORETURN void exit_cleanup(const char *tmpdir, int exit_code) +{ + struct strbuf buf = STRBUF_INIT; + strbuf_addstr(&buf, tmpdir); + remove_dir_recursively(&buf, 0); + if (exit_code) + warning(_("failed: %d"), exit_code); + exit(exit_code); +} + +static int ensure_leading_directories(char *path) +{ + switch (safe_create_leading_directories(path)) { + case SCLD_OK: + case SCLD_EXISTS: + return 0; + default: + return error(_("could not create leading directories " + "of '%s'"), path); + } +} + +static int run_dir_diff(const char *extcmd, int symlinks, + int argc, const char **argv) +{ + char tmpdir[PATH_MAX]; + struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT; + struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT; + struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT; + struct strbuf wtdir = STRBUF_INIT; + size_t ldir_len, rdir_len, wtdir_len; + struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1); + const char *workdir, *tmp; + int ret = 0, i; + FILE *fp; + struct hashmap working_tree_dups, submodules, symlinks2; + struct hashmap_iter iter; + struct pair_entry *entry; + enum object_type type; + unsigned long size; + struct index_state wtindex; + struct checkout lstate, rstate; + int rc, flags = RUN_GIT_CMD, err = 0; + struct child_process child = CHILD_PROCESS_INIT; + const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL }; + struct hashmap wt_modified, tmp_modified; + int indices_loaded = 0; + + setup_work_tree(); + workdir = get_git_work_tree(); + + /* Setup temp directories */ + tmp = getenv("TMPDIR"); + sprintf(tmpdir, "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp"); + if (!mkdtemp(tmpdir)) + return error("could not create temporary directory"); + strbuf_addf(&ldir, "%s/left/", tmpdir); + strbuf_addf(&rdir, "%s/right/", tmpdir); + strbuf_addstr(&wtdir, workdir); + if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1])) + strbuf_addch(&wtdir, '/'); + mkdir(ldir.buf, 0777); + mkdir(rdir.buf, 0777); + + memset(&wtindex, 0, sizeof(wtindex)); + + memset(&lstate, 0, sizeof(lstate)); + lstate.base_dir = ldir.buf; + lstate.base_dir_len = ldir.len; + lstate.force = 1; + memset(&rstate, 0, sizeof(rstate)); + rstate.base_dir = rdir.buf; + rstate.base_dir_len = rdir.len; + rstate.force = 1; + + ldir_len = ldir.len; + rdir_len = rdir.len; + wtdir_len = wtdir.len; + + hashmap_init(&working_tree_dups, + (hashmap_cmp_fn)working_tree_entry_cmp, 0); + hashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0); + hashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0); + + child.no_stdin = 1; + child.git_cmd = 1; + child.use_shell = 0; + child.clean_on_exit = 1; + child.out = -1; + argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z", + NULL); + for (i = 0; i < argc; i++) + argv_array_push(&child.args, argv[i]); + if (start_command(&child)) + die("could not obtain raw diff"); + fp = xfdopen(child.out, "r"); + + /* Build index info for left and right sides of the diff */ + while (!strbuf_getline_nul(&info, fp)) { + int lmode, rmode; + struct object_id loid, roid; + char status; + const char *src_path, *dst_path; + size_t src_path_len, dst_path_len; + + if (starts_with(info.buf, "::")) + die(N_("combined diff formats('-c' and '--cc') are " + "not supported in\n" + "directory diff mode('-d' and '--dir-diff').")); + + if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid, + &status)) + break; + if (strbuf_getline_nul(&lpath, fp)) + break; + src_path = lpath.buf; + src_path_len = lpath.len; + + if (status != 'C' && status != 'R') { + dst_path = src_path; + dst_path_len = src_path_len; + } else { + if (strbuf_getline_nul(&rpath, fp)) + break; + dst_path = rpath.buf; + dst_path_len = rpath.len; + } + + if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) { + strbuf_reset(&buf); + strbuf_addf(&buf, "Subproject commit %s", + oid_to_hex(&loid)); + add_left_or_right(&submodules, src_path, buf.buf, 0); + strbuf_reset(&buf); + strbuf_addf(&buf, "Subproject commit %s", + oid_to_hex(&roid)); + if (!oidcmp(&loid, &roid)) + strbuf_addstr(&buf, "-dirty"); + add_left_or_right(&submodules, dst_path, buf.buf, 1); + continue; + } + + if (S_ISLNK(lmode)) { + char *content = read_sha1_file(loid.hash, &type, &size); + add_left_or_right(&symlinks2, src_path, content, 0); + free(content); + } + + if (S_ISLNK(rmode)) { + char *content = read_sha1_file(roid.hash, &type, &size); + add_left_or_right(&symlinks2, dst_path, content, 1); + free(content); + } + + if (lmode && status != 'C') { + ce->ce_mode = lmode; + oidcpy(&ce->oid, &loid); + strcpy(ce->name, src_path); + ce->ce_namelen = src_path_len; + if (checkout_entry(ce, &lstate, NULL)) + return error("could not write '%s'", src_path); + } + + if (rmode) { + struct working_tree_entry *entry; + + /* Avoid duplicate working_tree entries */ + FLEX_ALLOC_STR(entry, path, dst_path); + hashmap_entry_init(entry, strhash(dst_path)); + if (hashmap_get(&working_tree_dups, entry, NULL)) { + free(entry); + continue; + } + hashmap_add(&working_tree_dups, entry); + + if (!use_wt_file(workdir, dst_path, &roid)) { + ce->ce_mode = rmode; + oidcpy(&ce->oid, &roid); + strcpy(ce->name, dst_path); + ce->ce_namelen = dst_path_len; + if (checkout_entry(ce, &rstate, NULL)) + return error("could not write '%s'", + dst_path); + } else if (!is_null_oid(&roid)) { + /* + * Changes in the working tree need special + * treatment since they are not part of the + * index. + */ + struct cache_entry *ce2 = + make_cache_entry(rmode, roid.hash, + dst_path, 0, 0); + ce_mode_from_stat(ce2, rmode); + + add_index_entry(&wtindex, ce2, + ADD_CACHE_JUST_APPEND); + + add_path(&wtdir, wtdir_len, dst_path); + add_path(&rdir, rdir_len, dst_path); + if (ensure_leading_directories(rdir.buf)) + return error("could not create " + "directory for '%s'", + dst_path); + if (symlinks) { + if (symlink(wtdir.buf, rdir.buf)) { + ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf); + goto finish; + } + } else { + struct stat st; + if (stat(wtdir.buf, &st)) + st.st_mode = 0644; + if (copy_file(rdir.buf, wtdir.buf, + st.st_mode)) { + ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf); + goto finish; + } + } + } + } + } + if (finish_command(&child)) { + ret = error("error occurred running diff --raw"); + goto finish; + } + + /* + * Changes to submodules require special treatment.This loop writes a + * temporary file to both the left and right directories to show the + * change in the recorded SHA1 for the submodule. + */ + hashmap_iter_init(&submodules, &iter); + while ((entry = hashmap_iter_next(&iter))) { + if (*entry->left) { + add_path(&ldir, ldir_len, entry->path); + ensure_leading_directories(ldir.buf); + write_file(ldir.buf, "%s", entry->left); + } + if (*entry->right) { + add_path(&rdir, rdir_len, entry->path); + ensure_leading_directories(rdir.buf); + write_file(rdir.buf, "%s", entry->right); + } + } + + /* + * Symbolic links require special treatment.The standard "git diff" + * shows only the link itself, not the contents of the link target. + * This loop replicates that behavior. + */ + hashmap_iter_init(&symlinks2, &iter); + while ((entry = hashmap_iter_next(&iter))) { + if (*entry->left) { + add_path(&ldir, ldir_len, entry->path); + ensure_leading_directories(ldir.buf); + write_file(ldir.buf, "%s", entry->left); + } + if (*entry->right) { + add_path(&rdir, rdir_len, entry->path); + ensure_leading_directories(rdir.buf); + write_file(rdir.buf, "%s", entry->right); + } + } + + strbuf_release(&buf); + + strbuf_setlen(&ldir, ldir_len); + helper_argv[1] = ldir.buf; + strbuf_setlen(&rdir, rdir_len); + helper_argv[2] = rdir.buf; + + if (extcmd) { + helper_argv[0] = extcmd; + flags = 0; + } else + setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1); + rc = run_command_v_opt(helper_argv, flags); + + /* + * If the diff includes working copy files and those + * files were modified during the diff, then the changes + * should be copied back to the working tree. + * Do not copy back files when symlinks are used and the + * external tool did not replace the original link with a file. + * + * These hashes are loaded lazily since they aren't needed + * in the common case of --symlinks and the difftool updating + * files through the symlink. + */ + hashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp, + wtindex.cache_nr); + hashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp, + wtindex.cache_nr); + + for (i = 0; i < wtindex.cache_nr; i++) { + struct hashmap_entry dummy; + const char *name = wtindex.cache[i]->name; + struct stat st; + + add_path(&rdir, rdir_len, name); + if (lstat(rdir.buf, &st)) + continue; + + if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode)) + continue; + + if (!indices_loaded) { + static struct lock_file lock; + strbuf_reset(&buf); + strbuf_addf(&buf, "%s/wtindex", tmpdir); + if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 || + write_locked_index(&wtindex, &lock, COMMIT_LOCK)) { + ret = error("could not write %s", buf.buf); + rollback_lock_file(&lock); + goto finish; + } + changed_files(&wt_modified, buf.buf, workdir); + strbuf_setlen(&rdir, rdir_len); + changed_files(&tmp_modified, buf.buf, rdir.buf); + add_path(&rdir, rdir_len, name); + indices_loaded = 1; + } + + hashmap_entry_init(&dummy, strhash(name)); + if (hashmap_get(&tmp_modified, &dummy, name)) { + add_path(&wtdir, wtdir_len, name); + if (hashmap_get(&wt_modified, &dummy, name)) { + warning(_("both files modified: '%s' and '%s'."), + wtdir.buf, rdir.buf); + warning(_("working tree file has been left.")); + warning(""); + err = 1; + } else if (unlink(wtdir.buf) || + copy_file(wtdir.buf, rdir.buf, st.st_mode)) + warning_errno(_("could not copy '%s' to '%s'"), + rdir.buf, wtdir.buf); + } + } + + if (err) { + warning(_("temporary files exist in '%s'."), tmpdir); + warning(_("you may want to cleanup or recover these.")); + exit(1); + } else + exit_cleanup(tmpdir, rc); + +finish: + free(ce); + strbuf_release(&ldir); + strbuf_release(&rdir); + strbuf_release(&wtdir); + strbuf_release(&buf); + + return ret; +} + +static int run_file_diff(int prompt, int argc, const char **argv) +{ + struct argv_array args = ARGV_ARRAY_INIT; + const char *env[] = { + "GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL, + NULL + }; + int ret = 0, i; + + if (prompt > 0) + env[2] = "GIT_DIFFTOOL_PROMPT=true"; + else if (!prompt) + env[2] = "GIT_DIFFTOOL_NO_PROMPT=true"; + + argv_array_push(&args, "diff"); + for (i = 0; i < argc; i++) + argv_array_push(&args, argv[i]); + ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, NULL, env); + exit(ret); +} + +int cmd_builtin_difftool(int argc, const char ** argv, const char * prefix) +{ + int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0, + tool_help = 0; + static char *difftool_cmd = NULL, *extcmd = NULL; + + struct option builtin_difftool_options[] = { + OPT_BOOL('g', "gui", &use_gui_tool, + N_("use `diff.guitool` instead of `diff.tool`")), + OPT_BOOL('d', "dir-diff", &dir_diff, + N_("perform a full-directory diff")), + { OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL, + N_("do not prompt before launching a diff tool"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0}, + { OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL, + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN, + NULL, 1 }, + OPT_BOOL(0, "symlinks", &symlinks, + N_("use symlinks in dir-diff mode")), + OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"), + N_("use the specified diff tool")), + OPT_BOOL(0, "tool-help", &tool_help, + N_("print a list of diff tools that may be used with " + "`--tool`")), + OPT_BOOL(0, "trust-exit-code", &trust_exit_code, + N_("make 'git-difftool' exit when an invoked diff " + "tool returns a non - zero exit code")), + OPT_STRING('x', "extcmd", &extcmd, N_("<command>"), + N_("specify a custom command for viewing diffs")), + OPT_END() + }; + + symlinks = has_symlinks; + + git_config(difftool_config, NULL); + + argc = parse_options(argc, argv, prefix, builtin_difftool_options, + builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_DASHDASH); + + if (tool_help) + return print_tool_help(); + + if (use_gui_tool && diff_gui_tool && *diff_gui_tool) + setenv("GIT_DIFF_TOOL", diff_gui_tool, 1); + else if (difftool_cmd) { + if (*difftool_cmd) + setenv("GIT_DIFF_TOOL", difftool_cmd, 1); + else + die(_("no <tool> given for --tool=<tool>")); + } + + if (extcmd) { + if (*extcmd) + setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1); + else + die(_("no <cmd> given for --extcmd=<cmd>")); + } + + setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE", + trust_exit_code ? "true" : "false", 1); + + /* + * In directory diff mode, 'git-difftool--helper' is called once + * to compare the a / b directories.In file diff mode, 'git diff' + * will invoke a separate instance of 'git-difftool--helper' for + * each file that changed. + */ + if (dir_diff) + return run_dir_diff(extcmd, symlinks, argc, argv); + return run_file_diff(prompt, argc, argv); +} diff --git a/git.c b/git.c index efa1059..eaa0f67 100644 --- a/git.c +++ b/git.c @@ -424,6 +424,7 @@ static struct cmd_struct commands[] = { { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE }, { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, + { "builtin-difftool", cmd_builtin_difftool, RUN_SETUP | NEED_WORK_TREE }, { "fast-export", cmd_fast_export, RUN_SETUP }, { "fetch", cmd_fetch, RUN_SETUP }, { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, -- 2.10.1.583.g721a9e0 ^ permalink raw reply related [flat|nested] 86+ messages in thread
* Re: [PATCH 1/2] difftool: add the builtin 2016-11-22 17:01 ` [PATCH 1/2] difftool: add the builtin Johannes Schindelin @ 2016-11-23 8:08 ` David Aguilar 2016-11-23 11:34 ` Johannes Schindelin 0 siblings, 1 reply; 86+ messages in thread From: David Aguilar @ 2016-11-23 8:08 UTC (permalink / raw) To: Johannes Schindelin; +Cc: git, Junio C Hamano On Tue, Nov 22, 2016 at 06:01:23PM +0100, Johannes Schindelin wrote: > This adds a builtin difftool that represents a conversion of the current > Perl script version of the difftool. > > The motivation is that Perl scripts are not at all native on Windows, > and that `git difftool` therefore is pretty slow on that platform, when > there is no good reason for it to be slow. > > In addition, Perl does not really have access to Git's internals. That > means that any script will always have to jump through unnecessary > hoops. Nice! > The current version of the builtin difftool does not, however, make full > use of the internals but instead chooses to spawn a couple of Git > processes, still, to make for an easier conversion. There remains a lot > of room for improvement, left for a later date. > > Note: the original difftool is still called by `git difftool`. To get the > new, experimental version, call `git builtin-difftool`. The reason: this > new, experimental, builtin difftool will be shipped as part of Git for > Windows v2.11.0, to allow for easier large-scale testing. I like this plan. I was going to ask for an environment variable (to preset in git-cola) but since Git for Windows is handling it then everyone benefits. > diff --git a/builtin/builtin-difftool.c b/builtin/builtin-difftool.c > new file mode 100644 > index 0000000..9feefcd > --- /dev/null > +++ b/builtin/builtin-difftool.c > @@ -0,0 +1,680 @@ > +/* > + * "git difftool" builtin command > + * > + * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible > + * git-difftool--helper script. > + * > + * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git. > + * The GIT_DIFF* variables are exported for use by git-difftool--helper. > + * > + * Any arguments that are unknown to this script are forwarded to 'git diff'. > + * > + * Copyright (C) 2016 Johannes Schindelin > + */ > +#include "cache.h" > +#include "builtin.h" > +#include "parse-options.h" > +#include "run-command.h" > +#include "argv-array.h" > +#include "strbuf.h" > +#include "lockfile.h" > + > +static char *diff_gui_tool; > +static int trust_exit_code; > + > +static const char * const builtin_difftool_usage[] = { > + N_("git add [<options>] [--] <pathspec>..."), > + NULL > +}; The usage should probably say "difftool" (or "builtin-difftool"). > [...] > +static void changed_files(struct hashmap *result, const char *index_path, > + const char *workdir) > +{ > +[...] > +} > + > +#include "dir.h" Can this mid-file #include go to the top of the file? > +static int run_dir_diff(const char *extcmd, int symlinks, > + int argc, const char **argv) > +{ > + char tmpdir[PATH_MAX]; > + struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT; > + struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT; > + struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT; > + struct strbuf wtdir = STRBUF_INIT; > + size_t ldir_len, rdir_len, wtdir_len; > + struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1); > + const char *workdir, *tmp; > + int ret = 0, i; > + FILE *fp; > + struct hashmap working_tree_dups, submodules, symlinks2; > + struct hashmap_iter iter; > + struct pair_entry *entry; > + enum object_type type; > + unsigned long size; > + struct index_state wtindex; > + struct checkout lstate, rstate; > + int rc, flags = RUN_GIT_CMD, err = 0; > + struct child_process child = CHILD_PROCESS_INIT; > + const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL }; > + struct hashmap wt_modified, tmp_modified; > + int indices_loaded = 0; > + > + setup_work_tree(); > + workdir = get_git_work_tree(); > + > + /* Setup temp directories */ > + tmp = getenv("TMPDIR"); > + sprintf(tmpdir, "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp"); Maybe snprintf instead? getenv() won't return anything longer than PATH_MAX for most users, but users are weird. > + if (!mkdtemp(tmpdir)) > + return error("could not create temporary directory"); Mention the tmpdir here? > + strbuf_addf(&ldir, "%s/left/", tmpdir); > + strbuf_addf(&rdir, "%s/right/", tmpdir); > + strbuf_addstr(&wtdir, workdir); > + if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1])) > + strbuf_addch(&wtdir, '/'); > + mkdir(ldir.buf, 0777); > + mkdir(rdir.buf, 0777); Seeing the perl mkpath() default 0777 spelled out this way makes me wonder whether 0700 would be safer. The mkdtemp() above is already using 0700 so it's ok, but it might be worth making it consistent (later, perhaps). > [...] > +int cmd_builtin_difftool(int argc, const char ** argv, const char * prefix) > +{ > + int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0, > + tool_help = 0; > + static char *difftool_cmd = NULL, *extcmd = NULL; > + > + struct option builtin_difftool_options[] = { > + OPT_BOOL('g', "gui", &use_gui_tool, > + N_("use `diff.guitool` instead of `diff.tool`")), > + OPT_BOOL('d', "dir-diff", &dir_diff, > + N_("perform a full-directory diff")), > + { OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL, > + N_("do not prompt before launching a diff tool"), > + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0}, > + { OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL, > + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN, > + NULL, 1 }, > + OPT_BOOL(0, "symlinks", &symlinks, > + N_("use symlinks in dir-diff mode")), > + OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"), > + N_("use the specified diff tool")), > + OPT_BOOL(0, "tool-help", &tool_help, > + N_("print a list of diff tools that may be used with " > + "`--tool`")), > + OPT_BOOL(0, "trust-exit-code", &trust_exit_code, > + N_("make 'git-difftool' exit when an invoked diff " > + "tool returns a non - zero exit code")), > + OPT_STRING('x', "extcmd", &extcmd, N_("<command>"), > + N_("specify a custom command for viewing diffs")), > + OPT_END() > + }; > + > + symlinks = has_symlinks; > + > + git_config(difftool_config, NULL); > + > + argc = parse_options(argc, argv, prefix, builtin_difftool_options, > + builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN | > + PARSE_OPT_KEEP_DASHDASH); > + > + if (tool_help) > + return print_tool_help(); > + > + if (use_gui_tool && diff_gui_tool && *diff_gui_tool) > + setenv("GIT_DIFF_TOOL", diff_gui_tool, 1); > + else if (difftool_cmd) { > + if (*difftool_cmd) > + setenv("GIT_DIFF_TOOL", difftool_cmd, 1); > + else > + die(_("no <tool> given for --tool=<tool>")); > + } > + > + if (extcmd) { > + if (*extcmd) > + setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1); > + else > + die(_("no <cmd> given for --extcmd=<cmd>")); > + } > + > + setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE", > + trust_exit_code ? "true" : "false", 1); > + > + /* > + * In directory diff mode, 'git-difftool--helper' is called once > + * to compare the a / b directories.In file diff mode, 'git diff' > + * will invoke a separate instance of 'git-difftool--helper' for > + * each file that changed. > + */ Missing space after "." in the comment above. > + if (dir_diff) > + return run_dir_diff(extcmd, symlinks, argc, argv); > + return run_file_diff(prompt, argc, argv); > +} > diff --git a/git.c b/git.c > index efa1059..eaa0f67 100644 > --- a/git.c > +++ b/git.c > @@ -424,6 +424,7 @@ static struct cmd_struct commands[] = { > { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE }, > { "diff-index", cmd_diff_index, RUN_SETUP }, > { "diff-tree", cmd_diff_tree, RUN_SETUP }, > + { "builtin-difftool", cmd_builtin_difftool, RUN_SETUP | NEED_WORK_TREE }, > { "fast-export", cmd_fast_export, RUN_SETUP }, > { "fetch", cmd_fetch, RUN_SETUP }, > { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, This isn't alphabetical anymore, but it actually is if you consider that the final plan is to change "builtin-difftool" to "difftool". If we want to minimize that future diff we could name cmd_builtin_difftool() as cmd_difftool() for consistency now so that the future commit only needs to tweak the string here. -- David ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH 1/2] difftool: add the builtin 2016-11-23 8:08 ` David Aguilar @ 2016-11-23 11:34 ` Johannes Schindelin 0 siblings, 0 replies; 86+ messages in thread From: Johannes Schindelin @ 2016-11-23 11:34 UTC (permalink / raw) To: David Aguilar; +Cc: git, Junio C Hamano Hi David, On Wed, 23 Nov 2016, David Aguilar wrote: > On Tue, Nov 22, 2016 at 06:01:23PM +0100, Johannes Schindelin wrote: > > > +static const char * const builtin_difftool_usage[] = { > > + N_("git add [<options>] [--] <pathspec>..."), > > + NULL > > +}; > > The usage should probably say "difftool" (or "builtin-difftool"). Ah, my dirty secret was spilled. I copy-edited this. *pours ashes over his head* > > [...] > > +static void changed_files(struct hashmap *result, const char *index_path, > > + const char *workdir) > > +{ > > +[...] > > +} > > + > > +#include "dir.h" > > Can this mid-file #include go to the top of the file? Yep, thanks. In case you are interested: You probably guessed it, it was left for a later clean-up. I worked a bit over the last weeks on getting Git to build in Visual Studio, to be able to benefit from its quite nice features (I was always a fan of Visual Studio, long before I started working at Microsoft). I used the conversion of the difftool as an excuse to make use of this myself: I did the entire conversion in Visual Studio, reverting to the old, tedious command-line driven workflow to fix the bugs identified by t7800-difftool.sh. > > +static int run_dir_diff(const char *extcmd, int symlinks, > > + int argc, const char **argv) > > +{ > > + char tmpdir[PATH_MAX]; > > + struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT; > > + struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT; > > + struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT; > > + struct strbuf wtdir = STRBUF_INIT; > > + size_t ldir_len, rdir_len, wtdir_len; > > + struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1); > > + const char *workdir, *tmp; > > + int ret = 0, i; > > + FILE *fp; > > + struct hashmap working_tree_dups, submodules, symlinks2; > > + struct hashmap_iter iter; > > + struct pair_entry *entry; > > + enum object_type type; > > + unsigned long size; > > + struct index_state wtindex; > > + struct checkout lstate, rstate; > > + int rc, flags = RUN_GIT_CMD, err = 0; > > + struct child_process child = CHILD_PROCESS_INIT; > > + const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL }; > > + struct hashmap wt_modified, tmp_modified; > > + int indices_loaded = 0; > > + > > + setup_work_tree(); > > + workdir = get_git_work_tree(); > > + > > + /* Setup temp directories */ > > + tmp = getenv("TMPDIR"); > > + sprintf(tmpdir, "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp"); > > Maybe snprintf instead? > > getenv() won't return anything longer than PATH_MAX for most > users, but users are weird. True. > > + if (!mkdtemp(tmpdir)) > > + return error("could not create temporary directory"); > > Mention the tmpdir here? Sure thing. > > + strbuf_addf(&ldir, "%s/left/", tmpdir); > > + strbuf_addf(&rdir, "%s/right/", tmpdir); > > + strbuf_addstr(&wtdir, workdir); > > + if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1])) > > + strbuf_addch(&wtdir, '/'); > > + mkdir(ldir.buf, 0777); > > + mkdir(rdir.buf, 0777); > > Seeing the perl mkpath() default 0777 spelled out this way > makes me wonder whether 0700 would be safer. > > The mkdtemp() above is already using 0700 so it's ok, but it > might be worth making it consistent (later, perhaps). Ah, of course! I stupidly imitated other `mkdir()` calls elsewhere, but they refer to directories within the Git worktree... > > + /* > > + * In directory diff mode, 'git-difftool--helper' is called once > > + * to compare the a / b directories.In file diff mode, 'git diff' > > + * will invoke a separate instance of 'git-difftool--helper' for > > + * each file that changed. > > + */ > > Missing space after "." in the comment above. Yep. It was two spaces and I deleted one too many (we are so way past actual print, where the two spaces may have made sense...). > > + if (dir_diff) > > + return run_dir_diff(extcmd, symlinks, argc, argv); > > + return run_file_diff(prompt, argc, argv); > > +} > > diff --git a/git.c b/git.c > > index efa1059..eaa0f67 100644 > > --- a/git.c > > +++ b/git.c > > @@ -424,6 +424,7 @@ static struct cmd_struct commands[] = { > > { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE }, > > { "diff-index", cmd_diff_index, RUN_SETUP }, > > { "diff-tree", cmd_diff_tree, RUN_SETUP }, > > + { "builtin-difftool", cmd_builtin_difftool, RUN_SETUP | NEED_WORK_TREE }, > > { "fast-export", cmd_fast_export, RUN_SETUP }, > > { "fetch", cmd_fetch, RUN_SETUP }, > > { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, > > This isn't alphabetical anymore, but it actually is if you > consider that the final plan is to change "builtin-difftool" to > "difftool". Exactly, that was my thinking. > If we want to minimize that future diff we could name > cmd_builtin_difftool() as cmd_difftool() for consistency now so > that the future commit only needs to tweak the string here. Yes! For the record, this is a left-over from an impatient attempt at avoiding problems with `make` overwriting the Perl version of `git difftool` by the builtin version; I had originally assumed that a list of builtins was generated from parsing git.c or builtin.h, but it turns out that the BUILTIN_OBJS are actually responsible, i.e. the file name. Fixed. Thank you for your review! Dscho ^ permalink raw reply [flat|nested] 86+ messages in thread
* [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version 2016-11-22 17:01 [PATCH 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin 2016-11-22 17:01 ` [PATCH 1/2] difftool: add the builtin Johannes Schindelin @ 2016-11-22 17:01 ` Johannes Schindelin 2016-11-23 14:51 ` Dennis Kaarsemaker 2016-11-23 22:03 ` [PATCH v2 0/1] Show Git Mailing List: a builtin difftool Johannes Schindelin 2 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2016-11-22 17:01 UTC (permalink / raw) To: git; +Cc: Junio C Hamano The popular difftool command was just converted into a builtin, for better performance on Windows as well as to reduce the number of Perl scripts (so that we may, in the very long run, be able to ship Git for Windows without any Perl interpreter at all). However, it would be sloppy practice to simply switch over from the tried-and-tested (albeit slow) scripted version to the spanking new builtin version that has not seen a whole lot of real-world testing. So let's add a feature flag. If the file `use-builtin-difftool` exists in Git's exec path, Git will now automagically use the builtin version of the difftool, without requiring the user to call `git builtin-difftool <args>`. This comes in particularly handy when the difftool command is used from within scripts. If the file `use-builtin-difftool` is absent from Git's exec path, which is the default, Git will use the scripted version as before. The original idea was to use an environment variable GIT_USE_BUILTIN_DIFFTOOL, but the test suite resets those variables, and we do want to use that feature flag to run the tests with, and without, the feature flag. Besides, the plan is to add an opt-in flag in Git for Windows' installer. If we implemented the feature flag as an environment variable, we would have to modify the user's environment, in order to make the builtin difftool the default when called from Git Bash, Git CMD or third-party tools. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- .gitignore | 1 + git-difftool.perl | 7 +++++++ git.c | 20 ++++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/.gitignore b/.gitignore index 4f54531..91bfd09 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/use-builtin-difftool /GIT-BUILD-OPTIONS /GIT-CFLAGS /GIT-LDFLAGS diff --git a/git-difftool.perl b/git-difftool.perl index a5790d0..28e47d8 100755 --- a/git-difftool.perl +++ b/git-difftool.perl @@ -23,6 +23,13 @@ use File::Temp qw(tempdir); use Getopt::Long qw(:config pass_through); use Git; +if (-e Git::exec_path() . '/use-builtin-difftool') { + unshift(@ARGV, "builtin-difftool"); + unshift(@ARGV, "git"); + exec(@ARGV); + die("Could not execute builtin difftool"); +} + sub usage { my $exitcode = shift; diff --git a/git.c b/git.c index eaa0f67..7a0df7a 100644 --- a/git.c +++ b/git.c @@ -2,6 +2,7 @@ #include "exec_cmd.h" #include "help.h" #include "run-command.h" +#include "dir.h" const char git_usage_string[] = "git [--version] [--help] [-C <path>] [-c name=value]\n" @@ -542,6 +543,22 @@ static void strip_extension(const char **argv) #define strip_extension(cmd) #endif +static int use_builtin_difftool(void) +{ + static int initialized, use; + + if (!initialized) { + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, "%s/%s", git_exec_path(), + "use-builtin-difftool"); + use = file_exists(buf.buf); + strbuf_release(&buf); + initialized = 1; + } + + return use; +} + static void handle_builtin(int argc, const char **argv) { struct argv_array args = ARGV_ARRAY_INIT; @@ -551,6 +568,9 @@ static void handle_builtin(int argc, const char **argv) strip_extension(argv); cmd = argv[0]; + if (!strcmp("difftool", cmd) && use_builtin_difftool()) + cmd = "builtin-difftool"; + /* Turn "git cmd --help" into "git help --exclude-guides cmd" */ if (argc > 1 && !strcmp(argv[1], "--help")) { int i; -- 2.10.1.583.g721a9e0 ^ permalink raw reply related [flat|nested] 86+ messages in thread
* Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version 2016-11-22 17:01 ` [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version Johannes Schindelin @ 2016-11-23 14:51 ` Dennis Kaarsemaker 2016-11-23 17:29 ` Johannes Schindelin 0 siblings, 1 reply; 86+ messages in thread From: Dennis Kaarsemaker @ 2016-11-23 14:51 UTC (permalink / raw) To: Johannes Schindelin, git; +Cc: Junio C Hamano On Tue, 2016-11-22 at 18:01 +0100, Johannes Schindelin wrote: > The original idea was to use an environment variable > GIT_USE_BUILTIN_DIFFTOOL, but the test suite resets those variables, and > we do want to use that feature flag to run the tests with, and without, > the feature flag. > > Besides, the plan is to add an opt-in flag in Git for Windows' > installer. If we implemented the feature flag as an environment > variable, we would have to modify the user's environment, in order to > make the builtin difftool the default when called from Git Bash, Git CMD > or third-party tools. Hi Johannes, Why is this not a normal configuration variable (as in git config difftool.builtin true or something)? It doesn't make much sense to me to introduce a way of configuring git by introducing magic files, when a normal configuration variable would do just fine, and the GfW installer can also set such variables, like it does for the crlf config I believe. -- Dennis Kaarsemaker http://www.kaarsemaker.net ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version 2016-11-23 14:51 ` Dennis Kaarsemaker @ 2016-11-23 17:29 ` Johannes Schindelin 2016-11-23 17:40 ` Junio C Hamano 2016-11-23 22:01 ` Johannes Schindelin 0 siblings, 2 replies; 86+ messages in thread From: Johannes Schindelin @ 2016-11-23 17:29 UTC (permalink / raw) To: Dennis Kaarsemaker; +Cc: git, Junio C Hamano Hi Dennis, On Wed, 23 Nov 2016, Dennis Kaarsemaker wrote: > On Tue, 2016-11-22 at 18:01 +0100, Johannes Schindelin wrote: > > The original idea was to use an environment variable > > GIT_USE_BUILTIN_DIFFTOOL, but the test suite resets those variables, and > > we do want to use that feature flag to run the tests with, and without, > > the feature flag. > > > > Besides, the plan is to add an opt-in flag in Git for Windows' > > installer. If we implemented the feature flag as an environment > > variable, we would have to modify the user's environment, in order to > > make the builtin difftool the default when called from Git Bash, Git CMD > > or third-party tools. > > Why is this not a normal configuration variable (as in git config > difftool.builtin true or something)? It doesn't make much sense to me > to introduce a way of configuring git by introducing magic files, when > a normal configuration variable would do just fine, and the GfW > installer can also set such variables, like it does for the crlf config > I believe. I considered that. Adding a config setting would mean we simply test for it in git-difftool.perl and call the builtin if the setting is active, right? The downside is that we actually *do* go through Perl to do that. Only to go back to a builtin. Which is exactly the thing I intended to avoid. If we do not go through Perl, we have to set up the git directory and parse the config in git.c *just* to figure out whether we want to magically forward difftool to builtin-difftool. That is not only ugly, but has potential side effects I was not willing to risk. In any case, this feature flag will be there only for one or two Git for Windows releases, to give early adopters a chance to send me bug reports about any regressions. To be crystal-clear: I never expected this patch to enter git.git. In that light, I am okay with taking the heat for introducing a temporary, Git for Windows-only feature flag that is implemented as a "does the file <xyz> exist?" test. Ciao, Dscho ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version 2016-11-23 17:29 ` Johannes Schindelin @ 2016-11-23 17:40 ` Junio C Hamano 2016-11-23 18:18 ` Junio C Hamano 2016-11-23 22:01 ` Johannes Schindelin 1 sibling, 1 reply; 86+ messages in thread From: Junio C Hamano @ 2016-11-23 17:40 UTC (permalink / raw) To: Johannes Schindelin; +Cc: Dennis Kaarsemaker, git Johannes Schindelin <Johannes.Schindelin@gmx.de> writes: > The downside is that we actually *do* go through Perl to do that. Only to > go back to a builtin. Which is exactly the thing I intended to avoid. > > If we do not go through Perl, we have to set up the git directory and > parse the config in git.c *just* to figure out whether we want to > magically forward difftool to builtin-difftool. That is not only ugly, but > has potential side effects I was not willing to risk. I won't accept the latter anyway, so do not worry ;-) > In any case, this feature flag will be there only for one or two Git for > Windows releases, to give early adopters a chance to send me bug reports > about any regressions. I think that is sensible. I suspect that for early detection of breakages, you do not need to invent and force people to use a completely new "mechanism" to switch between two implementations. Can't you route the control upon seeing "git difftool" to your experimental "C" difftool and check the configuration there? Then you can decide to run_command() a non-builtin one depending what the configuration says---that way, you would incur cost of spawning Perl only when you need it, no? ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version 2016-11-23 17:40 ` Junio C Hamano @ 2016-11-23 18:18 ` Junio C Hamano 2016-11-23 19:55 ` Johannes Schindelin 0 siblings, 1 reply; 86+ messages in thread From: Junio C Hamano @ 2016-11-23 18:18 UTC (permalink / raw) To: Johannes Schindelin; +Cc: Dennis Kaarsemaker, git Junio C Hamano <gitster@pobox.com> writes: > Can't you route the control upon seeing "git difftool" to your > experimental "C" difftool and check the configuration there? Then > you can decide to run_command() a non-builtin one depending what the > configuration says---that way, you would incur cost of spawning Perl > only when you need it, no? FWIW, the approach taken by 73c2779f42 ("builtin-am: implement skeletal builtin am", 2015-08-04) is what I had in mind when I wrote the above. ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version 2016-11-23 18:18 ` Junio C Hamano @ 2016-11-23 19:55 ` Johannes Schindelin 2016-11-23 20:04 ` Junio C Hamano 0 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2016-11-23 19:55 UTC (permalink / raw) To: Junio C Hamano; +Cc: Dennis Kaarsemaker, git Hi Junio, On Wed, 23 Nov 2016, Junio C Hamano wrote: > Junio C Hamano <gitster@pobox.com> writes: > > > Can't you route the control upon seeing "git difftool" to your > > experimental "C" difftool and check the configuration there? Then > > you can decide to run_command() a non-builtin one depending what the > > configuration says---that way, you would incur cost of spawning Perl > > only when you need it, no? > > FWIW, the approach taken by 73c2779f42 ("builtin-am: implement > skeletal builtin am", 2015-08-04) is what I had in mind when I wrote > the above. Maybe that worked back then. But I doubt it, because checking out that revision, I get this "warning": Makefile:1732: warning: overriding recipe for target 'git-am' Makefile:1696: warning: ignoring old recipe for target 'git-am' Seems like a matter of luck whether the `make` executable you happen to use guesses what we want: to munge git-am.sh into git-am, as opposed to hard-linking git to git-am. Ciao, Dscho ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version 2016-11-23 19:55 ` Johannes Schindelin @ 2016-11-23 20:04 ` Junio C Hamano 0 siblings, 0 replies; 86+ messages in thread From: Junio C Hamano @ 2016-11-23 20:04 UTC (permalink / raw) To: Johannes Schindelin; +Cc: Dennis Kaarsemaker, git Johannes Schindelin <Johannes.Schindelin@gmx.de> writes: > Hi Junio, > > On Wed, 23 Nov 2016, Junio C Hamano wrote: > >> Junio C Hamano <gitster@pobox.com> writes: >> >> > Can't you route the control upon seeing "git difftool" to your >> > experimental "C" difftool and check the configuration there? Then >> > you can decide to run_command() a non-builtin one depending what the >> > configuration says---that way, you would incur cost of spawning Perl >> > only when you need it, no? >> >> FWIW, the approach taken by 73c2779f42 ("builtin-am: implement >> skeletal builtin am", 2015-08-04) is what I had in mind when I wrote >> the above. > > Maybe that worked back then. But I doubt it, because checking out that > revision, I get this "warning": > > Makefile:1732: warning: overriding recipe for target 'git-am' > Makefile:1696: warning: ignoring old recipe for target 'git-am' > > Seems like a matter of luck whether the `make` executable you happen to > use guesses what we want: to munge git-am.sh into git-am, as opposed to > hard-linking git to git-am. You do not need to keep two copies of "git-cmd", though. commands[] table can have an entry "difftool" that points at cmd_difftool(), which switches between run_command("difftool-scripted") or makes a function call to a static difftool_builtin() that you wrote in 1/2. ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version 2016-11-23 17:29 ` Johannes Schindelin 2016-11-23 17:40 ` Junio C Hamano @ 2016-11-23 22:01 ` Johannes Schindelin 1 sibling, 0 replies; 86+ messages in thread From: Johannes Schindelin @ 2016-11-23 22:01 UTC (permalink / raw) To: Dennis Kaarsemaker; +Cc: git, Junio C Hamano Hi Dennis, On Wed, 23 Nov 2016, Johannes Schindelin wrote: > On Wed, 23 Nov 2016, Dennis Kaarsemaker wrote: > > > On Tue, 2016-11-22 at 18:01 +0100, Johannes Schindelin wrote: > > > The original idea was to use an environment variable > > > GIT_USE_BUILTIN_DIFFTOOL, but the test suite resets those variables, and > > > we do want to use that feature flag to run the tests with, and without, > > > the feature flag. > > > > > > Besides, the plan is to add an opt-in flag in Git for Windows' > > > installer. If we implemented the feature flag as an environment > > > variable, we would have to modify the user's environment, in order to > > > make the builtin difftool the default when called from Git Bash, Git CMD > > > or third-party tools. > > > > Why is this not a normal configuration variable (as in git config > > difftool.builtin true or something)? It doesn't make much sense to me > > to introduce a way of configuring git by introducing magic files, when > > a normal configuration variable would do just fine, and the GfW > > installer can also set such variables, like it does for the crlf config > > I believe. > > I considered that. Adding a config setting would mean we simply test for > it in git-difftool.perl and call the builtin if the setting is active, > right? > > The downside is that we actually *do* go through Perl to do that. Only to > go back to a builtin. Which is exactly the thing I intended to avoid. Okay, I reconsidered. Junio's comment about how git-am did it made me rethink the issue: I need not keep the name "difftool" for the script. So what I do now is rename the Perl script to git-legacy-difftool and always read the config in the builtin difftool, handing off to the legacy difftool unless core.useBuiltinDifftool=true. This is an easy way to do it, and a portable and clean blueprint for similar feature-flags in the future. Ciao, Johannes ^ permalink raw reply [flat|nested] 86+ messages in thread
* [PATCH v2 0/1] Show Git Mailing List: a builtin difftool 2016-11-22 17:01 [PATCH 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin 2016-11-22 17:01 ` [PATCH 1/2] difftool: add the builtin Johannes Schindelin 2016-11-22 17:01 ` [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version Johannes Schindelin @ 2016-11-23 22:03 ` Johannes Schindelin 2016-11-23 22:03 ` [PATCH v2 1/1] difftool: add the builtin Johannes Schindelin 2016-11-24 20:55 ` [PATCH v3 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin 2 siblings, 2 replies; 86+ messages in thread From: Johannes Schindelin @ 2016-11-23 22:03 UTC (permalink / raw) To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker I have been working on the builtin difftool for almost two weeks, for two reasons: 1. Perl is really not native on Windows. Not only is there a performance penalty to be paid just for running Perl scripts, we also have to deal with the fact that users may have different Perl installations, with different options, and some other Perl installation may decide to set PERL5LIB globally, wreaking havoc with Git for Windows' Perl (which we have to use because almost all other Perl distributions lack the Subversion bindings we need for `git svn`). 2. Perl makes for a rather large reason that Git for Windows' installer weighs in with >30MB. While one Perl script less does not relieve us of that burden, it is one step in the right direction. This patch serves two purposes: to ask for reviews, and to show what I plan to release as part of Git for Windows v2.11.0 (which is due this coming Wednesday, if Git v2.11.0 is released on Tuesday, as planned). Changes since v1: - fixed the usage (pointed out by David Aguilar) - moved the stray #include "dir.h" to cuddle with the other #include's (also pointed out by David Aguilar) - changed the `sprintf()` call to a much safer `xsnprintf()` call (again, pointed out by David Aguilar) - changed an error message to include the offending path (need I say, pointed out by David Aguilar?) - used more restrictive permissions for the temporary directories (once again, David Aguilar's suggestion) - fixed a comment that lacked a space after a period (another fix thanks to David Aguilar). - switched the opt-in feature flag triggering the use of the builtin difftool to a config variable (this suggestion came from Junio Hamano). - made difftool respect core.symlinks by moving the usage of has_symlinks after the config was parsed. Johannes Schindelin (1): difftool: add the builtin .gitignore | 1 + Makefile | 3 +- builtin.h | 1 + builtin/difftool.c | 692 ++++++++++++++++++++++++++ git-difftool.perl => git-legacy-difftool.perl | 0 git.c | 1 + 6 files changed, 697 insertions(+), 1 deletion(-) create mode 100644 builtin/difftool.c rename git-difftool.perl => git-legacy-difftool.perl (100%) base-commit: 1e37181391e305a7ab0c382ca3c3b2de998d4138 Published-As: https://github.com/dscho/git/releases/tag/builtin-difftool-v2 Fetch-It-Via: git fetch https://github.com/dscho/git builtin-difftool-v2 Interdiff vs v1: diff --git a/.gitignore b/.gitignore index 91bfd09..f96e50e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -/use-builtin-difftool /GIT-BUILD-OPTIONS /GIT-CFLAGS /GIT-LDFLAGS @@ -52,7 +51,6 @@ /git-diff-tree /git-difftool /git-difftool--helper -/git-builtin-difftool /git-describe /git-fast-export /git-fast-import @@ -78,6 +76,7 @@ /git-init-db /git-interpret-trailers /git-instaweb +/git-legacy-difftool /git-log /git-ls-files /git-ls-remote diff --git a/Makefile b/Makefile index f764174..7863bc2 100644 --- a/Makefile +++ b/Makefile @@ -527,7 +527,7 @@ SCRIPT_LIB += git-sh-setup SCRIPT_LIB += git-sh-i18n SCRIPT_PERL += git-add--interactive.perl -SCRIPT_PERL += git-difftool.perl +SCRIPT_PERL += git-legacy-difftool.perl SCRIPT_PERL += git-archimport.perl SCRIPT_PERL += git-cvsexportcommit.perl SCRIPT_PERL += git-cvsimport.perl @@ -888,7 +888,7 @@ BUILTIN_OBJS += builtin/diff-files.o BUILTIN_OBJS += builtin/diff-index.o BUILTIN_OBJS += builtin/diff-tree.o BUILTIN_OBJS += builtin/diff.o -BUILTIN_OBJS += builtin/builtin-difftool.o +BUILTIN_OBJS += builtin/difftool.o BUILTIN_OBJS += builtin/fast-export.o BUILTIN_OBJS += builtin/fetch-pack.o BUILTIN_OBJS += builtin/fetch.o diff --git a/builtin.h b/builtin.h index 409a61e..67f8051 100644 --- a/builtin.h +++ b/builtin.h @@ -60,7 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix); extern int cmd_diff_index(int argc, const char **argv, const char *prefix); extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); -extern int cmd_builtin_difftool(int argc, const char **argv, const char *prefix); +extern int cmd_difftool(int argc, const char **argv, const char *prefix); extern int cmd_fast_export(int argc, const char **argv, const char *prefix); extern int cmd_fetch(int argc, const char **argv, const char *prefix); extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix); diff --git a/builtin/builtin-difftool.c b/builtin/difftool.c similarity index 95% rename from builtin/builtin-difftool.c rename to builtin/difftool.c index 9feefcd..f845879 100644 --- a/builtin/builtin-difftool.c +++ b/builtin/difftool.c @@ -18,12 +18,15 @@ #include "argv-array.h" #include "strbuf.h" #include "lockfile.h" +#include "dir.h" +#include "exec_cmd.h" static char *diff_gui_tool; static int trust_exit_code; +static int use_builtin_difftool; -static const char * const builtin_difftool_usage[] = { - N_("git add [<options>] [--] <pathspec>..."), +static const char *const builtin_difftool_usage[] = { + N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"), NULL }; @@ -39,6 +42,11 @@ static int difftool_config(const char *var, const char *value, void *cb) return 0; } + if (!strcmp(var, "core.usebuiltindifftool")) { + use_builtin_difftool = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, cb); } @@ -227,8 +235,6 @@ static void changed_files(struct hashmap *result, const char *index_path, strbuf_release(&buf); } -#include "dir.h" - static NORETURN void exit_cleanup(const char *tmpdir, int exit_code) { struct strbuf buf = STRBUF_INIT; @@ -282,16 +288,16 @@ static int run_dir_diff(const char *extcmd, int symlinks, /* Setup temp directories */ tmp = getenv("TMPDIR"); - sprintf(tmpdir, "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp"); + xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp"); if (!mkdtemp(tmpdir)) - return error("could not create temporary directory"); + return error("could not create '%s'", tmpdir); strbuf_addf(&ldir, "%s/left/", tmpdir); strbuf_addf(&rdir, "%s/right/", tmpdir); strbuf_addstr(&wtdir, workdir); if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1])) strbuf_addch(&wtdir, '/'); - mkdir(ldir.buf, 0777); - mkdir(rdir.buf, 0777); + mkdir(ldir.buf, 0700); + mkdir(rdir.buf, 0700); memset(&wtindex, 0, sizeof(wtindex)); @@ -606,12 +612,11 @@ static int run_file_diff(int prompt, int argc, const char **argv) exit(ret); } -int cmd_builtin_difftool(int argc, const char ** argv, const char * prefix) +int cmd_difftool(int argc, const char ** argv, const char * prefix) { int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0, tool_help = 0; static char *difftool_cmd = NULL, *extcmd = NULL; - struct option builtin_difftool_options[] = { OPT_BOOL('g', "gui", &use_gui_tool, N_("use `diff.guitool` instead of `diff.tool`")), @@ -638,9 +643,16 @@ int cmd_builtin_difftool(int argc, const char ** argv, const char * prefix) OPT_END() }; + git_config(difftool_config, NULL); symlinks = has_symlinks; + if (!use_builtin_difftool) { + const char *path = mkpath("%s/git-legacy-difftool", git_exec_path()); - git_config(difftool_config, NULL); + if (sane_execvp(path, (char **)argv) < 0) + die_errno("could not exec %s", path); + + return 0; + } argc = parse_options(argc, argv, prefix, builtin_difftool_options, builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN | @@ -670,7 +682,7 @@ int cmd_builtin_difftool(int argc, const char ** argv, const char * prefix) /* * In directory diff mode, 'git-difftool--helper' is called once - * to compare the a / b directories.In file diff mode, 'git diff' + * to compare the a / b directories. In file diff mode, 'git diff' * will invoke a separate instance of 'git-difftool--helper' for * each file that changed. */ diff --git a/git-difftool.perl b/git-legacy-difftool.perl similarity index 98% rename from git-difftool.perl rename to git-legacy-difftool.perl index 28e47d8..a5790d0 100755 --- a/git-difftool.perl +++ b/git-legacy-difftool.perl @@ -23,13 +23,6 @@ use File::Temp qw(tempdir); use Getopt::Long qw(:config pass_through); use Git; -if (-e Git::exec_path() . '/use-builtin-difftool') { - unshift(@ARGV, "builtin-difftool"); - unshift(@ARGV, "git"); - exec(@ARGV); - die("Could not execute builtin difftool"); -} - sub usage { my $exitcode = shift; diff --git a/git.c b/git.c index 7a0df7a..0e6bbee 100644 --- a/git.c +++ b/git.c @@ -2,7 +2,6 @@ #include "exec_cmd.h" #include "help.h" #include "run-command.h" -#include "dir.h" const char git_usage_string[] = "git [--version] [--help] [-C <path>] [-c name=value]\n" @@ -425,7 +424,7 @@ static struct cmd_struct commands[] = { { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE }, { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, - { "builtin-difftool", cmd_builtin_difftool, RUN_SETUP | NEED_WORK_TREE }, + { "difftool", cmd_difftool, RUN_SETUP | NEED_WORK_TREE }, { "fast-export", cmd_fast_export, RUN_SETUP }, { "fetch", cmd_fetch, RUN_SETUP }, { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, @@ -543,22 +542,6 @@ static void strip_extension(const char **argv) #define strip_extension(cmd) #endif -static int use_builtin_difftool(void) -{ - static int initialized, use; - - if (!initialized) { - struct strbuf buf = STRBUF_INIT; - strbuf_addf(&buf, "%s/%s", git_exec_path(), - "use-builtin-difftool"); - use = file_exists(buf.buf); - strbuf_release(&buf); - initialized = 1; - } - - return use; -} - static void handle_builtin(int argc, const char **argv) { struct argv_array args = ARGV_ARRAY_INIT; @@ -568,9 +551,6 @@ static void handle_builtin(int argc, const char **argv) strip_extension(argv); cmd = argv[0]; - if (!strcmp("difftool", cmd) && use_builtin_difftool()) - cmd = "builtin-difftool"; - /* Turn "git cmd --help" into "git help --exclude-guides cmd" */ if (argc > 1 && !strcmp(argv[1], "--help")) { int i; -- 2.10.1.583.g721a9e0 ^ permalink raw reply [flat|nested] 86+ messages in thread
* [PATCH v2 1/1] difftool: add the builtin 2016-11-23 22:03 ` [PATCH v2 0/1] Show Git Mailing List: a builtin difftool Johannes Schindelin @ 2016-11-23 22:03 ` Johannes Schindelin 2016-11-23 22:25 ` Junio C Hamano 2016-11-24 20:55 ` [PATCH v3 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin 1 sibling, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2016-11-23 22:03 UTC (permalink / raw) To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker This adds a builtin difftool that represents a conversion of the current Perl script version of the difftool. The motivation is that Perl scripts are not at all native on Windows, and that `git difftool` therefore is pretty slow on that platform, when there is no good reason for it to be slow. In addition, Perl does not really have access to Git's internals. That means that any script will always have to jump through unnecessary hoops. The current version of the builtin difftool does not, however, make full use of the internals but instead chooses to spawn a couple of Git processes, still, to make for an easier conversion. There remains a lot of room for improvement, left for a later date. Note: the original difftool is now called `git legacy-difftool`, but to play it safe, it is still called by difftool unless the config setting core.useBuiltinDifftool=true. The reason: this new, experimental, builtin difftool will be shipped as part of Git for Windows v2.11.0, to allow for easier large-scale testing, but of course as an opt-in feature. Sadly, the speedup is more noticable on Linux than on Windows: a quick test shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s) (real/user/sys) in a Linux VM, down from (6.529s/3.112s/0.644s), while on Windows, it is (36.064s/2.730s/7.194s), down from (47.637s/2.407s/6.863s). The culprit is most likely the overhead incurred from *still* having to shell out to mergetool-lib.sh and difftool--helper.sh. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- .gitignore | 1 + Makefile | 3 +- builtin.h | 1 + builtin/difftool.c | 692 ++++++++++++++++++++++++++ git-difftool.perl => git-legacy-difftool.perl | 0 git.c | 1 + 6 files changed, 697 insertions(+), 1 deletion(-) create mode 100644 builtin/difftool.c rename git-difftool.perl => git-legacy-difftool.perl (100%) diff --git a/.gitignore b/.gitignore index 05cb58a..f96e50e 100644 --- a/.gitignore +++ b/.gitignore @@ -76,6 +76,7 @@ /git-init-db /git-interpret-trailers /git-instaweb +/git-legacy-difftool /git-log /git-ls-files /git-ls-remote diff --git a/Makefile b/Makefile index f53fcc9..7863bc2 100644 --- a/Makefile +++ b/Makefile @@ -527,7 +527,7 @@ SCRIPT_LIB += git-sh-setup SCRIPT_LIB += git-sh-i18n SCRIPT_PERL += git-add--interactive.perl -SCRIPT_PERL += git-difftool.perl +SCRIPT_PERL += git-legacy-difftool.perl SCRIPT_PERL += git-archimport.perl SCRIPT_PERL += git-cvsexportcommit.perl SCRIPT_PERL += git-cvsimport.perl @@ -888,6 +888,7 @@ BUILTIN_OBJS += builtin/diff-files.o BUILTIN_OBJS += builtin/diff-index.o BUILTIN_OBJS += builtin/diff-tree.o BUILTIN_OBJS += builtin/diff.o +BUILTIN_OBJS += builtin/difftool.o BUILTIN_OBJS += builtin/fast-export.o BUILTIN_OBJS += builtin/fetch-pack.o BUILTIN_OBJS += builtin/fetch.o diff --git a/builtin.h b/builtin.h index b9122bc..67f8051 100644 --- a/builtin.h +++ b/builtin.h @@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix); extern int cmd_diff_index(int argc, const char **argv, const char *prefix); extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); +extern int cmd_difftool(int argc, const char **argv, const char *prefix); extern int cmd_fast_export(int argc, const char **argv, const char *prefix); extern int cmd_fetch(int argc, const char **argv, const char *prefix); extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix); diff --git a/builtin/difftool.c b/builtin/difftool.c new file mode 100644 index 0000000..f845879 --- /dev/null +++ b/builtin/difftool.c @@ -0,0 +1,692 @@ +/* + * "git difftool" builtin command + * + * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible + * git-difftool--helper script. + * + * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git. + * The GIT_DIFF* variables are exported for use by git-difftool--helper. + * + * Any arguments that are unknown to this script are forwarded to 'git diff'. + * + * Copyright (C) 2016 Johannes Schindelin + */ +#include "cache.h" +#include "builtin.h" +#include "parse-options.h" +#include "run-command.h" +#include "argv-array.h" +#include "strbuf.h" +#include "lockfile.h" +#include "dir.h" +#include "exec_cmd.h" + +static char *diff_gui_tool; +static int trust_exit_code; +static int use_builtin_difftool; + +static const char *const builtin_difftool_usage[] = { + N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"), + NULL +}; + +static int difftool_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "diff.guitool")) { + diff_gui_tool = xstrdup(value); + return 0; + } + + if (!strcmp(var, "difftool.trustexitcode")) { + trust_exit_code = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "core.usebuiltindifftool")) { + use_builtin_difftool = git_config_bool(var, value); + return 0; + } + + return git_default_config(var, value, cb); +} + +static int print_tool_help(void) +{ + const char *argv[] = { "mergetool", "--tool-help=diff", NULL }; + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int parse_index_info(char *p, int *mode1, int *mode2, + struct object_id *oid1, struct object_id *oid2, + char *status) +{ + if (*p != ':') + return error("expected ':', got '%c'", *p); + *mode1 = (int)strtol(p + 1, &p, 8); + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + *mode2 = (int)strtol(p + 1, &p, 8); + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + if (get_oid_hex(++p, oid1)) + return error("expected object ID, got '%s'", p + 1); + p += GIT_SHA1_HEXSZ; + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + if (get_oid_hex(++p, oid2)) + return error("expected object ID, got '%s'", p + 1); + p += GIT_SHA1_HEXSZ; + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + *status = *++p; + if (!status || p[1]) + return error("unexpected trailer: '%s'", p); + return 0; +} + +/* + * Remove any trailing slash from $workdir + * before starting to avoid double slashes in symlink targets. + */ +static void add_path(struct strbuf *buf, size_t base_len, const char *path) +{ + strbuf_setlen(buf, base_len); + if (buf->len && buf->buf[buf->len - 1] != '/') + strbuf_addch(buf, '/'); + strbuf_addstr(buf, path); +} + +/* + * Determine whether we can simply reuse the file in the worktree. + */ +static int use_wt_file(const char *workdir, const char *name, + struct object_id *oid) +{ + struct strbuf buf = STRBUF_INIT; + struct stat st; + int use = 0; + + strbuf_addstr(&buf, workdir); + add_path(&buf, buf.len, name); + + if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) { + struct object_id wt_oid; + int fd = open(buf.buf, O_RDONLY); + + if (!index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) { + if (is_null_oid(oid)) { + oidcpy(oid, &wt_oid); + use = 1; + } else if (!oidcmp(oid, &wt_oid)) + use = 1; + } + } + + strbuf_release(&buf); + + return use; +} + +struct working_tree_entry { + struct hashmap_entry entry; + char path[FLEX_ARRAY]; +}; + +static int working_tree_entry_cmp(struct working_tree_entry *a, + struct working_tree_entry *b, void *keydata) +{ + return strcmp(a->path, b->path); +} + +/* + * The `left` and `right` entries hold paths for the symlinks hashmap, + * and a SHA-1 surrounded by brief text for submodules. + */ +struct pair_entry { + struct hashmap_entry entry; + char left[PATH_MAX], right[PATH_MAX]; + const char path[FLEX_ARRAY]; +}; + +static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata) +{ + return strcmp(a->path, b->path); +} + +static void add_left_or_right(struct hashmap *map, const char *path, + const char *content, int is_right) +{ + struct pair_entry *e, *existing; + + FLEX_ALLOC_STR(e, path, path); + hashmap_entry_init(e, strhash(path)); + existing = hashmap_get(map, e, NULL); + if (existing) { + free(e); + e = existing; + } else { + e->left[0] = e->right[0] = '\0'; + hashmap_add(map, e); + } + strcpy(is_right ? e->right : e->left, content); +} + +struct path_entry { + struct hashmap_entry entry; + char path[FLEX_ARRAY]; +}; + +int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key) +{ + return strcmp(a->path, key ? key : b->path); +} + +static void changed_files(struct hashmap *result, const char *index_path, + const char *workdir) +{ + struct child_process update_index = CHILD_PROCESS_INIT; + struct child_process diff_files = CHILD_PROCESS_INIT; + struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT; + const char *git_dir = absolute_path(get_git_dir()), *env[] = { + NULL, NULL + }; + FILE *fp; + + strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path); + env[0] = index_env.buf; + + argv_array_pushl(&update_index.args, + "--git-dir", git_dir, "--work-tree", workdir, + "update-index", "--really-refresh", "-q", + "--unmerged", NULL); + update_index.no_stdin = 1; + update_index.no_stdout = 1; + update_index.no_stderr = 1; + update_index.git_cmd = 1; + update_index.use_shell = 0; + update_index.clean_on_exit = 1; + update_index.dir = workdir; + update_index.env = env; + /* Ignore any errors of update-index */ + run_command(&update_index); + + argv_array_pushl(&diff_files.args, + "--git-dir", git_dir, "--work-tree", workdir, + "diff-files", "--name-only", "-z", NULL); + diff_files.no_stdin = 1; + diff_files.git_cmd = 1; + diff_files.use_shell = 0; + diff_files.clean_on_exit = 1; + diff_files.out = -1; + diff_files.dir = workdir; + diff_files.env = env; + if (start_command(&diff_files)) + die("could not obtain raw diff"); + fp = xfdopen(diff_files.out, "r"); + while (!strbuf_getline_nul(&buf, fp)) { + struct path_entry *entry; + FLEX_ALLOC_STR(entry, path, buf.buf); + hashmap_entry_init(entry, strhash(buf.buf)); + hashmap_add(result, entry); + } + if (finish_command(&diff_files)) + die("diff-files did not exit properly"); + strbuf_release(&index_env); + strbuf_release(&buf); +} + +static NORETURN void exit_cleanup(const char *tmpdir, int exit_code) +{ + struct strbuf buf = STRBUF_INIT; + strbuf_addstr(&buf, tmpdir); + remove_dir_recursively(&buf, 0); + if (exit_code) + warning(_("failed: %d"), exit_code); + exit(exit_code); +} + +static int ensure_leading_directories(char *path) +{ + switch (safe_create_leading_directories(path)) { + case SCLD_OK: + case SCLD_EXISTS: + return 0; + default: + return error(_("could not create leading directories " + "of '%s'"), path); + } +} + +static int run_dir_diff(const char *extcmd, int symlinks, + int argc, const char **argv) +{ + char tmpdir[PATH_MAX]; + struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT; + struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT; + struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT; + struct strbuf wtdir = STRBUF_INIT; + size_t ldir_len, rdir_len, wtdir_len; + struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1); + const char *workdir, *tmp; + int ret = 0, i; + FILE *fp; + struct hashmap working_tree_dups, submodules, symlinks2; + struct hashmap_iter iter; + struct pair_entry *entry; + enum object_type type; + unsigned long size; + struct index_state wtindex; + struct checkout lstate, rstate; + int rc, flags = RUN_GIT_CMD, err = 0; + struct child_process child = CHILD_PROCESS_INIT; + const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL }; + struct hashmap wt_modified, tmp_modified; + int indices_loaded = 0; + + setup_work_tree(); + workdir = get_git_work_tree(); + + /* Setup temp directories */ + tmp = getenv("TMPDIR"); + xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp"); + if (!mkdtemp(tmpdir)) + return error("could not create '%s'", tmpdir); + strbuf_addf(&ldir, "%s/left/", tmpdir); + strbuf_addf(&rdir, "%s/right/", tmpdir); + strbuf_addstr(&wtdir, workdir); + if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1])) + strbuf_addch(&wtdir, '/'); + mkdir(ldir.buf, 0700); + mkdir(rdir.buf, 0700); + + memset(&wtindex, 0, sizeof(wtindex)); + + memset(&lstate, 0, sizeof(lstate)); + lstate.base_dir = ldir.buf; + lstate.base_dir_len = ldir.len; + lstate.force = 1; + memset(&rstate, 0, sizeof(rstate)); + rstate.base_dir = rdir.buf; + rstate.base_dir_len = rdir.len; + rstate.force = 1; + + ldir_len = ldir.len; + rdir_len = rdir.len; + wtdir_len = wtdir.len; + + hashmap_init(&working_tree_dups, + (hashmap_cmp_fn)working_tree_entry_cmp, 0); + hashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0); + hashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0); + + child.no_stdin = 1; + child.git_cmd = 1; + child.use_shell = 0; + child.clean_on_exit = 1; + child.out = -1; + argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z", + NULL); + for (i = 0; i < argc; i++) + argv_array_push(&child.args, argv[i]); + if (start_command(&child)) + die("could not obtain raw diff"); + fp = xfdopen(child.out, "r"); + + /* Build index info for left and right sides of the diff */ + while (!strbuf_getline_nul(&info, fp)) { + int lmode, rmode; + struct object_id loid, roid; + char status; + const char *src_path, *dst_path; + size_t src_path_len, dst_path_len; + + if (starts_with(info.buf, "::")) + die(N_("combined diff formats('-c' and '--cc') are " + "not supported in\n" + "directory diff mode('-d' and '--dir-diff').")); + + if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid, + &status)) + break; + if (strbuf_getline_nul(&lpath, fp)) + break; + src_path = lpath.buf; + src_path_len = lpath.len; + + if (status != 'C' && status != 'R') { + dst_path = src_path; + dst_path_len = src_path_len; + } else { + if (strbuf_getline_nul(&rpath, fp)) + break; + dst_path = rpath.buf; + dst_path_len = rpath.len; + } + + if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) { + strbuf_reset(&buf); + strbuf_addf(&buf, "Subproject commit %s", + oid_to_hex(&loid)); + add_left_or_right(&submodules, src_path, buf.buf, 0); + strbuf_reset(&buf); + strbuf_addf(&buf, "Subproject commit %s", + oid_to_hex(&roid)); + if (!oidcmp(&loid, &roid)) + strbuf_addstr(&buf, "-dirty"); + add_left_or_right(&submodules, dst_path, buf.buf, 1); + continue; + } + + if (S_ISLNK(lmode)) { + char *content = read_sha1_file(loid.hash, &type, &size); + add_left_or_right(&symlinks2, src_path, content, 0); + free(content); + } + + if (S_ISLNK(rmode)) { + char *content = read_sha1_file(roid.hash, &type, &size); + add_left_or_right(&symlinks2, dst_path, content, 1); + free(content); + } + + if (lmode && status != 'C') { + ce->ce_mode = lmode; + oidcpy(&ce->oid, &loid); + strcpy(ce->name, src_path); + ce->ce_namelen = src_path_len; + if (checkout_entry(ce, &lstate, NULL)) + return error("could not write '%s'", src_path); + } + + if (rmode) { + struct working_tree_entry *entry; + + /* Avoid duplicate working_tree entries */ + FLEX_ALLOC_STR(entry, path, dst_path); + hashmap_entry_init(entry, strhash(dst_path)); + if (hashmap_get(&working_tree_dups, entry, NULL)) { + free(entry); + continue; + } + hashmap_add(&working_tree_dups, entry); + + if (!use_wt_file(workdir, dst_path, &roid)) { + ce->ce_mode = rmode; + oidcpy(&ce->oid, &roid); + strcpy(ce->name, dst_path); + ce->ce_namelen = dst_path_len; + if (checkout_entry(ce, &rstate, NULL)) + return error("could not write '%s'", + dst_path); + } else if (!is_null_oid(&roid)) { + /* + * Changes in the working tree need special + * treatment since they are not part of the + * index. + */ + struct cache_entry *ce2 = + make_cache_entry(rmode, roid.hash, + dst_path, 0, 0); + ce_mode_from_stat(ce2, rmode); + + add_index_entry(&wtindex, ce2, + ADD_CACHE_JUST_APPEND); + + add_path(&wtdir, wtdir_len, dst_path); + add_path(&rdir, rdir_len, dst_path); + if (ensure_leading_directories(rdir.buf)) + return error("could not create " + "directory for '%s'", + dst_path); + if (symlinks) { + if (symlink(wtdir.buf, rdir.buf)) { + ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf); + goto finish; + } + } else { + struct stat st; + if (stat(wtdir.buf, &st)) + st.st_mode = 0644; + if (copy_file(rdir.buf, wtdir.buf, + st.st_mode)) { + ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf); + goto finish; + } + } + } + } + } + if (finish_command(&child)) { + ret = error("error occurred running diff --raw"); + goto finish; + } + + /* + * Changes to submodules require special treatment.This loop writes a + * temporary file to both the left and right directories to show the + * change in the recorded SHA1 for the submodule. + */ + hashmap_iter_init(&submodules, &iter); + while ((entry = hashmap_iter_next(&iter))) { + if (*entry->left) { + add_path(&ldir, ldir_len, entry->path); + ensure_leading_directories(ldir.buf); + write_file(ldir.buf, "%s", entry->left); + } + if (*entry->right) { + add_path(&rdir, rdir_len, entry->path); + ensure_leading_directories(rdir.buf); + write_file(rdir.buf, "%s", entry->right); + } + } + + /* + * Symbolic links require special treatment.The standard "git diff" + * shows only the link itself, not the contents of the link target. + * This loop replicates that behavior. + */ + hashmap_iter_init(&symlinks2, &iter); + while ((entry = hashmap_iter_next(&iter))) { + if (*entry->left) { + add_path(&ldir, ldir_len, entry->path); + ensure_leading_directories(ldir.buf); + write_file(ldir.buf, "%s", entry->left); + } + if (*entry->right) { + add_path(&rdir, rdir_len, entry->path); + ensure_leading_directories(rdir.buf); + write_file(rdir.buf, "%s", entry->right); + } + } + + strbuf_release(&buf); + + strbuf_setlen(&ldir, ldir_len); + helper_argv[1] = ldir.buf; + strbuf_setlen(&rdir, rdir_len); + helper_argv[2] = rdir.buf; + + if (extcmd) { + helper_argv[0] = extcmd; + flags = 0; + } else + setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1); + rc = run_command_v_opt(helper_argv, flags); + + /* + * If the diff includes working copy files and those + * files were modified during the diff, then the changes + * should be copied back to the working tree. + * Do not copy back files when symlinks are used and the + * external tool did not replace the original link with a file. + * + * These hashes are loaded lazily since they aren't needed + * in the common case of --symlinks and the difftool updating + * files through the symlink. + */ + hashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp, + wtindex.cache_nr); + hashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp, + wtindex.cache_nr); + + for (i = 0; i < wtindex.cache_nr; i++) { + struct hashmap_entry dummy; + const char *name = wtindex.cache[i]->name; + struct stat st; + + add_path(&rdir, rdir_len, name); + if (lstat(rdir.buf, &st)) + continue; + + if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode)) + continue; + + if (!indices_loaded) { + static struct lock_file lock; + strbuf_reset(&buf); + strbuf_addf(&buf, "%s/wtindex", tmpdir); + if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 || + write_locked_index(&wtindex, &lock, COMMIT_LOCK)) { + ret = error("could not write %s", buf.buf); + rollback_lock_file(&lock); + goto finish; + } + changed_files(&wt_modified, buf.buf, workdir); + strbuf_setlen(&rdir, rdir_len); + changed_files(&tmp_modified, buf.buf, rdir.buf); + add_path(&rdir, rdir_len, name); + indices_loaded = 1; + } + + hashmap_entry_init(&dummy, strhash(name)); + if (hashmap_get(&tmp_modified, &dummy, name)) { + add_path(&wtdir, wtdir_len, name); + if (hashmap_get(&wt_modified, &dummy, name)) { + warning(_("both files modified: '%s' and '%s'."), + wtdir.buf, rdir.buf); + warning(_("working tree file has been left.")); + warning(""); + err = 1; + } else if (unlink(wtdir.buf) || + copy_file(wtdir.buf, rdir.buf, st.st_mode)) + warning_errno(_("could not copy '%s' to '%s'"), + rdir.buf, wtdir.buf); + } + } + + if (err) { + warning(_("temporary files exist in '%s'."), tmpdir); + warning(_("you may want to cleanup or recover these.")); + exit(1); + } else + exit_cleanup(tmpdir, rc); + +finish: + free(ce); + strbuf_release(&ldir); + strbuf_release(&rdir); + strbuf_release(&wtdir); + strbuf_release(&buf); + + return ret; +} + +static int run_file_diff(int prompt, int argc, const char **argv) +{ + struct argv_array args = ARGV_ARRAY_INIT; + const char *env[] = { + "GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL, + NULL + }; + int ret = 0, i; + + if (prompt > 0) + env[2] = "GIT_DIFFTOOL_PROMPT=true"; + else if (!prompt) + env[2] = "GIT_DIFFTOOL_NO_PROMPT=true"; + + argv_array_push(&args, "diff"); + for (i = 0; i < argc; i++) + argv_array_push(&args, argv[i]); + ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, NULL, env); + exit(ret); +} + +int cmd_difftool(int argc, const char ** argv, const char * prefix) +{ + int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0, + tool_help = 0; + static char *difftool_cmd = NULL, *extcmd = NULL; + struct option builtin_difftool_options[] = { + OPT_BOOL('g', "gui", &use_gui_tool, + N_("use `diff.guitool` instead of `diff.tool`")), + OPT_BOOL('d', "dir-diff", &dir_diff, + N_("perform a full-directory diff")), + { OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL, + N_("do not prompt before launching a diff tool"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0}, + { OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL, + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN, + NULL, 1 }, + OPT_BOOL(0, "symlinks", &symlinks, + N_("use symlinks in dir-diff mode")), + OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"), + N_("use the specified diff tool")), + OPT_BOOL(0, "tool-help", &tool_help, + N_("print a list of diff tools that may be used with " + "`--tool`")), + OPT_BOOL(0, "trust-exit-code", &trust_exit_code, + N_("make 'git-difftool' exit when an invoked diff " + "tool returns a non - zero exit code")), + OPT_STRING('x', "extcmd", &extcmd, N_("<command>"), + N_("specify a custom command for viewing diffs")), + OPT_END() + }; + + git_config(difftool_config, NULL); + symlinks = has_symlinks; + if (!use_builtin_difftool) { + const char *path = mkpath("%s/git-legacy-difftool", git_exec_path()); + + if (sane_execvp(path, (char **)argv) < 0) + die_errno("could not exec %s", path); + + return 0; + } + + argc = parse_options(argc, argv, prefix, builtin_difftool_options, + builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_DASHDASH); + + if (tool_help) + return print_tool_help(); + + if (use_gui_tool && diff_gui_tool && *diff_gui_tool) + setenv("GIT_DIFF_TOOL", diff_gui_tool, 1); + else if (difftool_cmd) { + if (*difftool_cmd) + setenv("GIT_DIFF_TOOL", difftool_cmd, 1); + else + die(_("no <tool> given for --tool=<tool>")); + } + + if (extcmd) { + if (*extcmd) + setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1); + else + die(_("no <cmd> given for --extcmd=<cmd>")); + } + + setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE", + trust_exit_code ? "true" : "false", 1); + + /* + * In directory diff mode, 'git-difftool--helper' is called once + * to compare the a / b directories. In file diff mode, 'git diff' + * will invoke a separate instance of 'git-difftool--helper' for + * each file that changed. + */ + if (dir_diff) + return run_dir_diff(extcmd, symlinks, argc, argv); + return run_file_diff(prompt, argc, argv); +} diff --git a/git-difftool.perl b/git-legacy-difftool.perl similarity index 100% rename from git-difftool.perl rename to git-legacy-difftool.perl diff --git a/git.c b/git.c index efa1059..0e6bbee 100644 --- a/git.c +++ b/git.c @@ -424,6 +424,7 @@ static struct cmd_struct commands[] = { { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE }, { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, + { "difftool", cmd_difftool, RUN_SETUP | NEED_WORK_TREE }, { "fast-export", cmd_fast_export, RUN_SETUP }, { "fetch", cmd_fetch, RUN_SETUP }, { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, -- 2.10.1.583.g721a9e0 ^ permalink raw reply related [flat|nested] 86+ messages in thread
* Re: [PATCH v2 1/1] difftool: add the builtin 2016-11-23 22:03 ` [PATCH v2 1/1] difftool: add the builtin Johannes Schindelin @ 2016-11-23 22:25 ` Junio C Hamano 2016-11-23 22:30 ` Junio C Hamano 0 siblings, 1 reply; 86+ messages in thread From: Junio C Hamano @ 2016-11-23 22:25 UTC (permalink / raw) To: Johannes Schindelin; +Cc: git, David Aguilar, Dennis Kaarsemaker Johannes Schindelin <johannes.schindelin@gmx.de> writes: > + if (!strcmp(var, "core.usebuiltindifftool")) { > + use_builtin_difftool = git_config_bool(var, value); > + return 0; > + } This no way belongs to the core set; difftool.usebuiltin would be more appropriate. > + if (!use_builtin_difftool) { > + const char *path = mkpath("%s/git-legacy-difftool", git_exec_path()); > + > + if (sane_execvp(path, (char **)argv) < 0) > + die_errno("could not exec %s", path); > + > + return 0; > + } > + ... > diff --git a/git-difftool.perl b/git-legacy-difftool.perl > similarity index 100% > rename from git-difftool.perl > rename to git-legacy-difftool.perl > diff --git a/git.c b/git.c > index efa1059..0e6bbee 100644 > --- a/git.c > +++ b/git.c > @@ -424,6 +424,7 @@ static struct cmd_struct commands[] = { > { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE }, > { "diff-index", cmd_diff_index, RUN_SETUP }, > { "diff-tree", cmd_diff_tree, RUN_SETUP }, > + { "difftool", cmd_difftool, RUN_SETUP | NEED_WORK_TREE }, Running set-up would mean that the spawning of legacy-difftool would be done after you chdir(2) up to the root level of the working tree, no? I do not think you can safely add these two bits here until the migration completes. I doubt that setting core.usebuiltindifftool to false and running the tool from a subdirectory and a pathspec work correctly with this patch. If running difftool from a subdirectory with a pathspec is not tested in t7800, perhaps we should. It is nice that we can now lose PERL prerequisite from t7800 ;-) Thanks. ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v2 1/1] difftool: add the builtin 2016-11-23 22:25 ` Junio C Hamano @ 2016-11-23 22:30 ` Junio C Hamano 2016-11-24 10:38 ` Johannes Schindelin 0 siblings, 1 reply; 86+ messages in thread From: Junio C Hamano @ 2016-11-23 22:30 UTC (permalink / raw) To: Johannes Schindelin; +Cc: git, David Aguilar, Dennis Kaarsemaker Junio C Hamano <gitster@pobox.com> writes: > ... I do not think you can safely add these two bits here until the > migration completes. I accidentally removed a more useful bit I wrote after the above sentence while editing. The NEEDSWORK comment in 73c2779f42 ("builtin-am: implement skeletal builtin am", 2015-08-04) mentions why it calls setup-git-directory and setup-work-tree instead of letting run_builtin() do so; perhaps you can do something similar here to fix this. > I doubt that setting core.usebuiltindifftool to false and running > the tool from a subdirectory and a pathspec work correctly with this > patch. If running difftool from a subdirectory with a pathspec is > not tested in t7800, perhaps we should. > > It is nice that we can now lose PERL prerequisite from t7800 ;-) > > Thanks. ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v2 1/1] difftool: add the builtin 2016-11-23 22:30 ` Junio C Hamano @ 2016-11-24 10:38 ` Johannes Schindelin 0 siblings, 0 replies; 86+ messages in thread From: Johannes Schindelin @ 2016-11-24 10:38 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, David Aguilar, Dennis Kaarsemaker Hi Junio, On Wed, 23 Nov 2016, Junio C Hamano wrote: > Junio C Hamano <gitster@pobox.com> writes: > > > ... I do not think you can safely add these two bits here until the > > migration completes. > > I accidentally removed a more useful bit I wrote after the above > sentence while editing. > > The NEEDSWORK comment in 73c2779f42 ("builtin-am: implement skeletal > builtin am", 2015-08-04) mentions why it calls setup-git-directory > and setup-work-tree instead of letting run_builtin() do so; perhaps > you can do something similar here to fix this. This is the Catch-22 I mentioned a couple times: if you insist on a config setting, the config has to be read. For that to work, setup_git_directory() has to be called. So no matter what you do, if you want to have conditional code that depends on the config, and that wants setup_git_directory() *not* to be called before, you are simply out of luck. Sadly, I now bought into your comment that using a file in exec-path as a feature flag is a bad thing, and that we have to use a config setting. So now I have to spend more time on fixing something that was not a problem in my original patches. However, this exchange has something else in it, apart from creating unneeded work for me. What you really accidentally did was to identify a fundamental problem with the builtin difftool: when called from a subdirectory, the RUN_SETUP flag would make it chdir() to the top-level directory, and the subsequently spawned Git processes would get the wrong idea about relative paths. Thank you for that, Dscho ^ permalink raw reply [flat|nested] 86+ messages in thread
* [PATCH v3 0/2] Show Git Mailing List: a builtin difftool 2016-11-23 22:03 ` [PATCH v2 0/1] Show Git Mailing List: a builtin difftool Johannes Schindelin 2016-11-23 22:03 ` [PATCH v2 1/1] difftool: add the builtin Johannes Schindelin @ 2016-11-24 20:55 ` Johannes Schindelin 2016-11-24 20:55 ` [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin Johannes Schindelin ` (2 more replies) 1 sibling, 3 replies; 86+ messages in thread From: Johannes Schindelin @ 2016-11-24 20:55 UTC (permalink / raw) To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker I have been working on the builtin difftool for almost two weeks, for two reasons: 1. Perl is really not native on Windows. Not only is there a performance penalty to be paid just for running Perl scripts, we also have to deal with the fact that users may have different Perl installations, with different options, and some other Perl installation may decide to set PERL5LIB globally, wreaking havoc with Git for Windows' Perl (which we have to use because almost all other Perl distributions lack the Subversion bindings we need for `git svn`). 2. Perl makes for a rather large reason that Git for Windows' installer weighs in with >30MB. While one Perl script less does not relieve us of that burden, it is one step in the right direction. This patch serves two purposes: to ask for reviews, and to show what I plan to release as part of Git for Windows v2.11.0 (which is due this coming Wednesday, if Git v2.11.0 is released on Tuesday, as planned). Changes since v2: - adjusted the config setting's name according to Junio's concerns - fixed launching difftool in a subdirectory - fixed dir-diff mode when there are no changes (it did not exit early but tried to diff two empty directories) Johannes Schindelin (2): difftool: add a skeleton for the upcoming builtin difftool: implement the functionality in the builtin .gitignore | 1 + Makefile | 3 +- builtin.h | 1 + builtin/difftool.c | 731 ++++++++++++++++++++++++++ git-difftool.perl => git-legacy-difftool.perl | 0 git.c | 6 + t/t7800-difftool.sh | 2 + 7 files changed, 743 insertions(+), 1 deletion(-) create mode 100644 builtin/difftool.c rename git-difftool.perl => git-legacy-difftool.perl (100%) base-commit: e2b2d6a172b76d44cb7b1ddb12ea5bfac9613a44 Published-As: https://github.com/dscho/git/releases/tag/builtin-difftool-v3 Fetch-It-Via: git fetch https://github.com/dscho/git builtin-difftool-v3 Interdiff vs v2: diff --git a/builtin/difftool.c b/builtin/difftool.c index f845879..3480920 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -13,17 +13,16 @@ */ #include "cache.h" #include "builtin.h" -#include "parse-options.h" #include "run-command.h" +#include "exec_cmd.h" +#include "parse-options.h" #include "argv-array.h" #include "strbuf.h" #include "lockfile.h" #include "dir.h" -#include "exec_cmd.h" static char *diff_gui_tool; static int trust_exit_code; -static int use_builtin_difftool; static const char *const builtin_difftool_usage[] = { N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"), @@ -42,11 +41,6 @@ static int difftool_config(const char *var, const char *value, void *cb) return 0; } - if (!strcmp(var, "core.usebuiltindifftool")) { - use_builtin_difftool = git_config_bool(var, value); - return 0; - } - return git_default_config(var, value, cb); } @@ -257,7 +251,7 @@ static int ensure_leading_directories(char *path) } } -static int run_dir_diff(const char *extcmd, int symlinks, +static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, int argc, const char **argv) { char tmpdir[PATH_MAX]; @@ -283,7 +277,6 @@ static int run_dir_diff(const char *extcmd, int symlinks, struct hashmap wt_modified, tmp_modified; int indices_loaded = 0; - setup_work_tree(); workdir = get_git_work_tree(); /* Setup temp directories */ @@ -323,6 +316,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, child.git_cmd = 1; child.use_shell = 0; child.clean_on_exit = 1; + child.dir = prefix; child.out = -1; argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z", NULL); @@ -333,6 +327,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, fp = xfdopen(child.out, "r"); /* Build index info for left and right sides of the diff */ + i = 0; while (!strbuf_getline_nul(&info, fp)) { int lmode, rmode; struct object_id loid, roid; @@ -353,6 +348,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, src_path = lpath.buf; src_path_len = lpath.len; + i++; if (status != 'C' && status != 'R') { dst_path = src_path; dst_path_len = src_path_len; @@ -456,11 +452,15 @@ static int run_dir_diff(const char *extcmd, int symlinks, } } } + if (finish_command(&child)) { ret = error("error occurred running diff --raw"); goto finish; } + if (!i) + return 0; + /* * Changes to submodules require special treatment.This loop writes a * temporary file to both the left and right directories to show the @@ -591,7 +591,8 @@ static int run_dir_diff(const char *extcmd, int symlinks, return ret; } -static int run_file_diff(int prompt, int argc, const char **argv) +static int run_file_diff(int prompt, const char *prefix, + int argc, const char **argv) { struct argv_array args = ARGV_ARRAY_INIT; const char *env[] = { @@ -605,14 +606,39 @@ static int run_file_diff(int prompt, int argc, const char **argv) else if (!prompt) env[2] = "GIT_DIFFTOOL_NO_PROMPT=true"; + argv_array_push(&args, "diff"); for (i = 0; i < argc; i++) argv_array_push(&args, argv[i]); - ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, NULL, env); + ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env); exit(ret); } -int cmd_difftool(int argc, const char ** argv, const char * prefix) +/* + * NEEDSWORK: this function can go once the legacy-difftool Perl script is + * retired. + * + * We intentionally avoid reading the config directly here, to avoid messing up + * the GIT_* environment variables when we need to fall back to exec()ing the + * Perl script. + */ +static int use_builtin_difftool(void) { + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + int ret; + + argv_array_pushl(&cp.args, + "config", "--bool", "difftool.usebuiltin", NULL); + cp.git_cmd = 1; + if (capture_command(&cp, &out, 6)) + return 0; + strbuf_trim(&out); + ret = !strcmp("true", out.buf); + strbuf_release(&out); + return ret; +} + +int cmd_difftool(int argc, const char **argv, const char *prefix) { int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0, tool_help = 0; @@ -643,16 +669,29 @@ int cmd_difftool(int argc, const char ** argv, const char * prefix) OPT_END() }; - git_config(difftool_config, NULL); - symlinks = has_symlinks; - if (!use_builtin_difftool) { - const char *path = mkpath("%s/git-legacy-difftool", git_exec_path()); + /* + * NEEDSWORK: Once the builtin difftool has been tested enough + * and git-legacy-difftool.perl is retired to contrib/, this preamble + * can be removed. + */ + if (!use_builtin_difftool()) { + const char *path = mkpath("%s/git-legacy-difftool", + git_exec_path()); if (sane_execvp(path, (char **)argv) < 0) die_errno("could not exec %s", path); return 0; } + prefix = setup_git_directory(); + trace_repo_setup(prefix); + setup_work_tree(); + /* NEEDSWORK: once we no longer spawn anything, remove this */ + setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1); + setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1); + + git_config(difftool_config, NULL); + symlinks = has_symlinks; argc = parse_options(argc, argv, prefix, builtin_difftool_options, builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN | @@ -687,6 +726,6 @@ int cmd_difftool(int argc, const char ** argv, const char * prefix) * each file that changed. */ if (dir_diff) - return run_dir_diff(extcmd, symlinks, argc, argv); - return run_file_diff(prompt, argc, argv); + return run_dir_diff(extcmd, symlinks, prefix, argc, argv); + return run_file_diff(prompt, prefix, argc, argv); } diff --git a/git.c b/git.c index e68b6eb..a8e6a15 100644 --- a/git.c +++ b/git.c @@ -424,7 +424,12 @@ static struct cmd_struct commands[] = { { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE }, { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, - { "difftool", cmd_difftool, RUN_SETUP | NEED_WORK_TREE }, + /* + * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in + * builtin/difftool.c has been removed, this entry should be changed to + * RUN_SETUP | NEED_WORK_TREE + */ + { "difftool", cmd_difftool }, { "fast-export", cmd_fast_export, RUN_SETUP }, { "fetch", cmd_fetch, RUN_SETUP }, { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 70a2de4..b6a6c30 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -23,6 +23,8 @@ prompt_given () test "$prompt" = "Launch 'test-tool' [Y/n]? branch" } +# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired. + # Create a file on master and change it on branch test_expect_success PERL 'setup' ' echo master >file && -- 2.10.1.583.g721a9e0 ^ permalink raw reply [flat|nested] 86+ messages in thread
* [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-24 20:55 ` [PATCH v3 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin @ 2016-11-24 20:55 ` Johannes Schindelin 2016-11-24 21:08 ` Jeff King 2016-11-24 20:55 ` [PATCH v3 2/2] difftool: implement the functionality in the builtin Johannes Schindelin 2017-01-02 16:16 ` [PATCH v4 0/4] Show Git Mailing List: a builtin difftool Johannes Schindelin 2 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2016-11-24 20:55 UTC (permalink / raw) To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker This adds a builtin difftool that still falls back to the legacy Perl version, which has been renamed to `legacy-difftool`. The idea is that the new, experimental, builtin difftool immediately hands off to the legacy difftool for now, unless the config variable difftool.useBuiltin is set to true. This feature flag will be used in the upcoming Git for Windows v2.11.0 release, to allow early testers to opt-in to use the builtin difftool and flesh out any bugs. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- .gitignore | 1 + Makefile | 3 +- builtin.h | 1 + builtin/difftool.c | 63 +++++++++++++++++++++++++++ git-difftool.perl => git-legacy-difftool.perl | 0 git.c | 6 +++ t/t7800-difftool.sh | 2 + 7 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 builtin/difftool.c rename git-difftool.perl => git-legacy-difftool.perl (100%) diff --git a/.gitignore b/.gitignore index 05cb58a..f96e50e 100644 --- a/.gitignore +++ b/.gitignore @@ -76,6 +76,7 @@ /git-init-db /git-interpret-trailers /git-instaweb +/git-legacy-difftool /git-log /git-ls-files /git-ls-remote diff --git a/Makefile b/Makefile index f53fcc9..7863bc2 100644 --- a/Makefile +++ b/Makefile @@ -527,7 +527,7 @@ SCRIPT_LIB += git-sh-setup SCRIPT_LIB += git-sh-i18n SCRIPT_PERL += git-add--interactive.perl -SCRIPT_PERL += git-difftool.perl +SCRIPT_PERL += git-legacy-difftool.perl SCRIPT_PERL += git-archimport.perl SCRIPT_PERL += git-cvsexportcommit.perl SCRIPT_PERL += git-cvsimport.perl @@ -888,6 +888,7 @@ BUILTIN_OBJS += builtin/diff-files.o BUILTIN_OBJS += builtin/diff-index.o BUILTIN_OBJS += builtin/diff-tree.o BUILTIN_OBJS += builtin/diff.o +BUILTIN_OBJS += builtin/difftool.o BUILTIN_OBJS += builtin/fast-export.o BUILTIN_OBJS += builtin/fetch-pack.o BUILTIN_OBJS += builtin/fetch.o diff --git a/builtin.h b/builtin.h index b9122bc..67f8051 100644 --- a/builtin.h +++ b/builtin.h @@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix); extern int cmd_diff_index(int argc, const char **argv, const char *prefix); extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); +extern int cmd_difftool(int argc, const char **argv, const char *prefix); extern int cmd_fast_export(int argc, const char **argv, const char *prefix); extern int cmd_fetch(int argc, const char **argv, const char *prefix); extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix); diff --git a/builtin/difftool.c b/builtin/difftool.c new file mode 100644 index 0000000..53870bb --- /dev/null +++ b/builtin/difftool.c @@ -0,0 +1,63 @@ +/* + * "git difftool" builtin command + * + * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible + * git-difftool--helper script. + * + * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git. + * The GIT_DIFF* variables are exported for use by git-difftool--helper. + * + * Any arguments that are unknown to this script are forwarded to 'git diff'. + * + * Copyright (C) 2016 Johannes Schindelin + */ +#include "builtin.h" +#include "run-command.h" +#include "exec_cmd.h" + +/* + * NEEDSWORK: this function can go once the legacy-difftool Perl script is + * retired. + * + * We intentionally avoid reading the config directly here, to avoid messing up + * the GIT_* environment variables when we need to fall back to exec()ing the + * Perl script. + */ +static int use_builtin_difftool(void) { + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + int ret; + + argv_array_pushl(&cp.args, + "config", "--bool", "difftool.usebuiltin", NULL); + cp.git_cmd = 1; + if (capture_command(&cp, &out, 6)) + return 0; + strbuf_trim(&out); + ret = !strcmp("true", out.buf); + strbuf_release(&out); + return ret; +} + +int cmd_difftool(int argc, const char **argv, const char *prefix) +{ + /* + * NEEDSWORK: Once the builtin difftool has been tested enough + * and git-legacy-difftool.perl is retired to contrib/, this preamble + * can be removed. + */ + if (!use_builtin_difftool()) { + const char *path = mkpath("%s/git-legacy-difftool", + git_exec_path()); + + if (sane_execvp(path, (char **)argv) < 0) + die_errno("could not exec %s", path); + + return 0; + } + prefix = setup_git_directory(); + trace_repo_setup(prefix); + setup_work_tree(); + + die("TODO"); +} diff --git a/git-difftool.perl b/git-legacy-difftool.perl similarity index 100% rename from git-difftool.perl rename to git-legacy-difftool.perl diff --git a/git.c b/git.c index e8b2baf..a8e6a15 100644 --- a/git.c +++ b/git.c @@ -424,6 +424,12 @@ static struct cmd_struct commands[] = { { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE }, { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, + /* + * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in + * builtin/difftool.c has been removed, this entry should be changed to + * RUN_SETUP | NEED_WORK_TREE + */ + { "difftool", cmd_difftool }, { "fast-export", cmd_fast_export, RUN_SETUP }, { "fetch", cmd_fetch, RUN_SETUP }, { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 70a2de4..b6a6c30 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -23,6 +23,8 @@ prompt_given () test "$prompt" = "Launch 'test-tool' [Y/n]? branch" } +# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired. + # Create a file on master and change it on branch test_expect_success PERL 'setup' ' echo master >file && -- 2.10.1.583.g721a9e0 ^ permalink raw reply related [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-24 20:55 ` [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin Johannes Schindelin @ 2016-11-24 21:08 ` Jeff King 2016-11-24 21:56 ` Johannes Schindelin 0 siblings, 1 reply; 86+ messages in thread From: Jeff King @ 2016-11-24 21:08 UTC (permalink / raw) To: Johannes Schindelin Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker On Thu, Nov 24, 2016 at 09:55:07PM +0100, Johannes Schindelin wrote: > +/* > + * NEEDSWORK: this function can go once the legacy-difftool Perl script is > + * retired. > + * > + * We intentionally avoid reading the config directly here, to avoid messing up > + * the GIT_* environment variables when we need to fall back to exec()ing the > + * Perl script. > + */ > +static int use_builtin_difftool(void) { > + struct child_process cp = CHILD_PROCESS_INIT; > + struct strbuf out = STRBUF_INIT; > + int ret; > + > + argv_array_pushl(&cp.args, > + "config", "--bool", "difftool.usebuiltin", NULL); > + cp.git_cmd = 1; > + if (capture_command(&cp, &out, 6)) > + return 0; > + strbuf_trim(&out); > + ret = !strcmp("true", out.buf); > + strbuf_release(&out); > + return ret; > +} FWIW, it should mostly Just Work to use the internal config functions these days, with the caveat that they will not read repo-level config if you haven't done repo setup yet. I think it would probably be OK to ship with that caveat (people would probably use --global config, or "git -c" for a quick override), but if you really wanted to address it, you can do something like what pager.c:read_early_config() does. Of course, your method here is fine, too; I just know you are sensitive to forking extra processes. Also, a minor nit: capture_command() might return data in "out" with a non-zero exit if the command both generates stdout and exits non-zero itself. I'm not sure that's possible with git-config, though, so it might not be worth worrying about. -Peff ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-24 21:08 ` Jeff King @ 2016-11-24 21:56 ` Johannes Schindelin 2016-11-25 3:18 ` Jeff King 0 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2016-11-24 21:56 UTC (permalink / raw) To: Jeff King; +Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker Hi Peff, On Thu, 24 Nov 2016, Jeff King wrote: > On Thu, Nov 24, 2016 at 09:55:07PM +0100, Johannes Schindelin wrote: > > > +/* > > + * NEEDSWORK: this function can go once the legacy-difftool Perl script is > > + * retired. > > + * > > + * We intentionally avoid reading the config directly here, to avoid messing up > > + * the GIT_* environment variables when we need to fall back to exec()ing the > > + * Perl script. > > + */ > > +static int use_builtin_difftool(void) { > > + struct child_process cp = CHILD_PROCESS_INIT; > > + struct strbuf out = STRBUF_INIT; > > + int ret; > > + > > + argv_array_pushl(&cp.args, > > + "config", "--bool", "difftool.usebuiltin", NULL); > > + cp.git_cmd = 1; > > + if (capture_command(&cp, &out, 6)) > > + return 0; > > + strbuf_trim(&out); > > + ret = !strcmp("true", out.buf); > > + strbuf_release(&out); > > + return ret; > > +} > > FWIW, it should mostly Just Work to use the internal config functions > these days, with the caveat that they will not read repo-level config if > you haven't done repo setup yet. > > I think it would probably be OK to ship with that caveat (people would > probably use --global config, or "git -c" for a quick override), but if > you really wanted to address it, you can do something like what > pager.c:read_early_config() does. The config setting is already overkill (and does even make something much harder than before: running tests with the builtin difftool used to be as simply as `touch use-builtin-difftool && make -C t t7800-difftool.sh, now I have to edit t7800-difftool.sh to configure difftool.useBuiltin, and without the repo-level config even that would not be working). Imitating read_early_config() would be overkill deluxe. > Of course, your method here is fine, too; I just know you are sensitive > to forking extra processes. > > Also, a minor nit: capture_command() might return data in "out" with a > non-zero exit if the command both generates stdout and exits non-zero > itself. I'm not sure that's possible with git-config, though, so it > might not be worth worrying about. As it is, I spent way too much time on a feature flag *that will go away real soon*. And not only I spent too much time on it: everybody who even bothered to think about it spent too much time on it. It is a temporary feature flag. It will go away. If it is inefficient, or inelegant, it won't matter in a month from now. In other words, it was not really necessary to spend all of that time and all of that brain power to first discuss the shortcomings of having the presence of a file in exec path as a feature flag, then converting it into a config setting, only to find out that this *causes* problems that were not there before. Frankly, that was not what I was hoping for. I was hoping to get a decent review of the *difftool* functionality. David did a fine job to provide that review. All that discussion about the temporary feature flag was really a side track for me, and I hate to admit that I let myself get sucked into it, and it cost me quite some time that I would have rather spent on release engineering the preview based on v2.11.0-rc3 (including the difftool with *any* type of working opt-in feature flag). Maybe I will write more explicitly in the next cover letter what I intend to do, and what parts of the patch series is intended to be throw-away material (hence "good enough" is good enough, and discussions about it are really wasting all of our time). Of course, I cannot dictate what other people find interesting to comment on, but at least I will have a good excuse to ignore suggestions that only distract from the work that is really needed. My only solace is that I did some substantially more intensive testing in the wake of this discussion, thanks to the test suite breakages incurred by switching the feature flag to a soon-to-be-abandoned config setting. That makes me much more confident about the builtin difftool, which is all I wanted in the first place. Ciao, Dscho ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-24 21:56 ` Johannes Schindelin @ 2016-11-25 3:18 ` Jeff King 2016-11-25 11:05 ` Johannes Schindelin 0 siblings, 1 reply; 86+ messages in thread From: Jeff King @ 2016-11-25 3:18 UTC (permalink / raw) To: Johannes Schindelin Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker On Thu, Nov 24, 2016 at 10:56:23PM +0100, Johannes Schindelin wrote: > > I think it would probably be OK to ship with that caveat (people would > > probably use --global config, or "git -c" for a quick override), but if > > you really wanted to address it, you can do something like what > > pager.c:read_early_config() does. > > The config setting is already overkill (and does even make something much > harder than before: running tests with the builtin difftool used to be as > simply as `touch use-builtin-difftool && make -C t t7800-difftool.sh, now > I have to edit t7800-difftool.sh to configure difftool.useBuiltin, and > without the repo-level config even that would not be working). > > Imitating read_early_config() would be overkill deluxe. I would have expected it to just be a build-time flag, like: make BUILTIN_DIFFTOOL=Yes test but I did not closely follow the rest of the conversation, so I am probably just repeating bits that were already said. So probably ignore me. I'm happy with pretty much anything under the reasoning of "this does not matter much because it is going away soon". -Peff ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-25 3:18 ` Jeff King @ 2016-11-25 11:05 ` Johannes Schindelin 2016-11-25 17:19 ` Jeff King 0 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2016-11-25 11:05 UTC (permalink / raw) To: Jeff King; +Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker Hi Peff, On Thu, 24 Nov 2016, Jeff King wrote: > On Thu, Nov 24, 2016 at 10:56:23PM +0100, Johannes Schindelin wrote: > > > > I think it would probably be OK to ship with that caveat (people would > > > probably use --global config, or "git -c" for a quick override), but if > > > you really wanted to address it, you can do something like what > > > pager.c:read_early_config() does. > > > > The config setting is already overkill (and does even make something much > > harder than before: running tests with the builtin difftool used to be as > > simply as `touch use-builtin-difftool && make -C t t7800-difftool.sh, now > > I have to edit t7800-difftool.sh to configure difftool.useBuiltin, and > > without the repo-level config even that would not be working). > > > > Imitating read_early_config() would be overkill deluxe. > > I would have expected it to just be a build-time flag, like: > > make BUILTIN_DIFFTOOL=Yes test That works for Git developers. I want to let as many users as possible test the builtin difftool. Hopefully a lot more users than there are Git developers. Which means that I need a feature flag in production code, not a build time flag. > I'm happy with pretty much anything under the reasoning of "this does not > matter much because it is going away soon". Yeah, well, I am more happy with anything along the lines of David's review, pointing out flaws in the current revision of the builtin difftool before it bites users ;-) Ciao, Dscho ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-25 11:05 ` Johannes Schindelin @ 2016-11-25 17:19 ` Jeff King 2016-11-25 17:41 ` Johannes Schindelin 0 siblings, 1 reply; 86+ messages in thread From: Jeff King @ 2016-11-25 17:19 UTC (permalink / raw) To: Johannes Schindelin Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker On Fri, Nov 25, 2016 at 12:05:00PM +0100, Johannes Schindelin wrote: > > I would have expected it to just be a build-time flag, like: > > > > make BUILTIN_DIFFTOOL=Yes test > > That works for Git developers. > > I want to let as many users as possible test the builtin difftool. > Hopefully a lot more users than there are Git developers. > > Which means that I need a feature flag in production code, not a build > time flag. Ah, I didn't realize that was a requirement. If this is going to be part of a release and real end-users are going to see it, that does make me think the config option is the better path (than the presence of some file), as it's our standard way of tweaking run-time behavior. The implementation can still remain slightly gross if it's eventually going away. > > I'm happy with pretty much anything under the reasoning of "this does not > > matter much because it is going away soon". > > Yeah, well, I am more happy with anything along the lines of David's > review, pointing out flaws in the current revision of the builtin difftool > before it bites users ;-) Sorry, I can't really help much there, not having much knowledge of difftool. -Peff ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-25 17:19 ` Jeff King @ 2016-11-25 17:41 ` Johannes Schindelin 2016-11-25 17:47 ` Jeff King 0 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2016-11-25 17:41 UTC (permalink / raw) To: Jeff King; +Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker Hi Peff, On Fri, 25 Nov 2016, Jeff King wrote: > On Fri, Nov 25, 2016 at 12:05:00PM +0100, Johannes Schindelin wrote: > > > > I would have expected it to just be a build-time flag, like: > > > > > > make BUILTIN_DIFFTOOL=Yes test > > > > That works for Git developers. > > > > I want to let as many users as possible test the builtin difftool. > > Hopefully a lot more users than there are Git developers. > > > > Which means that I need a feature flag in production code, not a build > > time flag. > > Ah, I didn't realize that was a requirement. If this is going to be part > of a release and real end-users are going to see it, that does make me > think the config option is the better path (than the presence of some > file), as it's our standard way of tweaking run-time behavior. So how do you easily switch back and forth between testing the old vs the new difftool via the test suite? Ciao, Dscho ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-25 17:41 ` Johannes Schindelin @ 2016-11-25 17:47 ` Jeff King 2016-11-26 12:22 ` Johannes Schindelin 0 siblings, 1 reply; 86+ messages in thread From: Jeff King @ 2016-11-25 17:47 UTC (permalink / raw) To: Johannes Schindelin Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker On Fri, Nov 25, 2016 at 06:41:23PM +0100, Johannes Schindelin wrote: > > Ah, I didn't realize that was a requirement. If this is going to be part > > of a release and real end-users are going to see it, that does make me > > think the config option is the better path (than the presence of some > > file), as it's our standard way of tweaking run-time behavior. > > So how do you easily switch back and forth between testing the old vs the > new difftool via the test suite? If it's for a specific tool, I'd consider teaching the test suite to run the whole script twice: once with the flag set and once without. That is sometimes more complicated, though, if the script creates many sub-repos. An environment variable might be more natural. If you already support flipping the default via config, you can probably do: GIT_CONFIG_PARAMETERS="'difftool.usebuiltin=true'" export GIT_CONFIG_PARAMETERS -Peff ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-25 17:47 ` Jeff King @ 2016-11-26 12:22 ` Johannes Schindelin 2016-11-26 16:19 ` Jeff King 0 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2016-11-26 12:22 UTC (permalink / raw) To: Jeff King; +Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker Hi Peff, On Fri, 25 Nov 2016, Jeff King wrote: > On Fri, Nov 25, 2016 at 06:41:23PM +0100, Johannes Schindelin wrote: > > > > Ah, I didn't realize that was a requirement. If this is going to be part > > > of a release and real end-users are going to see it, that does make me > > > think the config option is the better path (than the presence of some > > > file), as it's our standard way of tweaking run-time behavior. > > > > So how do you easily switch back and forth between testing the old vs the > > new difftool via the test suite? > > If it's for a specific tool, I'd consider teaching the test suite to run > the whole script twice: once with the flag set and once without. > > That is sometimes more complicated, though, if the script creates many > sub-repos. An environment variable might be more natural. If you already > support flipping the default via config, you can probably do: > > GIT_CONFIG_PARAMETERS="'difftool.usebuiltin=true'" > export GIT_CONFIG_PARAMETERS Except that that does not work, of course. To figure out why, apply this diff: -- snip -- diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 17f3008277..27159f65f3 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -10,6 +10,9 @@ Testing basic diff tool invocation . ./test-lib.sh +echo "config $(git config difftool.usebuiltin)." >&2 +exit 1 + difftool_test_setup () { test_config diff.tool test-tool && diff --git a/t/test-lib.sh b/t/test-lib.sh index 9980a46133..0ddeded92b 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -86,6 +86,7 @@ EDITOR=: # /usr/xpg4/bin/sh and /bin/ksh to bail out. So keep the unsets # deriving from the command substitution clustered with the other # ones. +echo "before $(git config difftool.usebuiltin)." >&2 unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e ' my @env = keys %ENV; my $ok = join("|", qw( @@ -104,6 +105,7 @@ unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e ' my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env); print join("\n", @vars); ') +echo "after $(git config difftool.usebuiltin)." >&2 unset XDG_CONFIG_HOME unset GITPERLLIB GIT_AUTHOR_EMAIL=author@example.com -- snap -- and then weep at this output: GIT_CONFIG_PARAMETERS="'difftool.usebuiltin=true'"; export GIT_CONFIG_PARAMETERS; bash t7800-difftool.sh -i -v -x before true. after . Initialized empty Git repository in /home/virtualbox/git/git-for-windows/t/trash directory.t7800-difftool/.git/ config . FATAL: Unexpected exit with code 1 In other words, GIT_CONFIG_PARAMETERS is *explicitly scrubbed* from the environment when we run our tests (by the code block between the "before" and the "after" statements in the diff above). Ciao, Dscho ^ permalink raw reply related [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-26 12:22 ` Johannes Schindelin @ 2016-11-26 16:19 ` Jeff King 2016-11-26 13:01 ` Johannes Schindelin 0 siblings, 1 reply; 86+ messages in thread From: Jeff King @ 2016-11-26 16:19 UTC (permalink / raw) To: Johannes Schindelin Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker On Sat, Nov 26, 2016 at 01:22:28PM +0100, Johannes Schindelin wrote: > In other words, GIT_CONFIG_PARAMETERS is *explicitly scrubbed* from the > environment when we run our tests (by the code block between the "before" > and the "after" statements in the diff above). Sorry if I wasn't clear. I meant to modify t7800 to run the tests twice, once with the existing script and once with the builtin. I.e., to set the variable after test-lib.sh has done its scrubbing, and then use a loop or similar to go through the tests twice. If you want to control it from outside the test script, you'd need something like: if test "$GIT_TEST_DIFFTOOL" = "builtin" then GIT_CONFIG_PARAMETERS=... fi -Peff ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-26 16:19 ` Jeff King @ 2016-11-26 13:01 ` Johannes Schindelin 2016-11-27 16:50 ` Jeff King 0 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2016-11-26 13:01 UTC (permalink / raw) To: Jeff King; +Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker Hi Peff, On Sat, 26 Nov 2016, Jeff King wrote: > On Sat, Nov 26, 2016 at 01:22:28PM +0100, Johannes Schindelin wrote: > > > In other words, GIT_CONFIG_PARAMETERS is *explicitly scrubbed* from the > > environment when we run our tests (by the code block between the "before" > > and the "after" statements in the diff above). > > Sorry if I wasn't clear. I meant to modify t7800 to run the tests twice, > once with the existing script and once with the builtin. I.e., to set > the variable after test-lib.sh has done its scrubbing, and then use a > loop or similar to go through the tests twice. > > If you want to control it from outside the test script, you'd need > something like: > > if test "$GIT_TEST_DIFFTOOL" = "builtin" That is a bit magic. I first used "GIT_USE_BUILTIN_DIFFTOOL" and it did not work. My name is arguably more correct (see also Jakub's note about "naming is hard"), but yours works because there is a "TEST" substring in it. Ciao, Dscho ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-26 13:01 ` Johannes Schindelin @ 2016-11-27 16:50 ` Jeff King 2016-11-28 17:06 ` Junio C Hamano 0 siblings, 1 reply; 86+ messages in thread From: Jeff King @ 2016-11-27 16:50 UTC (permalink / raw) To: Johannes Schindelin Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker On Sat, Nov 26, 2016 at 02:01:36PM +0100, Johannes Schindelin wrote: > > If you want to control it from outside the test script, you'd need > > something like: > > > > if test "$GIT_TEST_DIFFTOOL" = "builtin" > > That is a bit magic. I first used "GIT_USE_BUILTIN_DIFFTOOL" and it did > not work. My name is arguably more correct (see also Jakub's note about > "naming is hard"), but yours works because there is a "TEST" substring in > it. Yes. You are free to add an exception to the env list in test-lib.sh, but we usually use GIT_TEST_* to avoid having to do so. -Peff ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-27 16:50 ` Jeff King @ 2016-11-28 17:06 ` Junio C Hamano 2016-11-28 17:34 ` Johannes Schindelin 0 siblings, 1 reply; 86+ messages in thread From: Junio C Hamano @ 2016-11-28 17:06 UTC (permalink / raw) To: Jeff King; +Cc: Johannes Schindelin, git, David Aguilar, Dennis Kaarsemaker Jeff King <peff@peff.net> writes: > On Sat, Nov 26, 2016 at 02:01:36PM +0100, Johannes Schindelin wrote: > >> > If you want to control it from outside the test script, you'd need >> > something like: >> > >> > if test "$GIT_TEST_DIFFTOOL" = "builtin" >> >> That is a bit magic. I first used "GIT_USE_BUILTIN_DIFFTOOL" and it did >> not work. My name is arguably more correct (see also Jakub's note about >> "naming is hard"), but yours works because there is a "TEST" substring in >> it. > > Yes. You are free to add an exception to the env list in test-lib.sh, > but we usually use GIT_TEST_* to avoid having to do so. Perhaps - The switch between "do I use builtin, or scripted?" mechanism in 1/2 can look at an environment (just like the old "am" rewrite series did), instead of configuration. This would make the code a lot more simppler (you do not have to worry about the interaction between "setup" and .git/config). - That environment variable can be named GIT_TEST_BUILTIN_DIFFTOOL; after all, people are opting into helping to test the new shiny to make/prove it ready sooner. - The bulk of the existing test for difftool can be moved to a dot-included file (in a way similar to t/annotate-tests are usable to test both annotate and blame-imitating-annotate). Existing PERL prerequisites can all be lost. - Two tests can include that dot-included file; one would explicitly unset that environment (and gives up without PERL prerequisite), while the other explicitly sets it. ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-28 17:06 ` Junio C Hamano @ 2016-11-28 17:34 ` Johannes Schindelin 2016-11-28 19:27 ` Junio C Hamano 2016-11-30 16:02 ` Jakub Narębski 0 siblings, 2 replies; 86+ messages in thread From: Johannes Schindelin @ 2016-11-28 17:34 UTC (permalink / raw) To: Junio C Hamano; +Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker Hi, On Mon, 28 Nov 2016, Junio C Hamano wrote: > Jeff King <peff@peff.net> writes: > > > On Sat, Nov 26, 2016 at 02:01:36PM +0100, Johannes Schindelin wrote: > > > >> > If you want to control it from outside the test script, you'd need > >> > something like: > >> > > >> > if test "$GIT_TEST_DIFFTOOL" = "builtin" > >> > >> That is a bit magic. I first used "GIT_USE_BUILTIN_DIFFTOOL" and it did > >> not work. My name is arguably more correct (see also Jakub's note about > >> "naming is hard"), but yours works because there is a "TEST" substring in > >> it. > > > > Yes. You are free to add an exception to the env list in test-lib.sh, > > but we usually use GIT_TEST_* to avoid having to do so. > > Perhaps > > - The switch between "do I use builtin, or scripted?" mechanism in > 1/2 can look at an environment (just like the old "am" rewrite > series did), instead of configuration. This would make the code > a lot more simppler (you do not have to worry about the > interaction between "setup" and .git/config). > > - That environment variable can be named GIT_TEST_BUILTIN_DIFFTOOL; > after all, people are opting into helping to test the new shiny > to make/prove it ready sooner. > > - The bulk of the existing test for difftool can be moved to a > dot-included file (in a way similar to t/annotate-tests are > usable to test both annotate and blame-imitating-annotate). > Existing PERL prerequisites can all be lost. > > - Two tests can include that dot-included file; one would > explicitly unset that environment (and gives up without PERL > prerequisite), while the other explicitly sets it. If my main worry was the test suite, I would agree with this plan. However, I have been bitten time and again by problems that occurred only in production, our test suite (despite taking already waaaaaay too long to be truly useful in my daily development) was simply not good enough. So my plan was different: to let end users opt-in to test this new beast thoroughly, more thoroughly than any review would. And for that, environment variables are just not an option. I need something that can be configured in a portable application, so that the main Git for Windows installation is unaffected. My original "create a file in libexec/git-core/" was simple, did the job reliably, and worked also for testing. It is a pity that you two gentlemen shot it down for being inelegant. And ever since, we try to find a solution that is as simple, works as reliably, also for testing, *and* appeases your tastes. Ciao, Dscho ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-28 17:34 ` Johannes Schindelin @ 2016-11-28 19:27 ` Junio C Hamano 2016-11-29 20:36 ` Johannes Schindelin 2016-11-30 16:02 ` Jakub Narębski 1 sibling, 1 reply; 86+ messages in thread From: Junio C Hamano @ 2016-11-28 19:27 UTC (permalink / raw) To: Johannes Schindelin; +Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker Johannes Schindelin <Johannes.Schindelin@gmx.de> writes: > However, I have been bitten time and again by problems that occurred only > in production, our test suite (despite taking already waaaaaay too long to > be truly useful in my daily development) was simply not good enough. > > So my plan was different: to let end users opt-in to test this new beast > thoroughly, more thoroughly than any review would. I agree with that 100%. We need to ensure "fallback to known working code" escape hatch is robust for that plan to work well, and that is why (1) I have been more focused on getting 1/2 right, and (2) I do not think it should be Windows-only like in your early plan, and (3) I do not think it would be "this will merely be there for only a month or so", like you said earlier. > And for that, environment variables are just not an option. I need > something that can be configured in a portable application, so that the > main Git for Windows installation is unaffected. I am not sure I follow here. Are you saying that the users who are opting into the experiment will keep two installations, one for daily use that avoids getting hit by the experimental code and the other that is used for testing? How are they switching between the two? By using different %PATH%? I am not sure how it is different from setting an environment $GIT_TEST_BUILTIN_DIFFTOOL. In any case, I do not have strong preference between environment and configuration. If you can make 1/2 robust with configuration, that is just as well. My message you are responding to was merely to suggest another possibility. The latter two points in my four-bullet list are hopefully still viable if you go with the configuration; it may go something like: - The bulk of the tests is moved into a common dot-sourced file, with (1) PERL prerequite stripped and (2) "git difftool" replaced with $git_difftool - Two test files do one of git_difftool="git difftool" git_difftool="git -c difftool.useBuiltin=true difftool" and include the dot-sourced file. The one that does the former needs to give up early depending on PERL prerequisite. perhaps. > My original "create a file in libexec/git-core/" was simple, did the job > reliably, and worked also for testing. It may have been OK for quick-and-dirty hack during development, but I do not think it was good in anything released. ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-28 19:27 ` Junio C Hamano @ 2016-11-29 20:36 ` Johannes Schindelin 2016-11-29 20:49 ` Jeff King 2016-11-29 20:55 ` Junio C Hamano 0 siblings, 2 replies; 86+ messages in thread From: Johannes Schindelin @ 2016-11-29 20:36 UTC (permalink / raw) To: Junio C Hamano; +Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker Hi Junio, On Mon, 28 Nov 2016, Junio C Hamano wrote: > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes: > > > However, I have been bitten time and again by problems that occurred only > > in production, our test suite (despite taking already waaaaaay too long to > > be truly useful in my daily development) was simply not good enough. > > > > So my plan was different: to let end users opt-in to test this new beast > > thoroughly, more thoroughly than any review would. > > I agree with that 100%. > > [...] > > > And for that, environment variables are just not an option. I need > > something that can be configured in a portable application, so that the > > main Git for Windows installation is unaffected. > > I am not sure I follow here. > > Are you saying that the users who are opting into the experiment > will keep two installations, one for daily use that avoids getting > hit by the experimental code and the other that is used for testing? I have obviously done a real bad job at explaining the Windows situation well enough. Many, many users have multiple installations of Git for Windows. If you have GitHub for Windows and installed the command-line tools: you got one. If you installed Git for Windows, you got another one. If you installed Visual Studio, chances are you have another one. If you got any number of third-party tools requiring Git functionality, you have another one. They all live in separate directories that are their own little pseudo Unix root directory structures, complete with etc/, usr/, var/. Users do not necessarily keep track, or for that matter, are aware of, the multiple different installations. Obviously, I do not want any installation other than the one the user just installed to pick up on the configuration. So the suggestion by both you and Peff, to use an environment variable, which is either global, or requires the user to set it manually per session, is simply not a good idea at all. > > My original "create a file in libexec/git-core/" was simple, did the job > > reliably, and worked also for testing. > > It may have been OK for quick-and-dirty hack during development, but > I do not think it was good in anything released. Well, you say that it is quick and dirty. I say it is the only viable solution I saw so far. All proposed alternative solutions fall flat on their bellies, simply by not working in all the cases I need them to work. As I said elsewhere: I look for a correct solution first, and then I thrive to make it pretty. You start the other way round, and I do not have time for that right now. Ciao, Dscho ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-29 20:36 ` Johannes Schindelin @ 2016-11-29 20:49 ` Jeff King 2016-11-30 12:30 ` Johannes Schindelin 2016-11-29 20:55 ` Junio C Hamano 1 sibling, 1 reply; 86+ messages in thread From: Jeff King @ 2016-11-29 20:49 UTC (permalink / raw) To: Johannes Schindelin Cc: Junio C Hamano, git, David Aguilar, Dennis Kaarsemaker On Tue, Nov 29, 2016 at 09:36:55PM +0100, Johannes Schindelin wrote: > So the suggestion by both you and Peff, to use an environment variable, > which is either global, or requires the user to set it manually per > session, is simply not a good idea at all. No, my suggestion was to use config and have the test suite use an environment variable to test both cases (preferably automatically, without the user having to do anything). I do not see how that fails to cover all of your use cases. -Peff ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-29 20:49 ` Jeff King @ 2016-11-30 12:30 ` Johannes Schindelin 2016-11-30 12:35 ` Jeff King 0 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2016-11-30 12:30 UTC (permalink / raw) To: Jeff King; +Cc: Junio C Hamano, git, David Aguilar, Dennis Kaarsemaker Hi Peff, On Tue, 29 Nov 2016, Jeff King wrote: > On Tue, Nov 29, 2016 at 09:36:55PM +0100, Johannes Schindelin wrote: > > > So the suggestion by both you and Peff, to use an environment variable, > > which is either global, or requires the user to set it manually per > > session, is simply not a good idea at all. > > No, my suggestion was to use config and have the test suite use an > environment variable to test both cases (preferably automatically, > without the user having to do anything). > > I do not see how that fails to cover all of your use cases. Oh, so the suggestion is to have *both* a config *and* an environment variable. That is not elegant. Ciao, Dscho ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-30 12:30 ` Johannes Schindelin @ 2016-11-30 12:35 ` Jeff King 0 siblings, 0 replies; 86+ messages in thread From: Jeff King @ 2016-11-30 12:35 UTC (permalink / raw) To: Johannes Schindelin Cc: Junio C Hamano, git, David Aguilar, Dennis Kaarsemaker On Wed, Nov 30, 2016 at 01:30:47PM +0100, Johannes Schindelin wrote: > On Tue, 29 Nov 2016, Jeff King wrote: > > > On Tue, Nov 29, 2016 at 09:36:55PM +0100, Johannes Schindelin wrote: > > > > > So the suggestion by both you and Peff, to use an environment variable, > > > which is either global, or requires the user to set it manually per > > > session, is simply not a good idea at all. > > > > No, my suggestion was to use config and have the test suite use an > > environment variable to test both cases (preferably automatically, > > without the user having to do anything). > > > > I do not see how that fails to cover all of your use cases. > > Oh, so the suggestion is to have *both* a config *and* an environment > variable. That is not elegant. No, that is not at all what I said. I was going to explain myself again, but I do not see what good it would do, as clearly my point did not come across in the other three emails. And then you would just complain that I am making work for you. So whatever. I do not care about your difftool topic at all. Do whatever you like (which hey, I already said before, too). -Peff ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-29 20:36 ` Johannes Schindelin 2016-11-29 20:49 ` Jeff King @ 2016-11-29 20:55 ` Junio C Hamano 2016-11-30 12:30 ` Johannes Schindelin 1 sibling, 1 reply; 86+ messages in thread From: Junio C Hamano @ 2016-11-29 20:55 UTC (permalink / raw) To: Johannes Schindelin; +Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker Johannes Schindelin <Johannes.Schindelin@gmx.de> writes: > So the suggestion by both you and Peff, to use an environment variable, > which is either global, or requires the user to set it manually per > session, is simply not a good idea at all. As I already said, I do not have a strong preference between config and env. I raised the env as a possible alternative that you can think about its pros and cons, and as I already said, if you thought and your concluded that config would work better for your needs, that is fine by me. ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-29 20:55 ` Junio C Hamano @ 2016-11-30 12:30 ` Johannes Schindelin 2016-12-01 23:33 ` Junio C Hamano 0 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2016-11-30 12:30 UTC (permalink / raw) To: Junio C Hamano; +Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker Hi Junio, On Tue, 29 Nov 2016, Junio C Hamano wrote: > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes: > > > So the suggestion by both you and Peff, to use an environment variable, > > which is either global, or requires the user to set it manually per > > session, is simply not a good idea at all. > > As I already said, I do not have a strong preference between config > and env. I raised the env as a possible alternative that you can > think about its pros and cons, and as I already said, if you thought > and your concluded that config would work better for your needs, > that is fine by me. The env flat out fails, on the grounds of not integrating nicely into a Git for Windows installer. The config kinda works now. But for what price. It stole 4 hours I did not have. When the libexec/git-core/use-builtin-difftool solution took me a grand total of half an hour to devise, implement and test. And you know what? I still do not really see what is so bad about it. And I still see what is bad about the config "solution": it *creates* a chicken-and-egg problem with the order of config reading vs running scripts. It *creates* problems for requiring to spawn a `git config` call because reading the config in-process would mess up the global variables and environment *beyond repair*, making it *impossible* to even spawn the git-legacy-difftool Perl script. In short: the config setting now works. But it is ugly as hell. I wish I never had listened to you. Ciao, Dscho ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-30 12:30 ` Johannes Schindelin @ 2016-12-01 23:33 ` Junio C Hamano 2016-12-05 10:36 ` Johannes Schindelin 0 siblings, 1 reply; 86+ messages in thread From: Junio C Hamano @ 2016-12-01 23:33 UTC (permalink / raw) To: Johannes Schindelin; +Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker Johannes Schindelin <Johannes.Schindelin@gmx.de> writes: > The config kinda works now. But for what price. It stole 4 hours I did not > have. When the libexec/git-core/use-builtin-difftool solution took me a > grand total of half an hour to devise, implement and test. > > And you know what? I still do not really see what is so bad about it. I was wondering if I should explain myself again, even though I do not see what good it would do, as clearly my point did not come across in the other emails. And then you would just complain that I am making work for you. Clearly you do not seem to see why placing random files in $GIT_EXEC_PATH, which is a place for git subcommand implementations, is wrong, so I won't repeat it to you again. But you need to remember that you are not working on a Windows-only project. In non-Windows environment, many users would not have write access to /usr/libexec/git-core directory, but it is not just easy for them to write into ~/.gitconfig, but that is the way they are accustomed to, in order to affect the behaviour of Git for them. As to "I have to spawn config", I think it is sensible to start the cmd_difftool() wrapper without adding RUN_SETUP to the command table, then call git_config_get_bool() to check the configuration only from system and per-user files, and then finally either call into builtin_difftool() where setup_git_directory() is called, or spawn the scripted difftool, as Peff already said. Your "users opt-in while installing" is not about setting per-repository option. Calling git_config*(), setup_git_directory() and then git_config*() in this order should be safe, as setup_git_directory() would clear potentially cached configuration values read by any previous git_config*() calls, so any configuration enquiry made by builtin_difftool() would read from all three sources, not just system and per-user. So there is no chicken-and-egg issue, either. ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-12-01 23:33 ` Junio C Hamano @ 2016-12-05 10:36 ` Johannes Schindelin 2016-12-05 18:37 ` Junio C Hamano 0 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2016-12-05 10:36 UTC (permalink / raw) To: Junio C Hamano; +Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker Hi Junio, On Thu, 1 Dec 2016, Junio C Hamano wrote: > As to "I have to spawn config", I think it is sensible to start the > cmd_difftool() wrapper without adding RUN_SETUP to the command table, > then call git_config_get_bool() to check the configuration only from > system and per-user files, and then finally either call into > builtin_difftool() where setup_git_directory() is called, or spawn the > scripted difftool, as Peff already said. I just spent a considerable amount of time to figure out that I overlooked your comment about "only from system and per-user files". > Your "users opt-in while installing" is not about setting per-repository > option. Wow. That would make things really inconsistent. I know that *I* would want to be able to switch that opt-in feature off for repositories where I absolutely rely on some rock-solid difftool. And as a user I would not only be shocked when it would not work as expected, but I would be *positively* shocked to learn that this is intended, by design. Seriously, do you really think it is a good idea to have git_config_get_value() *ignore* any value in .git/config? Really? It caught *me* by surprise, and it definitely makes for a very, very bad user experience. And this is much more fundamental than just difftool.useBuiltin. I see for example that our pager.<cmd> setting is ignoring per-repository settings. That is, if the user sets pager.<cmd> in ~/.gitconfig and then tries to override this in a specific repository (which any user would only do for very good reasons, I am sure), Git would happily ignore that careful setup and create an angry user. The only reason this did not blow up in our face yet is that users apparently do not make use of this feature yet. Which does not make this behavior (that "git_config_get_value()" happily ignores .git/config) less broken. We need to fix this. Ciao, Dscho ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-12-05 10:36 ` Johannes Schindelin @ 2016-12-05 18:37 ` Junio C Hamano 2016-12-06 13:16 ` Johannes Schindelin 0 siblings, 1 reply; 86+ messages in thread From: Junio C Hamano @ 2016-12-05 18:37 UTC (permalink / raw) To: Johannes Schindelin; +Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker Johannes Schindelin <Johannes.Schindelin@gmx.de> writes: > Seriously, do you really think it is a good idea to have > git_config_get_value() *ignore* any value in .git/config? When we do not know ".git/" directory we see is the repository we were told to work on, then we should ignore ".git/config" file. So allowing git_config_get_value() to _ignore_ ".git/config" before the program calls setup_git_directory() does have its uses. But I agree that "difftool.useBuiltin" that flips between two implementations is a different use case than the above. Both implementations eventually want to work on ".git/" and would want to read from ".git/config". > We need to fix this. I have a feeling that "environment modifications that cannot be undone" we used as the rationale in 73c2779f42 ("builtin-am: implement skeletal builtin am", 2015-08-04) might be overly pessimistic and in order to implement undo_setup_git_directory(), all we need to do may just be matter of doing a chdir(2) back to prefix and unsetting GIT_PREFIX environment, but I haven't looked into details of the setup sequence recently. ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-12-05 18:37 ` Junio C Hamano @ 2016-12-06 13:16 ` Johannes Schindelin 2016-12-06 13:36 ` Jeff King 0 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2016-12-06 13:16 UTC (permalink / raw) To: Junio C Hamano; +Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker Hi Junio, On Mon, 5 Dec 2016, Junio C Hamano wrote: > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes: > > > Seriously, do you really think it is a good idea to have > > git_config_get_value() *ignore* any value in .git/config? > > When we do not know ".git/" directory we see is the repository we were > told to work on, then we should ignore ".git/config" file. So allowing > git_config_get_value() to _ignore_ ".git/config" before the program > calls setup_git_directory() does have its uses. I think you are wrong. This is yet another inconsistent behavior that violates the Law of Least Surprises. > > We need to fix this. > > I have a feeling that "environment modifications that cannot be undone" > we used as the rationale in 73c2779f42 ("builtin-am: implement skeletal > builtin am", 2015-08-04) might be overly pessimistic and in order to > implement undo_setup_git_directory(), all we need to do may just be > matter of doing a chdir(2) back to prefix and unsetting GIT_PREFIX > environment, but I haven't looked into details of the setup sequence > recently. The problem is that we may not know enough anymore to "undo setup_git_directory()", as some environment variables may have been set before calling Git. If we simply unset the environment variables, we do it incorrectly. On the other hand, the environment variables *may* have been set by setup_git_directory(). In which case we *do* have to unset them. The entire setup_git_directory() logic is a bit of a historically-grown garden. Ciao, Dscho ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-12-06 13:16 ` Johannes Schindelin @ 2016-12-06 13:36 ` Jeff King 2016-12-06 14:48 ` Johannes Schindelin 0 siblings, 1 reply; 86+ messages in thread From: Jeff King @ 2016-12-06 13:36 UTC (permalink / raw) To: Johannes Schindelin Cc: Junio C Hamano, git, David Aguilar, Dennis Kaarsemaker On Tue, Dec 06, 2016 at 02:16:35PM +0100, Johannes Schindelin wrote: > > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes: > > > > > Seriously, do you really think it is a good idea to have > > > git_config_get_value() *ignore* any value in .git/config? > > > > When we do not know ".git/" directory we see is the repository we were > > told to work on, then we should ignore ".git/config" file. So allowing > > git_config_get_value() to _ignore_ ".git/config" before the program > > calls setup_git_directory() does have its uses. > > I think you are wrong. This is yet another inconsistent behavior that > violates the Law of Least Surprises. There are surprises in this code any way you turn. If we have not called setup_git_directory(), then how does git_config_get_value() know if we are in a repository or not? Should it blindly look at ".git/config"? Now your program behaves differently depending on whether you are in the top-level of the working tree. Should it speculatively do repo discovery, and use any discovered config file? Now some commands respect config that they shouldn't (e.g., running "git init foo.git" from inside another repository will accidentally pick up the value of core.sharedrepository from wherever you happen to run it). So I think the caller of the config code has to provide some kind of context about how it is expecting to run and how the value will be used. Right now if setup_git_directory() or similar hasn't been called, the config code does not look. Ideally there would be a way for a caller to say "I am running early and not even sure yet if we are in a repo; please speculatively try to find repo config for me". The pager code does this manually, and without great accuracy; see the hack in pager.c's read_early_config(). I think the way forward is: 1. Make that an optional behavior in git_config_with_options() so other spots can reuse it (probably alias lookup, and something like your difftool config). 2. Make it more accurate. Right now it blindly looks in .git/config, but it should be able to do the usual repo-detection (_without_ actually entering the repo) to try to find a possible config file. -Peff ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-12-06 13:36 ` Jeff King @ 2016-12-06 14:48 ` Johannes Schindelin 2016-12-06 15:09 ` Jeff King 0 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2016-12-06 14:48 UTC (permalink / raw) To: Jeff King; +Cc: Junio C Hamano, git, David Aguilar, Dennis Kaarsemaker Hi Peff, On Tue, 6 Dec 2016, Jeff King wrote: > On Tue, Dec 06, 2016 at 02:16:35PM +0100, Johannes Schindelin wrote: > > > > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes: > > > > > > > Seriously, do you really think it is a good idea to have > > > > git_config_get_value() *ignore* any value in .git/config? > > > > > > When we do not know ".git/" directory we see is the repository we > > > were told to work on, then we should ignore ".git/config" file. So > > > allowing git_config_get_value() to _ignore_ ".git/config" before the > > > program calls setup_git_directory() does have its uses. > > > > I think you are wrong. This is yet another inconsistent behavior that > > violates the Law of Least Surprises. > > There are surprises in this code any way you turn. If we have not > called setup_git_directory(), then how does git_config_get_value() know > if we are in a repository or not? My biggest surprise, frankly, would be that `git init` and `git clone` are not special-cased. > Should it blindly look at ".git/config"? Absolutely not, of course. You did not need me to say that. > Now your program behaves differently depending on whether you are in the > top-level of the working tree. Exactly. This, BTW, is already how the code would behave if anybody called `git_path()` before `setup_git_directory()`, as the former function implicitly calls `setup_git_env()` which does *not* call `setup_git_directory()` but *does* set up `git_dir` which is then used by `do_git_config_sequence()`.. We have a few of these nasty surprises in our code base, where code silently assumes that global state is set up correctly, and succeeds in sometimes surprising ways if it is not. > Should it speculatively do repo discovery, and use any discovered config > file? Personally, I find the way we discover the repository most irritating. It seems that we have multiple, mutually incompatible code paths (`setup_git_directory()` and `setup_git_env()` come to mind already, and it does not help that consecutive calls to `setup_git_directory()` will yield a very unexpected outcome). Just try to explain to a veteran software engineer why you cannot call `setup_git_directory_gently()` multiple times and expect the very same return value every time. Another irritation is that some commands that clearly would like to use a repository if there is one (such as `git diff`) are *not* marked with RUN_SETUP_GENTLY, due to these unfortunate implementation details. > Now some commands respect config that they shouldn't (e.g., running "git > init foo.git" from inside another repository will accidentally pick up > the value of core.sharedrepository from wherever you happen to run it). Right. That points to another problem with the way we do things: we leak global state from discovering a git_dir, which means that we can neither undo nor override it. If we discovered our git_dir in a robust manner, `git init` could say: hey, this git_dir is actually not what I wanted, here is what I want. Likewise, `git submodule` would eventually be able to run in the very same process as the calling `git`, as would a local fetch. > So I think the caller of the config code has to provide some kind of > context about how it is expecting to run and how the value will be used. Yep. Maybe even go a step further and say that the config code needs a context "object". > Right now if setup_git_directory() or similar hasn't been called, the > config code does not look. Correct. Actually, half correct. If setup_git_directory() has not been called, but, say, git_path() (and thereby implicitly setup_git_env()), the config code *does* look. At a hard-coded .git/config. > Ideally there would be a way for a caller to say "I am running early and > not even sure yet if we are in a repo; please speculatively try to find > repo config for me". And ideally, it would not roll *yet another* way to discover the git_dir, but it would reuse the same function (fixing it not to chdir() unilaterally). Of course, not using `chdir()` means that we have to figure out symbolic links somehow, in case somebody works from a symlinked subdirectory, e.g.: ln -s $PWD/t/ ~/test-directory cd ~/test-directory git log > The pager code does this manually, and without great accuracy; see the > hack in pager.c's read_early_config(). I saw it. And that is what triggered the mail you are responding to (you probably saw my eye-rolling between the lines). The real question is: can we fix this? Or is there simply too great reluctance to change the current code? > I think the way forward is: > > 1. Make that an optional behavior in git_config_with_options() so > other spots can reuse it (probably alias lookup, and something like > your difftool config). Ideally: *any* early call to `git_config_get_value()`. Make things less surprising. > 2. Make it more accurate. Right now it blindly looks in .git/config, > but it should be able to do the usual repo-detection (_without_ > actually entering the repo) to try to find a possible config file. The real trick will be to convince Junio to have a single function for git_dir discovery, I guess, lest we have multiple, slightly incompatible ways to discover it. I expect a lot of resistance here, because we would have to change tried-and-tested (if POLA-violating) code. Ciao, Dscho ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-12-06 14:48 ` Johannes Schindelin @ 2016-12-06 15:09 ` Jeff King 2016-12-06 18:22 ` Stefan Beller 0 siblings, 1 reply; 86+ messages in thread From: Jeff King @ 2016-12-06 15:09 UTC (permalink / raw) To: Johannes Schindelin Cc: Junio C Hamano, git, David Aguilar, Dennis Kaarsemaker On Tue, Dec 06, 2016 at 03:48:38PM +0100, Johannes Schindelin wrote: > > Should it blindly look at ".git/config"? > > Absolutely not, of course. You did not need me to say that. > > > Now your program behaves differently depending on whether you are in the > > top-level of the working tree. > > Exactly. This, BTW, is already how the code would behave if anybody called > `git_path()` before `setup_git_directory()`, as the former function > implicitly calls `setup_git_env()` which does *not* call > `setup_git_directory()` but *does* set up `git_dir` which is then used by > `do_git_config_sequence()`.. > > We have a few of these nasty surprises in our code base, where code > silently assumes that global state is set up correctly, and succeeds in > sometimes surprising ways if it is not. Right. I have been working on fixing this. v2.11 has a ton of tweaks in this area, and my patch to die() rather than default to ".git" is cooking in next to catch any stragglers. > > Should it speculatively do repo discovery, and use any discovered config > > file? > > Personally, I find the way we discover the repository most irritating. It > seems that we have multiple, mutually incompatible code paths > (`setup_git_directory()` and `setup_git_env()` come to mind already, and > it does not help that consecutive calls to `setup_git_directory()` will > yield a very unexpected outcome). I agree. We should be killing off setup_git_env(), which is something I've been slowly working towards over the years. There are some annoyances with setup_git_directory(), too (like the fact that you cannot ask "is there a git repository you can find" without making un-recoverable changes to the process state). I think we should fix those, too. > > Now some commands respect config that they shouldn't (e.g., running "git > > init foo.git" from inside another repository will accidentally pick up > > the value of core.sharedrepository from wherever you happen to run it). > > Right. That points to another problem with the way we do things: we leak > global state from discovering a git_dir, which means that we can neither > undo nor override it. > > If we discovered our git_dir in a robust manner, `git init` could say: > hey, this git_dir is actually not what I wanted, here is what I want. > > Likewise, `git submodule` would eventually be able to run in the very same > process as the calling `git`, as would a local fetch. Yep, I agree with all that. I do think things _have_ been improving over the years, and the code is way less tangled than it used to be. But there are so many corner cases, and the code is so fundamental, that it has been slow going. I'd be happy to review patches if you want to push it along. > > So I think the caller of the config code has to provide some kind of > > context about how it is expecting to run and how the value will be used. > > Yep. > > Maybe even go a step further and say that the config code needs a context > "object". If I were writing git from scratch, I'd consider making a "struct repository" object. I'm not sure how painful it would be to retro-fit it at this point. > > Right now if setup_git_directory() or similar hasn't been called, the > > config code does not look. > > Correct. > > Actually, half correct. If setup_git_directory() has not been called, but, > say, git_path() (and thereby implicitly setup_git_env()), the config code > *does* look. At a hard-coded .git/config. Not since b9605bc4f (config: only read .git/config from configured repos, 2016-09-12). That's why pager.c needs its little hack. I guess you could see that as a step backwards, but I think it is necessary one on the road to doing it right. > > Ideally there would be a way for a caller to say "I am running early and > > not even sure yet if we are in a repo; please speculatively try to find > > repo config for me". > > And ideally, it would not roll *yet another* way to discover the git_dir, > but it would reuse the same function (fixing it not to chdir() > unilaterally). Yes, absolutely. > Of course, not using `chdir()` means that we have to figure out symbolic > links somehow, in case somebody works from a symlinked subdirectory, e.g.: > > ln -s $PWD/t/ ~/test-directory > cd ~/test-directory > git log There's work happening elsewhere[1] on making real_path() work without calling chdir(). Which necessarily involves resolving symlinks ourselves. I wonder if we could leverage that work here (ideally by using real_path() under the hood, and not reimplementing the same readlink() logic ourselves). [1] http://public-inbox.org/git/1480964316-99305-1-git-send-email-bmwill@google.com/ > > The pager code does this manually, and without great accuracy; see the > > hack in pager.c's read_early_config(). > > I saw it. And that is what triggered the mail you are responding to (you > probably saw my eye-rolling between the lines). > > The real question is: can we fix this? Or is there simply too great > reluctance to change the current code? The code in pager.c is only a month or two old. Like I said, it's ugly, but I think it's a necessary step on the way forward. So I don't think there's reluctance at all. The next steps (which I outlined) just haven't been taken yet. > > I think the way forward is: > > > > 1. Make that an optional behavior in git_config_with_options() so > > other spots can reuse it (probably alias lookup, and something like > > your difftool config). > > Ideally: *any* early call to `git_config_get_value()`. Make things less > surprising. I'm not convinced that's a good idea. The changes in b9605bc4f were motivated by a real bug, which your suggestion would reintroduce (namely low-level code run by git-init ending up with config variables from a repo that _should_ be unrelated). In my mental model, the cases are: 1. We are "early" in the process, before we know if we have a repo or not. These early looks should speculatively look at repo config, which is confined to generic things like pager config, alias config, etc. 2. We are in a repo. Obviously look at $GIT_DIR/config. 3. We are in a program which has done setup and determined we are _not_ in a repo. Definitely do not look at .git/config or anything else. My plan was for the config code to default to (3) when we are not in a repo, but let some lookups ask specifically for (1). If you want to default to (1), you need some way for programs to say "I am really case (3); do not look for a repo". And it needs to be global, as the config lookup may be done by much lower-level code. That could be by turning startup_info->have_repository into a tri-state. It just wasn't the way I was planning on it. > > 2. Make it more accurate. Right now it blindly looks in .git/config, > > but it should be able to do the usual repo-detection (_without_ > > actually entering the repo) to try to find a possible config file. > > The real trick will be to convince Junio to have a single function for > git_dir discovery, I guess, lest we have multiple, slightly incompatible > ways to discover it. I expect a lot of resistance here, because we would > have to change tried-and-tested (if POLA-violating) code. Personally, I haven't seen any resistance from Junio on refactoring this area. I'm sure he is concerned that we do not regress, but it's not like the area has been unchanged over the years. It has been slow going because we want to do it carefully, but I think we are actually at the point now where the next step is making setup_git_directory() more sane. -Peff ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-12-06 15:09 ` Jeff King @ 2016-12-06 18:22 ` Stefan Beller 2016-12-06 18:35 ` Jeff King 0 siblings, 1 reply; 86+ messages in thread From: Stefan Beller @ 2016-12-06 18:22 UTC (permalink / raw) To: Jeff King Cc: Johannes Schindelin, Junio C Hamano, git, David Aguilar, Dennis Kaarsemaker On Tue, Dec 6, 2016 at 7:09 AM, Jeff King <peff@peff.net> wrote: >> >> Maybe even go a step further and say that the config code needs a context >> "object". > > If I were writing git from scratch, I'd consider making a "struct > repository" object. I'm not sure how painful it would be to retro-fit it > at this point. Would it be possible to introduce "the repo" struct similar to "the index" in cache.h? From a submodule perspective I would very much welcome this object oriented approach to repositories. ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-12-06 18:22 ` Stefan Beller @ 2016-12-06 18:35 ` Jeff King 2017-01-18 22:38 ` Brandon Williams 0 siblings, 1 reply; 86+ messages in thread From: Jeff King @ 2016-12-06 18:35 UTC (permalink / raw) To: Stefan Beller Cc: Johannes Schindelin, Junio C Hamano, git, David Aguilar, Dennis Kaarsemaker On Tue, Dec 06, 2016 at 10:22:21AM -0800, Stefan Beller wrote: > >> Maybe even go a step further and say that the config code needs a context > >> "object". > > > > If I were writing git from scratch, I'd consider making a "struct > > repository" object. I'm not sure how painful it would be to retro-fit it > > at this point. > > Would it be possible to introduce "the repo" struct similar to "the index" > in cache.h? > > From a submodule perspective I would very much welcome this > object oriented approach to repositories. I think it may be more complicated, because there's some implicit global state in "the repo", like where files are relative to our cwd. All of those low-level functions would have to start caring about which repo we're talking about so they can prefix the appropriate working tree path, etc. For some operations that would be fine, but there are things that would subtly fail for submodules. I'm thinking we'd end up with some code state like: /* finding a repo does not modify global state; good */ struct repository *repo = repo_discover("."); /* obvious repo-level operations like looking up refs can be done with * a repository object; good */ repo_for_each_ref(repo, callback, NULL); /* * "enter" the repo so that we are at the top-level of the working * tree, etc. After this you can actually look at the index without * things breaking. */ repo_enter(repo); That would be enough to implement a lot of submodule-level stuff, but it would break pretty subtly as soon as you asked the submodule about its working tree. The solution is to make everything that accesses the working tree aware of the idea of a working tree root besides the cwd. But that's a pretty invasive change. -Peff ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-12-06 18:35 ` Jeff King @ 2017-01-18 22:38 ` Brandon Williams 0 siblings, 0 replies; 86+ messages in thread From: Brandon Williams @ 2017-01-18 22:38 UTC (permalink / raw) To: Jeff King Cc: Stefan Beller, Johannes Schindelin, Junio C Hamano, git, David Aguilar, Dennis Kaarsemaker On 12/06, Jeff King wrote: > On Tue, Dec 06, 2016 at 10:22:21AM -0800, Stefan Beller wrote: > > > >> Maybe even go a step further and say that the config code needs a context > > >> "object". > > > > > > If I were writing git from scratch, I'd consider making a "struct > > > repository" object. I'm not sure how painful it would be to retro-fit it > > > at this point. > > > > Would it be possible to introduce "the repo" struct similar to "the index" > > in cache.h? > > > > From a submodule perspective I would very much welcome this > > object oriented approach to repositories. > > I think it may be more complicated, because there's some implicit global > state in "the repo", like where files are relative to our cwd. All of > those low-level functions would have to start caring about which repo > we're talking about so they can prefix the appropriate working tree > path, etc. > > For some operations that would be fine, but there are things that would > subtly fail for submodules. I'm thinking we'd end up with some code > state like: > > /* finding a repo does not modify global state; good */ > struct repository *repo = repo_discover("."); > > /* obvious repo-level operations like looking up refs can be done with > * a repository object; good */ > repo_for_each_ref(repo, callback, NULL); > > /* > * "enter" the repo so that we are at the top-level of the working > * tree, etc. After this you can actually look at the index without > * things breaking. > */ > repo_enter(repo); > > That would be enough to implement a lot of submodule-level stuff, but it > would break pretty subtly as soon as you asked the submodule about its > working tree. The solution is to make everything that accesses the > working tree aware of the idea of a working tree root besides the cwd. > But that's a pretty invasive change. > > -Peff Some other challenges would be how to address people setting environment variables like GIT_DIR that indicate the location of a repositories git directory, which wouldn't work if you have multiple repos open. I do agree that having a repo object of some sort would aid in simplifying submodule operations but may require too many invasive changes to basic low-level functions. -- Brandon Williams ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-28 17:34 ` Johannes Schindelin 2016-11-28 19:27 ` Junio C Hamano @ 2016-11-30 16:02 ` Jakub Narębski 2016-11-30 18:39 ` Junio C Hamano 1 sibling, 1 reply; 86+ messages in thread From: Jakub Narębski @ 2016-11-30 16:02 UTC (permalink / raw) To: Johannes Schindelin, Junio C Hamano Cc: Jeff King, git, David Aguilar, Dennis Kaarsemaker Hello, W dniu 28.11.2016 o 18:34, Johannes Schindelin pisze: > My original "create a file in libexec/git-core/" was simple, did the job > reliably, and worked also for testing. > > It is a pity that you two gentlemen shot it down for being inelegant. And > ever since, we try to find a solution that is as simple, works as > reliably, also for testing, *and* appeases your tastes. I just would like to note that existence of file is used for both git-daemon and gitweb (the latter following the git-daemon example). So there is a precedent for the use of this mechanism. Best, -- Jakub Narębski ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin 2016-11-30 16:02 ` Jakub Narębski @ 2016-11-30 18:39 ` Junio C Hamano 0 siblings, 0 replies; 86+ messages in thread From: Junio C Hamano @ 2016-11-30 18:39 UTC (permalink / raw) To: Jakub Narębski Cc: Johannes Schindelin, Jeff King, git, David Aguilar, Dennis Kaarsemaker Jakub Narębski <jnareb@gmail.com> writes: >> My original "create a file in libexec/git-core/" was simple, did the job >> reliably, and worked also for testing. >> >> It is a pity that you two gentlemen shot it down for being inelegant. And >> ever since, we try to find a solution that is as simple, works as >> reliably, also for testing, *and* appeases your tastes. > > I just would like to note that existence of file is used for both > git-daemon and gitweb (the latter following the git-daemon example). > > So there is a precedent for the use of this mechanism. I think you are thinking about git-daemon-export-ok (for 'git daemon') and $GITWEB_EXPORT_OK file (for 'gitweb'). You do realize that it is apples-and-oranges [*1*] to take these as analogous to what Dscho is trying to do, don't you? First of all, these are to control access to each repository on the server side; the presence of the file is checked in each repository. What Dscho wants is to control the behaviour of an installation of Git as a whole, no matter which repository is being accessed [*2*, *3*]. More importantly, did you notice that git-daemon-export-ok predates the configuration mechanism by a large margin? The "does the file exist?" check done in a87e8be2ae ("Add a "git-daemon" that listens on a TCP port", 2005-07-13) is a relic from the past [*4*], and 32f4aaccaa ("gitweb: export options", 2006-09-17) added GITWEB_EXPORT_OK to mimic it, also long time ago [*5*]. They are not something you would want to mimic in new programs these days. Besides, $GIT_EXEC_PATH is where you place git subcommands. Who in the right mind considers it even remotely sane to design a system where you have to throw in a file that is not a command to /usr/bin to control the behaviour of your system? [*6*] So the "precedent" is irrelevant in the first place, and even if it were relevant, it is a bad piece of advice to mimic it. [Footnote] *1* Or is it apples-and-pineapples these days? *2* Not that I agree with that desire, if I understand him correctly from his description against the approach based on an environment variable. If a user has multiple installations and not even aware of which one of them s/he is currently using, a mechanism that affects only one of them (instead of consistently affecting all of them) would lead to more confusion, I would think. *3* If such hermetically configured independent installations are desirable, etc/gitconfig aka "git config --system" is a more appropriate thing to use, and you do not need to do repository discovery before you can read it. *4* If we had config mechanism, we would have used it just like we use daemon.* variables to control what services are enabled for each repository. *5* By that time, the config mechanism did already exist, so the GITWEB_EXPORT_OK could have been a per-repository configuration, but "gitweb" had another excuse to deviate from the norm. "Is this repository visible?" was done during repository listing and the script did not want to run "git config" in each and every repository-like directory it encountered in File::Find::find(). *6* And I do not think $GIT_EXEC_PATH vs /usr/bin is apples-and-oranges analogy. ^ permalink raw reply [flat|nested] 86+ messages in thread
* [PATCH v3 2/2] difftool: implement the functionality in the builtin 2016-11-24 20:55 ` [PATCH v3 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin 2016-11-24 20:55 ` [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin Johannes Schindelin @ 2016-11-24 20:55 ` Johannes Schindelin 2016-11-25 21:24 ` Jakub Narębski 2017-01-02 16:16 ` [PATCH v4 0/4] Show Git Mailing List: a builtin difftool Johannes Schindelin 2 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2016-11-24 20:55 UTC (permalink / raw) To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker This patch gives life to the skeleton added in the previous patch. The motivation for converting the difftool is that Perl scripts are not at all native on Windows, and that `git difftool` therefore is pretty slow on that platform, when there is no good reason for it to be slow. In addition, Perl does not really have access to Git's internals. That means that any script will always have to jump through unnecessary hoops. The current version of the builtin difftool does not, however, make full use of the internals but instead chooses to spawn a couple of Git processes, still, to make for an easier conversion. There remains a lot of room for improvement, left for a later date. Note: to play it safe, the original difftool is still called unless the config setting difftool.useBuiltin is set to true. The reason: this new, experimental, builtin difftool will be shipped as part of Git for Windows v2.11.0, to allow for easier large-scale testing, but of course as an opt-in feature. Sadly, the speedup is more noticable on Linux than on Windows: a quick test shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s) (real/user/sys) in a Linux VM, down from (6.529s/3.112s/0.644s), while on Windows, it is (36.064s/2.730s/7.194s), down from (47.637s/2.407s/6.863s). The culprit is most likely the overhead incurred from *still* having to shell out to mergetool-lib.sh and difftool--helper.sh. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/difftool.c | 670 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 669 insertions(+), 1 deletion(-) diff --git a/builtin/difftool.c b/builtin/difftool.c index 53870bb..3480920 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -11,9 +11,608 @@ * * Copyright (C) 2016 Johannes Schindelin */ +#include "cache.h" #include "builtin.h" #include "run-command.h" #include "exec_cmd.h" +#include "parse-options.h" +#include "argv-array.h" +#include "strbuf.h" +#include "lockfile.h" +#include "dir.h" + +static char *diff_gui_tool; +static int trust_exit_code; + +static const char *const builtin_difftool_usage[] = { + N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"), + NULL +}; + +static int difftool_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "diff.guitool")) { + diff_gui_tool = xstrdup(value); + return 0; + } + + if (!strcmp(var, "difftool.trustexitcode")) { + trust_exit_code = git_config_bool(var, value); + return 0; + } + + return git_default_config(var, value, cb); +} + +static int print_tool_help(void) +{ + const char *argv[] = { "mergetool", "--tool-help=diff", NULL }; + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int parse_index_info(char *p, int *mode1, int *mode2, + struct object_id *oid1, struct object_id *oid2, + char *status) +{ + if (*p != ':') + return error("expected ':', got '%c'", *p); + *mode1 = (int)strtol(p + 1, &p, 8); + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + *mode2 = (int)strtol(p + 1, &p, 8); + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + if (get_oid_hex(++p, oid1)) + return error("expected object ID, got '%s'", p + 1); + p += GIT_SHA1_HEXSZ; + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + if (get_oid_hex(++p, oid2)) + return error("expected object ID, got '%s'", p + 1); + p += GIT_SHA1_HEXSZ; + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + *status = *++p; + if (!status || p[1]) + return error("unexpected trailer: '%s'", p); + return 0; +} + +/* + * Remove any trailing slash from $workdir + * before starting to avoid double slashes in symlink targets. + */ +static void add_path(struct strbuf *buf, size_t base_len, const char *path) +{ + strbuf_setlen(buf, base_len); + if (buf->len && buf->buf[buf->len - 1] != '/') + strbuf_addch(buf, '/'); + strbuf_addstr(buf, path); +} + +/* + * Determine whether we can simply reuse the file in the worktree. + */ +static int use_wt_file(const char *workdir, const char *name, + struct object_id *oid) +{ + struct strbuf buf = STRBUF_INIT; + struct stat st; + int use = 0; + + strbuf_addstr(&buf, workdir); + add_path(&buf, buf.len, name); + + if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) { + struct object_id wt_oid; + int fd = open(buf.buf, O_RDONLY); + + if (!index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) { + if (is_null_oid(oid)) { + oidcpy(oid, &wt_oid); + use = 1; + } else if (!oidcmp(oid, &wt_oid)) + use = 1; + } + } + + strbuf_release(&buf); + + return use; +} + +struct working_tree_entry { + struct hashmap_entry entry; + char path[FLEX_ARRAY]; +}; + +static int working_tree_entry_cmp(struct working_tree_entry *a, + struct working_tree_entry *b, void *keydata) +{ + return strcmp(a->path, b->path); +} + +/* + * The `left` and `right` entries hold paths for the symlinks hashmap, + * and a SHA-1 surrounded by brief text for submodules. + */ +struct pair_entry { + struct hashmap_entry entry; + char left[PATH_MAX], right[PATH_MAX]; + const char path[FLEX_ARRAY]; +}; + +static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata) +{ + return strcmp(a->path, b->path); +} + +static void add_left_or_right(struct hashmap *map, const char *path, + const char *content, int is_right) +{ + struct pair_entry *e, *existing; + + FLEX_ALLOC_STR(e, path, path); + hashmap_entry_init(e, strhash(path)); + existing = hashmap_get(map, e, NULL); + if (existing) { + free(e); + e = existing; + } else { + e->left[0] = e->right[0] = '\0'; + hashmap_add(map, e); + } + strcpy(is_right ? e->right : e->left, content); +} + +struct path_entry { + struct hashmap_entry entry; + char path[FLEX_ARRAY]; +}; + +int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key) +{ + return strcmp(a->path, key ? key : b->path); +} + +static void changed_files(struct hashmap *result, const char *index_path, + const char *workdir) +{ + struct child_process update_index = CHILD_PROCESS_INIT; + struct child_process diff_files = CHILD_PROCESS_INIT; + struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT; + const char *git_dir = absolute_path(get_git_dir()), *env[] = { + NULL, NULL + }; + FILE *fp; + + strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path); + env[0] = index_env.buf; + + argv_array_pushl(&update_index.args, + "--git-dir", git_dir, "--work-tree", workdir, + "update-index", "--really-refresh", "-q", + "--unmerged", NULL); + update_index.no_stdin = 1; + update_index.no_stdout = 1; + update_index.no_stderr = 1; + update_index.git_cmd = 1; + update_index.use_shell = 0; + update_index.clean_on_exit = 1; + update_index.dir = workdir; + update_index.env = env; + /* Ignore any errors of update-index */ + run_command(&update_index); + + argv_array_pushl(&diff_files.args, + "--git-dir", git_dir, "--work-tree", workdir, + "diff-files", "--name-only", "-z", NULL); + diff_files.no_stdin = 1; + diff_files.git_cmd = 1; + diff_files.use_shell = 0; + diff_files.clean_on_exit = 1; + diff_files.out = -1; + diff_files.dir = workdir; + diff_files.env = env; + if (start_command(&diff_files)) + die("could not obtain raw diff"); + fp = xfdopen(diff_files.out, "r"); + while (!strbuf_getline_nul(&buf, fp)) { + struct path_entry *entry; + FLEX_ALLOC_STR(entry, path, buf.buf); + hashmap_entry_init(entry, strhash(buf.buf)); + hashmap_add(result, entry); + } + if (finish_command(&diff_files)) + die("diff-files did not exit properly"); + strbuf_release(&index_env); + strbuf_release(&buf); +} + +static NORETURN void exit_cleanup(const char *tmpdir, int exit_code) +{ + struct strbuf buf = STRBUF_INIT; + strbuf_addstr(&buf, tmpdir); + remove_dir_recursively(&buf, 0); + if (exit_code) + warning(_("failed: %d"), exit_code); + exit(exit_code); +} + +static int ensure_leading_directories(char *path) +{ + switch (safe_create_leading_directories(path)) { + case SCLD_OK: + case SCLD_EXISTS: + return 0; + default: + return error(_("could not create leading directories " + "of '%s'"), path); + } +} + +static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, + int argc, const char **argv) +{ + char tmpdir[PATH_MAX]; + struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT; + struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT; + struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT; + struct strbuf wtdir = STRBUF_INIT; + size_t ldir_len, rdir_len, wtdir_len; + struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1); + const char *workdir, *tmp; + int ret = 0, i; + FILE *fp; + struct hashmap working_tree_dups, submodules, symlinks2; + struct hashmap_iter iter; + struct pair_entry *entry; + enum object_type type; + unsigned long size; + struct index_state wtindex; + struct checkout lstate, rstate; + int rc, flags = RUN_GIT_CMD, err = 0; + struct child_process child = CHILD_PROCESS_INIT; + const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL }; + struct hashmap wt_modified, tmp_modified; + int indices_loaded = 0; + + workdir = get_git_work_tree(); + + /* Setup temp directories */ + tmp = getenv("TMPDIR"); + xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp"); + if (!mkdtemp(tmpdir)) + return error("could not create '%s'", tmpdir); + strbuf_addf(&ldir, "%s/left/", tmpdir); + strbuf_addf(&rdir, "%s/right/", tmpdir); + strbuf_addstr(&wtdir, workdir); + if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1])) + strbuf_addch(&wtdir, '/'); + mkdir(ldir.buf, 0700); + mkdir(rdir.buf, 0700); + + memset(&wtindex, 0, sizeof(wtindex)); + + memset(&lstate, 0, sizeof(lstate)); + lstate.base_dir = ldir.buf; + lstate.base_dir_len = ldir.len; + lstate.force = 1; + memset(&rstate, 0, sizeof(rstate)); + rstate.base_dir = rdir.buf; + rstate.base_dir_len = rdir.len; + rstate.force = 1; + + ldir_len = ldir.len; + rdir_len = rdir.len; + wtdir_len = wtdir.len; + + hashmap_init(&working_tree_dups, + (hashmap_cmp_fn)working_tree_entry_cmp, 0); + hashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0); + hashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0); + + child.no_stdin = 1; + child.git_cmd = 1; + child.use_shell = 0; + child.clean_on_exit = 1; + child.dir = prefix; + child.out = -1; + argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z", + NULL); + for (i = 0; i < argc; i++) + argv_array_push(&child.args, argv[i]); + if (start_command(&child)) + die("could not obtain raw diff"); + fp = xfdopen(child.out, "r"); + + /* Build index info for left and right sides of the diff */ + i = 0; + while (!strbuf_getline_nul(&info, fp)) { + int lmode, rmode; + struct object_id loid, roid; + char status; + const char *src_path, *dst_path; + size_t src_path_len, dst_path_len; + + if (starts_with(info.buf, "::")) + die(N_("combined diff formats('-c' and '--cc') are " + "not supported in\n" + "directory diff mode('-d' and '--dir-diff').")); + + if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid, + &status)) + break; + if (strbuf_getline_nul(&lpath, fp)) + break; + src_path = lpath.buf; + src_path_len = lpath.len; + + i++; + if (status != 'C' && status != 'R') { + dst_path = src_path; + dst_path_len = src_path_len; + } else { + if (strbuf_getline_nul(&rpath, fp)) + break; + dst_path = rpath.buf; + dst_path_len = rpath.len; + } + + if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) { + strbuf_reset(&buf); + strbuf_addf(&buf, "Subproject commit %s", + oid_to_hex(&loid)); + add_left_or_right(&submodules, src_path, buf.buf, 0); + strbuf_reset(&buf); + strbuf_addf(&buf, "Subproject commit %s", + oid_to_hex(&roid)); + if (!oidcmp(&loid, &roid)) + strbuf_addstr(&buf, "-dirty"); + add_left_or_right(&submodules, dst_path, buf.buf, 1); + continue; + } + + if (S_ISLNK(lmode)) { + char *content = read_sha1_file(loid.hash, &type, &size); + add_left_or_right(&symlinks2, src_path, content, 0); + free(content); + } + + if (S_ISLNK(rmode)) { + char *content = read_sha1_file(roid.hash, &type, &size); + add_left_or_right(&symlinks2, dst_path, content, 1); + free(content); + } + + if (lmode && status != 'C') { + ce->ce_mode = lmode; + oidcpy(&ce->oid, &loid); + strcpy(ce->name, src_path); + ce->ce_namelen = src_path_len; + if (checkout_entry(ce, &lstate, NULL)) + return error("could not write '%s'", src_path); + } + + if (rmode) { + struct working_tree_entry *entry; + + /* Avoid duplicate working_tree entries */ + FLEX_ALLOC_STR(entry, path, dst_path); + hashmap_entry_init(entry, strhash(dst_path)); + if (hashmap_get(&working_tree_dups, entry, NULL)) { + free(entry); + continue; + } + hashmap_add(&working_tree_dups, entry); + + if (!use_wt_file(workdir, dst_path, &roid)) { + ce->ce_mode = rmode; + oidcpy(&ce->oid, &roid); + strcpy(ce->name, dst_path); + ce->ce_namelen = dst_path_len; + if (checkout_entry(ce, &rstate, NULL)) + return error("could not write '%s'", + dst_path); + } else if (!is_null_oid(&roid)) { + /* + * Changes in the working tree need special + * treatment since they are not part of the + * index. + */ + struct cache_entry *ce2 = + make_cache_entry(rmode, roid.hash, + dst_path, 0, 0); + ce_mode_from_stat(ce2, rmode); + + add_index_entry(&wtindex, ce2, + ADD_CACHE_JUST_APPEND); + + add_path(&wtdir, wtdir_len, dst_path); + add_path(&rdir, rdir_len, dst_path); + if (ensure_leading_directories(rdir.buf)) + return error("could not create " + "directory for '%s'", + dst_path); + if (symlinks) { + if (symlink(wtdir.buf, rdir.buf)) { + ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf); + goto finish; + } + } else { + struct stat st; + if (stat(wtdir.buf, &st)) + st.st_mode = 0644; + if (copy_file(rdir.buf, wtdir.buf, + st.st_mode)) { + ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf); + goto finish; + } + } + } + } + } + + if (finish_command(&child)) { + ret = error("error occurred running diff --raw"); + goto finish; + } + + if (!i) + return 0; + + /* + * Changes to submodules require special treatment.This loop writes a + * temporary file to both the left and right directories to show the + * change in the recorded SHA1 for the submodule. + */ + hashmap_iter_init(&submodules, &iter); + while ((entry = hashmap_iter_next(&iter))) { + if (*entry->left) { + add_path(&ldir, ldir_len, entry->path); + ensure_leading_directories(ldir.buf); + write_file(ldir.buf, "%s", entry->left); + } + if (*entry->right) { + add_path(&rdir, rdir_len, entry->path); + ensure_leading_directories(rdir.buf); + write_file(rdir.buf, "%s", entry->right); + } + } + + /* + * Symbolic links require special treatment.The standard "git diff" + * shows only the link itself, not the contents of the link target. + * This loop replicates that behavior. + */ + hashmap_iter_init(&symlinks2, &iter); + while ((entry = hashmap_iter_next(&iter))) { + if (*entry->left) { + add_path(&ldir, ldir_len, entry->path); + ensure_leading_directories(ldir.buf); + write_file(ldir.buf, "%s", entry->left); + } + if (*entry->right) { + add_path(&rdir, rdir_len, entry->path); + ensure_leading_directories(rdir.buf); + write_file(rdir.buf, "%s", entry->right); + } + } + + strbuf_release(&buf); + + strbuf_setlen(&ldir, ldir_len); + helper_argv[1] = ldir.buf; + strbuf_setlen(&rdir, rdir_len); + helper_argv[2] = rdir.buf; + + if (extcmd) { + helper_argv[0] = extcmd; + flags = 0; + } else + setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1); + rc = run_command_v_opt(helper_argv, flags); + + /* + * If the diff includes working copy files and those + * files were modified during the diff, then the changes + * should be copied back to the working tree. + * Do not copy back files when symlinks are used and the + * external tool did not replace the original link with a file. + * + * These hashes are loaded lazily since they aren't needed + * in the common case of --symlinks and the difftool updating + * files through the symlink. + */ + hashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp, + wtindex.cache_nr); + hashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp, + wtindex.cache_nr); + + for (i = 0; i < wtindex.cache_nr; i++) { + struct hashmap_entry dummy; + const char *name = wtindex.cache[i]->name; + struct stat st; + + add_path(&rdir, rdir_len, name); + if (lstat(rdir.buf, &st)) + continue; + + if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode)) + continue; + + if (!indices_loaded) { + static struct lock_file lock; + strbuf_reset(&buf); + strbuf_addf(&buf, "%s/wtindex", tmpdir); + if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 || + write_locked_index(&wtindex, &lock, COMMIT_LOCK)) { + ret = error("could not write %s", buf.buf); + rollback_lock_file(&lock); + goto finish; + } + changed_files(&wt_modified, buf.buf, workdir); + strbuf_setlen(&rdir, rdir_len); + changed_files(&tmp_modified, buf.buf, rdir.buf); + add_path(&rdir, rdir_len, name); + indices_loaded = 1; + } + + hashmap_entry_init(&dummy, strhash(name)); + if (hashmap_get(&tmp_modified, &dummy, name)) { + add_path(&wtdir, wtdir_len, name); + if (hashmap_get(&wt_modified, &dummy, name)) { + warning(_("both files modified: '%s' and '%s'."), + wtdir.buf, rdir.buf); + warning(_("working tree file has been left.")); + warning(""); + err = 1; + } else if (unlink(wtdir.buf) || + copy_file(wtdir.buf, rdir.buf, st.st_mode)) + warning_errno(_("could not copy '%s' to '%s'"), + rdir.buf, wtdir.buf); + } + } + + if (err) { + warning(_("temporary files exist in '%s'."), tmpdir); + warning(_("you may want to cleanup or recover these.")); + exit(1); + } else + exit_cleanup(tmpdir, rc); + +finish: + free(ce); + strbuf_release(&ldir); + strbuf_release(&rdir); + strbuf_release(&wtdir); + strbuf_release(&buf); + + return ret; +} + +static int run_file_diff(int prompt, const char *prefix, + int argc, const char **argv) +{ + struct argv_array args = ARGV_ARRAY_INIT; + const char *env[] = { + "GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL, + NULL + }; + int ret = 0, i; + + if (prompt > 0) + env[2] = "GIT_DIFFTOOL_PROMPT=true"; + else if (!prompt) + env[2] = "GIT_DIFFTOOL_NO_PROMPT=true"; + + + argv_array_push(&args, "diff"); + for (i = 0; i < argc; i++) + argv_array_push(&args, argv[i]); + ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env); + exit(ret); +} /* * NEEDSWORK: this function can go once the legacy-difftool Perl script is @@ -41,6 +640,35 @@ static int use_builtin_difftool(void) { int cmd_difftool(int argc, const char **argv, const char *prefix) { + int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0, + tool_help = 0; + static char *difftool_cmd = NULL, *extcmd = NULL; + struct option builtin_difftool_options[] = { + OPT_BOOL('g', "gui", &use_gui_tool, + N_("use `diff.guitool` instead of `diff.tool`")), + OPT_BOOL('d', "dir-diff", &dir_diff, + N_("perform a full-directory diff")), + { OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL, + N_("do not prompt before launching a diff tool"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0}, + { OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL, + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN, + NULL, 1 }, + OPT_BOOL(0, "symlinks", &symlinks, + N_("use symlinks in dir-diff mode")), + OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"), + N_("use the specified diff tool")), + OPT_BOOL(0, "tool-help", &tool_help, + N_("print a list of diff tools that may be used with " + "`--tool`")), + OPT_BOOL(0, "trust-exit-code", &trust_exit_code, + N_("make 'git-difftool' exit when an invoked diff " + "tool returns a non - zero exit code")), + OPT_STRING('x', "extcmd", &extcmd, N_("<command>"), + N_("specify a custom command for viewing diffs")), + OPT_END() + }; + /* * NEEDSWORK: Once the builtin difftool has been tested enough * and git-legacy-difftool.perl is retired to contrib/, this preamble @@ -58,6 +686,46 @@ int cmd_difftool(int argc, const char **argv, const char *prefix) prefix = setup_git_directory(); trace_repo_setup(prefix); setup_work_tree(); + /* NEEDSWORK: once we no longer spawn anything, remove this */ + setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1); + setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1); + + git_config(difftool_config, NULL); + symlinks = has_symlinks; + + argc = parse_options(argc, argv, prefix, builtin_difftool_options, + builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_DASHDASH); - die("TODO"); + if (tool_help) + return print_tool_help(); + + if (use_gui_tool && diff_gui_tool && *diff_gui_tool) + setenv("GIT_DIFF_TOOL", diff_gui_tool, 1); + else if (difftool_cmd) { + if (*difftool_cmd) + setenv("GIT_DIFF_TOOL", difftool_cmd, 1); + else + die(_("no <tool> given for --tool=<tool>")); + } + + if (extcmd) { + if (*extcmd) + setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1); + else + die(_("no <cmd> given for --extcmd=<cmd>")); + } + + setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE", + trust_exit_code ? "true" : "false", 1); + + /* + * In directory diff mode, 'git-difftool--helper' is called once + * to compare the a / b directories. In file diff mode, 'git diff' + * will invoke a separate instance of 'git-difftool--helper' for + * each file that changed. + */ + if (dir_diff) + return run_dir_diff(extcmd, symlinks, prefix, argc, argv); + return run_file_diff(prompt, prefix, argc, argv); } -- 2.10.1.583.g721a9e0 ^ permalink raw reply related [flat|nested] 86+ messages in thread
* Re: [PATCH v3 2/2] difftool: implement the functionality in the builtin 2016-11-24 20:55 ` [PATCH v3 2/2] difftool: implement the functionality in the builtin Johannes Schindelin @ 2016-11-25 21:24 ` Jakub Narębski 2016-11-27 11:10 ` Johannes Schindelin 0 siblings, 1 reply; 86+ messages in thread From: Jakub Narębski @ 2016-11-25 21:24 UTC (permalink / raw) To: Johannes Schindelin, git Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker W dniu 24.11.2016 o 21:55, Johannes Schindelin pisze: > The current version of the builtin difftool does not, however, make full > use of the internals but instead chooses to spawn a couple of Git > processes, still, to make for an easier conversion. There remains a lot > of room for improvement, left for a later date. [...] > Sadly, the speedup is more noticable on Linux than on Windows: a quick > test shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s) > (real/user/sys) in a Linux VM, down from (6.529s/3.112s/0.644s), while > on Windows, it is (36.064s/2.730s/7.194s), down from > (47.637s/2.407s/6.863s). The culprit is most likely the overhead > incurred from *still* having to shell out to mergetool-lib.sh and > difftool--helper.sh. Does this mean that our shell-based testsuite is not well suited to be benchmark suite for comparing performance on MS Windows? Or does it mean that "builtin-difftool" spawning Git processes is the problem? > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> > --- > builtin/difftool.c | 670 ++++++++++++++++++++++++++++++++++++++++++++++++++++- > 1 file changed, 669 insertions(+), 1 deletion(-) > > diff --git a/builtin/difftool.c b/builtin/difftool.c > index 53870bb..3480920 100644 > --- a/builtin/difftool.c > +++ b/builtin/difftool.c > @@ -11,9 +11,608 @@ > * > * Copyright (C) 2016 Johannes Schindelin > */ > +#include "cache.h" > #include "builtin.h" > #include "run-command.h" > #include "exec_cmd.h" > +#include "parse-options.h" > +#include "argv-array.h" > +#include "strbuf.h" > +#include "lockfile.h" > +#include "dir.h" > + > +static char *diff_gui_tool; > +static int trust_exit_code; > + > +static const char *const builtin_difftool_usage[] = { > + N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"), > + NULL > +}; > + > +static int difftool_config(const char *var, const char *value, void *cb) > +{ > + if (!strcmp(var, "diff.guitool")) { Shouldn't you also read other configuration variables, like "diff.tool", and it's mergetool fallbacks ("merge.guitool", "merge.tool")? > + diff_gui_tool = xstrdup(value); > + return 0; > + } > + > + if (!strcmp(var, "difftool.trustexitcode")) { > + trust_exit_code = git_config_bool(var, value); > + return 0; > + } Why you do not need to check "difftool.prompt"? And "mergetool.*" fallbacks? > + > + return git_default_config(var, value, cb); > +} > + > +static int print_tool_help(void) > +{ > + const char *argv[] = { "mergetool", "--tool-help=diff", NULL }; > + return run_command_v_opt(argv, RUN_GIT_CMD); This looks a bit strange to me, but I guess this is to avoid recursively invoking ourself, and { "legacy-difftool", "--tool-help", NULL }; isn't that much better. > +} > + > +static int parse_index_info(char *p, int *mode1, int *mode2, > + struct object_id *oid1, struct object_id *oid2, > + char *status) There are only two hard things in Computer Science: cache invalidation and naming things. -- Phil Karlton Why did you name function that parses "diff --raw" output (aka "diff-tree" output) parse_index_info() instead of parse_raw_diff() or parse_diff_tree()? I went searching for `update-index --index-info` formats... This is not that important, because this function is file-static; though future developers would thank you for more descriptive naming. ADDED: Disregard this, I see this function is about index (?) part of raw diff, that is only a part of "git diff --raw -z" output. Though... > +{ > + if (*p != ':') > + return error("expected ':', got '%c'", *p); > + *mode1 = (int)strtol(p + 1, &p, 8); > + if (*p != ' ') > + return error("expected ' ', got '%c'", *p); Nitpicking. I guess because this error shouldn't really happen, and because current implementation is transient, we don't need to worry about better error messages (was it problem with parsing, or was it unexpected character). For example '10064x', or '10064\n' would fail parse, but it is not space that we were expecting... > + *mode2 = (int)strtol(p + 1, &p, 8); > + if (*p != ' ') > + return error("expected ' ', got '%c'", *p); > + if (get_oid_hex(++p, oid1)) > + return error("expected object ID, got '%s'", p + 1); > + p += GIT_SHA1_HEXSZ; > + if (*p != ' ') > + return error("expected ' ', got '%c'", *p); > + if (get_oid_hex(++p, oid2)) > + return error("expected object ID, got '%s'", p + 1); > + p += GIT_SHA1_HEXSZ; > + if (*p != ' ') > + return error("expected ' ', got '%c'", *p); > + *status = *++p; > + if (!status || p[1]) > + return error("unexpected trailer: '%s'", p); > + return 0; > +} > + > +/* > + * Remove any trailing slash from $workdir > + * before starting to avoid double slashes in symlink targets. > + */ Err... that's not what add_path() does, in its current implementation. It doesn't remove trailing slashes, but it checks if there is trailing slash, and if there isn't, it adds it as separator before adding path. Or was it original comment from the Perl implementation? It look like this, with '$workdir'... If it is meant to be straight copy of comment from legacy-difftool, a note would be nice. > +static void add_path(struct strbuf *buf, size_t base_len, const char *path) Naming: I think strbuf_addpath() would be a better name, but I guess it is a matter of taste. > +{ > + strbuf_setlen(buf, base_len); > + if (buf->len && buf->buf[buf->len - 1] != '/') > + strbuf_addch(buf, '/'); > + strbuf_addstr(buf, path); > +} > + > +/* > + * Determine whether we can simply reuse the file in the worktree. > + */ > +static int use_wt_file(const char *workdir, const char *name, Should it be 'name' or 'pathname'? > + struct object_id *oid) > +{ > + struct strbuf buf = STRBUF_INIT; > + struct stat st; > + int use = 0; > + > + strbuf_addstr(&buf, workdir); > + add_path(&buf, buf.len, name); With proposed rename, it would IMVVVHO looks better + strbuf_addstr(&buf, workdir); + strbuf_addpath(&buf, buf.len, name); But that is a matter of taste (again, the function is file-local). > + > + if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) { > + struct object_id wt_oid; > + int fd = open(buf.buf, O_RDONLY); > + > + if (!index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) { > + if (is_null_oid(oid)) { > + oidcpy(oid, &wt_oid); > + use = 1; > + } else if (!oidcmp(oid, &wt_oid)) > + use = 1; > + } > + } > + > + strbuf_release(&buf); > + > + return use; > +} [...] > +static int ensure_leading_directories(char *path) > +{ > + switch (safe_create_leading_directories(path)) { > + case SCLD_OK: > + case SCLD_EXISTS: > + return 0; > + default: > + return error(_("could not create leading directories " > + "of '%s'"), path); > + } > +} Nice function, I wonder if it would be useful in other places. > + > +static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, > + int argc, const char **argv) > +{ > + char tmpdir[PATH_MAX]; > + struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT; > + struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT; Nitpicking. To be symmetric, it could be reordered like this: + struct strbuf info = STRBUF_INIT, buf = STRBUF_INIT; + struct strbuf lpath = STRBUF_INIT, rpath = STRBUF_INIT; See: lpath, rpath; ldir, rdir; ldir_len, rdir_len. > + struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT; > + struct strbuf wtdir = STRBUF_INIT; > + size_t ldir_len, rdir_len, wtdir_len; [...] > + argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z", > + NULL); > + for (i = 0; i < argc; i++) > + argv_array_push(&child.args, argv[i]); > + if (start_command(&child)) > + die("could not obtain raw diff"); > + fp = xfdopen(child.out, "r"); > + > + /* Build index info for left and right sides of the diff */ > + i = 0; > + while (!strbuf_getline_nul(&info, fp)) { > + int lmode, rmode; > + struct object_id loid, roid; > + char status; > + const char *src_path, *dst_path; > + size_t src_path_len, dst_path_len; > + > + if (starts_with(info.buf, "::")) > + die(N_("combined diff formats('-c' and '--cc') are " > + "not supported in\n" > + "directory diff mode('-d' and '--dir-diff').")); > + > + if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid, > + &status)) After rename it would read as: + if (parse_raw_diff(info.buf, &lmode, &rmode, &loid, &roid, + &status)) Though now I see that you parse here index information of raw diff (I think)... so disregard my musings. > + break; > + if (strbuf_getline_nul(&lpath, fp)) > + break; > + src_path = lpath.buf; > + src_path_len = lpath.len; > + > + i++; > + if (status != 'C' && status != 'R') { > + dst_path = src_path; > + dst_path_len = src_path_len; > + } else { > + if (strbuf_getline_nul(&rpath, fp)) > + break; > + dst_path = rpath.buf; > + dst_path_len = rpath.len; > + } [...] > + /* > + * Changes to submodules require special treatment.This loop writes a Here and in few other places you are missing space after full stop. + * Changes to submodules require special treatment. This loop writes a > + * temporary file to both the left and right directories to show the > + * change in the recorded SHA1 for the submodule. > + */ > + hashmap_iter_init(&submodules, &iter); > + while ((entry = hashmap_iter_next(&iter))) { > + if (*entry->left) { > + add_path(&ldir, ldir_len, entry->path); > + ensure_leading_directories(ldir.buf); > + write_file(ldir.buf, "%s", entry->left); > + } > + if (*entry->right) { > + add_path(&rdir, rdir_len, entry->path); > + ensure_leading_directories(rdir.buf); > + write_file(rdir.buf, "%s", entry->right); > + } > + } > + > + /* > + * Symbolic links require special treatment.The standard "git diff" Same here. > + * shows only the link itself, not the contents of the link target. > + * This loop replicates that behavior. > + */ Best, -- Jakub Narębski ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 2/2] difftool: implement the functionality in the builtin 2016-11-25 21:24 ` Jakub Narębski @ 2016-11-27 11:10 ` Johannes Schindelin 2016-11-27 11:20 ` Jakub Narębski 0 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2016-11-27 11:10 UTC (permalink / raw) To: Jakub Narębski Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker [-- Attachment #1: Type: text/plain, Size: 3725 bytes --] Hi Jakub, On Fri, 25 Nov 2016, Jakub Narębski wrote: > W dniu 24.11.2016 o 21:55, Johannes Schindelin pisze: > > > The current version of the builtin difftool does not, however, make > > full use of the internals but instead chooses to spawn a couple of Git > > processes, still, to make for an easier conversion. There remains a > > lot of room for improvement, left for a later date. > [...] > > > Sadly, the speedup is more noticable on Linux than on Windows: a quick > > test shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s) > > (real/user/sys) in a Linux VM, down from (6.529s/3.112s/0.644s), > > while on Windows, it is (36.064s/2.730s/7.194s), down from > > (47.637s/2.407s/6.863s). The culprit is most likely the overhead > > incurred from *still* having to shell out to mergetool-lib.sh and > > difftool--helper.sh. > > Does this mean that our shell-based testsuite is not well suited to be > benchmark suite for comparing performance on MS Windows? It is quite likely the case that shell-based testing will always be inappropriate for performance testing. Even on Linux. > Or does it mean that "builtin-difftool" spawning Git processes is the > problem? At the moment I would have to guess, and I'd rather not. > > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> > > --- > > builtin/difftool.c | 670 ++++++++++++++++++++++++++++++++++++++++++++++++++++- > > 1 file changed, 669 insertions(+), 1 deletion(-) > > > > diff --git a/builtin/difftool.c b/builtin/difftool.c > > index 53870bb..3480920 100644 > > --- a/builtin/difftool.c > > +++ b/builtin/difftool.c > > @@ -11,9 +11,608 @@ > > * > > * Copyright (C) 2016 Johannes Schindelin > > */ > > +#include "cache.h" > > #include "builtin.h" > > #include "run-command.h" > > #include "exec_cmd.h" > > +#include "parse-options.h" > > +#include "argv-array.h" > > +#include "strbuf.h" > > +#include "lockfile.h" > > +#include "dir.h" > > + > > +static char *diff_gui_tool; > > +static int trust_exit_code; > > + > > +static const char *const builtin_difftool_usage[] = { > > + N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"), > > + NULL > > +}; > > + > > +static int difftool_config(const char *var, const char *value, void *cb) > > +{ > > + if (!strcmp(var, "diff.guitool")) { > > Shouldn't you also read other configuration variables, like "diff.tool", > and it's mergetool fallbacks ("merge.guitool", "merge.tool")? No, as those configuration variables are not used by the builtin difftool directly but read by subsequently spawned commands separately. There would be no use reading them here, for now. > > +static int print_tool_help(void) > > +{ > > + const char *argv[] = { "mergetool", "--tool-help=diff", NULL }; > > + return run_command_v_opt(argv, RUN_GIT_CMD); > > This looks a bit strange to me, but I guess this is to avoid recursively > invoking ourself, and { "legacy-difftool", "--tool-help", NULL }; isn't > that much better. This is obviously a straight translation of the Perl script (see https://github.com/git/git/blob/v2.10.2/git-difftool.perl#L40-L46): sub print_tool_help { # See the comment at the bottom of file_diff() for the reason # behind # using system() followed by exit() instead of exec(). my $rc = system(qw(git mergetool --tool-help=diff)); exit($rc | ($rc >> 8)); } I read the rest of your review, but it appears that it is more about style than about substance, while I am only willing to address the latter issues at the moment. You see, I want to focus on getting difftool correct first before attempting to make it pretty. Ciao, Dscho ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v3 2/2] difftool: implement the functionality in the builtin 2016-11-27 11:10 ` Johannes Schindelin @ 2016-11-27 11:20 ` Jakub Narębski 0 siblings, 0 replies; 86+ messages in thread From: Jakub Narębski @ 2016-11-27 11:20 UTC (permalink / raw) To: Johannes Schindelin Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker Hello Johannes, On 27 November 2016 at 12:10, Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote: > On Fri, 25 Nov 2016, Jakub Narębski wrote: > > W dniu 24.11.2016 o 21:55, Johannes Schindelin pisze: [...] > > > +static int difftool_config(const char *var, const char *value, void *cb) > > > +{ > > > + if (!strcmp(var, "diff.guitool")) { > > > > Shouldn't you also read other configuration variables, like "diff.tool", > > and it's mergetool fallbacks ("merge.guitool", "merge.tool")? > > No, as those configuration variables are not used by the builtin difftool > directly but read by subsequently spawned commands separately. There would > be no use reading them here, for now. Ah, all right then. Though NEEDSWORK comment would be nice to have here (for when we don't spawn commands). [...] > I read the rest of your review, but it appears that it is more about > style than about substance, while I am only willing to address the latter > issues at the moment. You see, I want to focus on getting difftool correct > first before attempting to make it pretty. > > Ciao, > Dscho Well, excet for the submodule-relates stuff, which I have skipped, it looks good to me. -- Jakub Narębski ^ permalink raw reply [flat|nested] 86+ messages in thread
* [PATCH v4 0/4] Show Git Mailing List: a builtin difftool 2016-11-24 20:55 ` [PATCH v3 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin 2016-11-24 20:55 ` [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin Johannes Schindelin 2016-11-24 20:55 ` [PATCH v3 2/2] difftool: implement the functionality in the builtin Johannes Schindelin @ 2017-01-02 16:16 ` Johannes Schindelin 2017-01-02 16:22 ` [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path() Johannes Schindelin ` (4 more replies) 2 siblings, 5 replies; 86+ messages in thread From: Johannes Schindelin @ 2017-01-02 16:16 UTC (permalink / raw) To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra I have been working on the builtin difftool for a while now, for two reasons: 1. Perl is really not native on Windows. Not only is there a performance penalty to be paid just for running Perl scripts, we also have to deal with the fact that users may have different Perl installations, with different options, and some other Perl installation may decide to set PERL5LIB globally, wreaking havoc with Git for Windows' Perl (which we have to use because almost all other Perl distributions lack the Subversion bindings we need for `git svn`). 2. Perl makes for a rather large reason that Git for Windows' installer weighs in with >30MB. While one Perl script less does not relieve us of that burden, it is one step in the right direction. Changes since v3: - made path_entry_cmp static - fixed a few bugs identified by Coverity - fixed overzealous status parsing that did not expect any number after C or R Johannes Schindelin (4): Avoid Coverity warning about unfree()d git_exec_path() difftool: add a skeleton for the upcoming builtin difftool: implement the functionality in the builtin t7800: run both builtin and scripted difftool, for now .gitignore | 1 + Makefile | 3 +- builtin.h | 1 + builtin/difftool.c | 733 ++++++++++++++++++++++++++ exec_cmd.c | 5 +- git-difftool.perl => git-legacy-difftool.perl | 0 git.c | 6 + t/t7800-difftool.sh | 29 + 8 files changed, 776 insertions(+), 2 deletions(-) create mode 100644 builtin/difftool.c rename git-difftool.perl => git-legacy-difftool.perl (100%) base-commit: e05806da9ec4aff8adfed142ab2a2b3b02e33c8c Published-As: https://github.com/dscho/git/releases/tag/builtin-difftool-v4 Fetch-It-Via: git fetch https://github.com/dscho/git builtin-difftool-v4 Interdiff vs v3: diff --git a/builtin/difftool.c b/builtin/difftool.c index 3480920fea..2115e548a5 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -73,8 +73,10 @@ static int parse_index_info(char *p, int *mode1, int *mode2, if (*p != ' ') return error("expected ' ', got '%c'", *p); *status = *++p; - if (!status || p[1]) - return error("unexpected trailer: '%s'", p); + if (!*status) + return error("missing status"); + if (p[1] && !isdigit(p[1])) + return error("unexpected trailer: '%s'", p + 1); return 0; } @@ -107,7 +109,8 @@ static int use_wt_file(const char *workdir, const char *name, struct object_id wt_oid; int fd = open(buf.buf, O_RDONLY); - if (!index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) { + if (fd >= 0 && + !index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) { if (is_null_oid(oid)) { oidcpy(oid, &wt_oid); use = 1; @@ -162,7 +165,7 @@ static void add_left_or_right(struct hashmap *map, const char *path, e->left[0] = e->right[0] = '\0'; hashmap_add(map, e); } - strcpy(is_right ? e->right : e->left, content); + strlcpy(is_right ? e->right : e->left, content, PATH_MAX); } struct path_entry { @@ -170,7 +173,7 @@ struct path_entry { char path[FLEX_ARRAY]; }; -int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key) +static int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key) { return strcmp(a->path, key ? key : b->path); } @@ -423,17 +426,16 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, struct cache_entry *ce2 = make_cache_entry(rmode, roid.hash, dst_path, 0, 0); - ce_mode_from_stat(ce2, rmode); add_index_entry(&wtindex, ce2, ADD_CACHE_JUST_APPEND); - add_path(&wtdir, wtdir_len, dst_path); add_path(&rdir, rdir_len, dst_path); if (ensure_leading_directories(rdir.buf)) return error("could not create " "directory for '%s'", dst_path); + add_path(&wtdir, wtdir_len, dst_path); if (symlinks) { if (symlink(wtdir.buf, rdir.buf)) { ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf); diff --git a/exec_cmd.c b/exec_cmd.c index 19ac2146d0..587bd7eb48 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -65,6 +65,7 @@ void git_set_argv_exec_path(const char *exec_path) const char *git_exec_path(void) { const char *env; + static char *system_exec_path; if (argv_exec_path) return argv_exec_path; @@ -74,7 +75,9 @@ const char *git_exec_path(void) return env; } - return system_path(GIT_EXEC_PATH); + if (!system_exec_path) + system_exec_path = system_path(GIT_EXEC_PATH); + return system_exec_path; } static void add_path(struct strbuf *out, const char *path) diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index e94910c563..273ab55723 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -23,6 +23,20 @@ prompt_given () test "$prompt" = "Launch 'test-tool' [Y/n]? branch" } +for use_builtin_difftool in false true +do + +test_expect_success 'verify we are running the correct difftool' ' + if test true = '$use_builtin_difftool' + then + test_must_fail ok=129 git difftool -h >help && + grep "g, --gui" help + else + git difftool -h >help && + grep "g|--gui" help + fi +' + # NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired. # Create a file on master and change it on branch @@ -606,4 +620,17 @@ test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' ' ) ' +test true != $use_builtin_difftool || break + +test_expect_success 'tear down for re-run' ' + rm -rf * .[a-z]* && + git init +' + +# run as builtin difftool now +GIT_CONFIG_PARAMETERS="'difftool.usebuiltin=true'" +export GIT_CONFIG_PARAMETERS + +done + test_done -- 2.11.0.rc3.windows.1 ^ permalink raw reply [flat|nested] 86+ messages in thread
* [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path() 2017-01-02 16:16 ` [PATCH v4 0/4] Show Git Mailing List: a builtin difftool Johannes Schindelin @ 2017-01-02 16:22 ` Johannes Schindelin 2017-01-03 20:11 ` Stefan Beller 2017-01-02 16:22 ` [PATCH v4 2/4] difftool: add a skeleton for the upcoming builtin Johannes Schindelin ` (3 subsequent siblings) 4 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2017-01-02 16:22 UTC (permalink / raw) To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra Technically, it is correct that git_exec_path() returns a possibly malloc()ed string. Practically, it is *sometimes* not malloc()ed. So let's just use a static variable to make it a singleton. That'll shut Coverity up, hopefully. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- exec_cmd.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/exec_cmd.c b/exec_cmd.c index 19ac2146d0..587bd7eb48 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -65,6 +65,7 @@ void git_set_argv_exec_path(const char *exec_path) const char *git_exec_path(void) { const char *env; + static char *system_exec_path; if (argv_exec_path) return argv_exec_path; @@ -74,7 +75,9 @@ const char *git_exec_path(void) return env; } - return system_path(GIT_EXEC_PATH); + if (!system_exec_path) + system_exec_path = system_path(GIT_EXEC_PATH); + return system_exec_path; } static void add_path(struct strbuf *out, const char *path) -- 2.11.0.rc3.windows.1 ^ permalink raw reply related [flat|nested] 86+ messages in thread
* Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path() 2017-01-02 16:22 ` [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path() Johannes Schindelin @ 2017-01-03 20:11 ` Stefan Beller 2017-01-03 21:33 ` Johannes Schindelin ` (2 more replies) 0 siblings, 3 replies; 86+ messages in thread From: Stefan Beller @ 2017-01-03 20:11 UTC (permalink / raw) To: Johannes Schindelin Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra On Mon, Jan 2, 2017 at 8:22 AM, Johannes Schindelin <johannes.schindelin@gmx.de> wrote: > Technically, it is correct that git_exec_path() returns a possibly > malloc()ed string. Practically, it is *sometimes* not malloc()ed. So > let's just use a static variable to make it a singleton. That'll shut > Coverity up, hopefully. I picked up this patch and applied it to the coverity branch that I maintain at github/stefanbeller/git. I'd love to see this patch upstream as it reduces my maintenance burden of the coverity branch by a patch. Early on when Git was new to coverity, some arguments were made that patches like these only clutter the main code base which is read by a lot of people, hence we want these quirks for coverity not upstream. And I think that still holds. If this patch is only to appease coverity (as the commit message eludes to) I think this may be a bad idea for upstream. If this patch fixes an actual problem, then the commit message needs to spell that out. Thanks, Stefan ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path() 2017-01-03 20:11 ` Stefan Beller @ 2017-01-03 21:33 ` Johannes Schindelin 2017-01-04 18:09 ` Stefan Beller 2017-01-04 1:13 ` Jeff King 2017-01-09 1:25 ` Junio C Hamano 2 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2017-01-03 21:33 UTC (permalink / raw) To: Stefan Beller Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra Hi Stefan, On Tue, 3 Jan 2017, Stefan Beller wrote: > On Mon, Jan 2, 2017 at 8:22 AM, Johannes Schindelin > <johannes.schindelin@gmx.de> wrote: > > Technically, it is correct that git_exec_path() returns a possibly > > malloc()ed string. Practically, it is *sometimes* not malloc()ed. So > > let's just use a static variable to make it a singleton. That'll shut > > Coverity up, hopefully. > > I picked up this patch and applied it to the coverity branch > that I maintain at github/stefanbeller/git. > > I'd love to see this patch upstream as it reduces my maintenance > burden of the coverity branch by a patch. > > Early on when Git was new to coverity, some arguments were made > that patches like these only clutter the main code base which is read > by a lot of people, hence we want these quirks for coverity not upstream. > And I think that still holds. > > If this patch is only to appease coverity (as the commit message eludes > to) I think this may be a bad idea for upstream. If this patch fixes an > actual problem, then the commit message needs to spell that out. This patch was originally only to appease Coverity, but it actually *does* plug a very real memory leak: previously, *every* call to git_exec_path() *possibly* returned a newly-malloc()ed buffer. Now, the first call will store that pointer in a static variable and reuse it later. Could you maybe help me with improving the commit message? Ciao, Dscho ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path() 2017-01-03 21:33 ` Johannes Schindelin @ 2017-01-04 18:09 ` Stefan Beller 0 siblings, 0 replies; 86+ messages in thread From: Stefan Beller @ 2017-01-04 18:09 UTC (permalink / raw) To: Johannes Schindelin Cc: git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra On Tue, Jan 3, 2017 at 1:33 PM, Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote: > > This patch was originally only to appease Coverity, but it actually *does* > plug a very real memory leak: previously, *every* call to git_exec_path() > *possibly* returned a newly-malloc()ed buffer. Now, the first call will > store that pointer in a static variable and reuse it later. > > Could you maybe help me with improving the commit message? As someone not familiar with that area of code, this explained it enough for me to understand, so maybe: exec_cmd: do not leak via git_exec_path Every call to git_exec_path() possibly returned a newly-malloc()ed buffer. Now, the first call will allocate the buffer and subsequent calls return a pointer to it, which then prevents leaking memory on each call. The return value of a "const char *" hints to the caller, that the memory is not owned by the caller, do we need to be explicit there (i.e. a comment declaring the memory ownership? Probably not.) Thanks, Stefan ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path() 2017-01-03 20:11 ` Stefan Beller 2017-01-03 21:33 ` Johannes Schindelin @ 2017-01-04 1:13 ` Jeff King 2017-01-09 1:25 ` Junio C Hamano 2 siblings, 0 replies; 86+ messages in thread From: Jeff King @ 2017-01-04 1:13 UTC (permalink / raw) To: Stefan Beller Cc: Johannes Schindelin, git, Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra On Tue, Jan 03, 2017 at 12:11:25PM -0800, Stefan Beller wrote: > On Mon, Jan 2, 2017 at 8:22 AM, Johannes Schindelin > <johannes.schindelin@gmx.de> wrote: > > Technically, it is correct that git_exec_path() returns a possibly > > malloc()ed string. Practically, it is *sometimes* not malloc()ed. So > > let's just use a static variable to make it a singleton. That'll shut > > Coverity up, hopefully. > > I picked up this patch and applied it to the coverity branch > that I maintain at github/stefanbeller/git. > > I'd love to see this patch upstream as it reduces my maintenance > burden of the coverity branch by a patch. There is another lurking issue in that function, which is that the return value of getenv() is not guaranteed to last beyond more calls to getenv() or setenv(). It should probably xstrdup() that result, too. At that point 2 out of 3 of the return cases would be malloc'd strings, so we _could_ switch the third and say "caller must free the result". But I think I prefer something like Dscho's solution (more on that below). > Early on when Git was new to coverity, some arguments were made > that patches like these only clutter the main code base which is read > by a lot of people, hence we want these quirks for coverity not upstream. > And I think that still holds. > > If this patch is only to appease coverity (as the commit message eludes > to) I think this may be a bad idea for upstream. If this patch fixes an > actual problem, then the commit message needs to spell that out. This is a real leak, though in all cases the program typically exits soon afterwards. But we leak from list_commands(), for example, and it is not immediately obvious that this is only called right before exiting. But I think more important is that caching the result in a static variable communicates something (both to Coverity and to a reader of the code). This is a value we expect to live through the life of the program, and it is OK for it to "leak" when it goes out of scope by the program exiting. So even though the behavior does not really change, the annotation has value. -Peff ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path() 2017-01-03 20:11 ` Stefan Beller 2017-01-03 21:33 ` Johannes Schindelin 2017-01-04 1:13 ` Jeff King @ 2017-01-09 1:25 ` Junio C Hamano 2017-01-09 6:00 ` Jeff King ` (2 more replies) 2 siblings, 3 replies; 86+ messages in thread From: Junio C Hamano @ 2017-01-09 1:25 UTC (permalink / raw) To: Stefan Beller Cc: Johannes Schindelin, git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra Stefan Beller <sbeller@google.com> writes: > On Mon, Jan 2, 2017 at 8:22 AM, Johannes Schindelin > <johannes.schindelin@gmx.de> wrote: >> Technically, it is correct that git_exec_path() returns a possibly >> malloc()ed string. Practically, it is *sometimes* not malloc()ed. So >> let's just use a static variable to make it a singleton. That'll shut >> Coverity up, hopefully. > > I picked up this patch and applied it to the coverity branch > that I maintain at github/stefanbeller/git. > > I'd love to see this patch upstream as it reduces my maintenance > burden of the coverity branch by a patch. So with the above, are you saying "Dscho said 'hopefully', and I confirm that this change does squelch misdiagnosis by Coverity"? > If this patch is only to appease coverity (as the commit message eludes > to) I think this may be a bad idea for upstream. If this patch fixes an > actual problem, then the commit message needs to spell that out. That is true, and I see Peff pointed out another possible issue around getenv(), but I think from the "one step at a time" point of view, it is an improvement to call system_path() just once and cache it in "static char *". How about explaining it like this then? (only the log message has been corrected; diff is from the original). commit c9bb5d101ca657fa466afa8c4368c43ea7b7aca8 Author: Johannes Schindelin <johannes.schindelin@gmx.de> Date: Mon Jan 2 17:22:33 2017 +0100 git_exec_path: avoid Coverity warning about unfree()d result Technically, it is correct that git_exec_path() returns a possibly malloc()ed string returned from system_path(), and it is sometimes not allocated. Cache the result in a static variable and make sure that we call system_path() only once, which plugs a potential leak. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com> diff --git a/exec_cmd.c b/exec_cmd.c index 9d5703a157..eae56fefba 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -69,6 +69,7 @@ void git_set_argv_exec_path(const char *exec_path) const char *git_exec_path(void) { const char *env; + static char *system_exec_path; if (argv_exec_path) return argv_exec_path; @@ -78,7 +79,9 @@ const char *git_exec_path(void) return env; } - return system_path(GIT_EXEC_PATH); + if (!system_exec_path) + system_exec_path = system_path(GIT_EXEC_PATH); + return system_exec_path; } static void add_path(struct strbuf *out, const char *path) ^ permalink raw reply related [flat|nested] 86+ messages in thread
* Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path() 2017-01-09 1:25 ` Junio C Hamano @ 2017-01-09 6:00 ` Jeff King 2017-01-09 7:49 ` Johannes Schindelin 2017-01-09 19:21 ` Stefan Beller 2 siblings, 0 replies; 86+ messages in thread From: Jeff King @ 2017-01-09 6:00 UTC (permalink / raw) To: Junio C Hamano Cc: Stefan Beller, Johannes Schindelin, git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra On Sun, Jan 08, 2017 at 05:25:00PM -0800, Junio C Hamano wrote: > > If this patch is only to appease coverity (as the commit message eludes > > to) I think this may be a bad idea for upstream. If this patch fixes an > > actual problem, then the commit message needs to spell that out. > > That is true, and I see Peff pointed out another possible issue > around getenv(), but I think from the "one step at a time" point of > view, it is an improvement to call system_path() just once and cache > it in "static char *". Yep, I don't think it's a big deal to do it on top, like this: -- >8 -- Subject: git_exec_path: do not return the result of getenv() The result of getenv() is not guaranteed by POSIX to last beyond another call to getenv(), or setenv(), etc. We should duplicate the string before returning to the caller to avoid any surprises. We already keep a cached pointer to avoid repeatedly leaking the result of system_path(). We can use the same pointer here to avoid allocating and leaking for each call. Signed-off-by: Jeff King <peff@peff.net> --- To be honest, I do not know how big a problem this is. I looked at the code paths that call git_exec_path(), and the most likely problem case is calling a second getenv() is via the strbuf functions, which call xmalloc(), which checks $GIT_ALLOC_LIMIT. We do cache that value, but it would be a potential problem if this is the first xmalloc call in the program. But we are not really solving that here, as xstrdup() would have the same problem. This _is_ safer, in that we've better contained the length of time that we expect the result to be valid. I have no idea what platforms, if any, use a single static buffer for the getenv() return. I don't know that we've ever gotten a bug report about it (I only knew about it because somebody pointed it out in one of my patches a few years ago, so I have it in the back of my mind as a potential problem). So I don't mind if this is dropped as "too esoteric" until somebody actually reports a bug about it. exec_cmd.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/exec_cmd.c b/exec_cmd.c index 587bd7eb4..fb94aeba9 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -64,20 +64,19 @@ void git_set_argv_exec_path(const char *exec_path) /* Returns the highest-priority, location to look for git programs. */ const char *git_exec_path(void) { - const char *env; - static char *system_exec_path; + static char *cached_exec_path; if (argv_exec_path) return argv_exec_path; - env = getenv(EXEC_PATH_ENVIRONMENT); - if (env && *env) { - return env; + if (!cached_exec_path) { + const char *env = getenv(EXEC_PATH_ENVIRONMENT); + if (env && *env) + cached_exec_path = xstrdup(env); + else + cached_exec_path = system_path(GIT_EXEC_PATH); } - - if (!system_exec_path) - system_exec_path = system_path(GIT_EXEC_PATH); - return system_exec_path; + return cached_exec_path; } static void add_path(struct strbuf *out, const char *path) -- 2.11.0.531.ge85397315 ^ permalink raw reply related [flat|nested] 86+ messages in thread
* Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path() 2017-01-09 1:25 ` Junio C Hamano 2017-01-09 6:00 ` Jeff King @ 2017-01-09 7:49 ` Johannes Schindelin 2017-01-09 19:21 ` Stefan Beller 2 siblings, 0 replies; 86+ messages in thread From: Johannes Schindelin @ 2017-01-09 7:49 UTC (permalink / raw) To: Junio C Hamano Cc: Stefan Beller, git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra Hi, On Sun, 8 Jan 2017, Junio C Hamano wrote: > How about explaining it like this then? > > (only the log message has been corrected; diff is from the original). > > commit c9bb5d101ca657fa466afa8c4368c43ea7b7aca8 > Author: Johannes Schindelin <johannes.schindelin@gmx.de> > Date: Mon Jan 2 17:22:33 2017 +0100 > > git_exec_path: avoid Coverity warning about unfree()d result > > Technically, it is correct that git_exec_path() returns a possibly > malloc()ed string returned from system_path(), and it is sometimes > not allocated. Cache the result in a static variable and make sure > that we call system_path() only once, which plugs a potential leak. > > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> > Signed-off-by: Junio C Hamano <gitster@pobox.com> Sounds good to me. Ciao, Dscho ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path() 2017-01-09 1:25 ` Junio C Hamano 2017-01-09 6:00 ` Jeff King 2017-01-09 7:49 ` Johannes Schindelin @ 2017-01-09 19:21 ` Stefan Beller 2 siblings, 0 replies; 86+ messages in thread From: Stefan Beller @ 2017-01-09 19:21 UTC (permalink / raw) To: Junio C Hamano Cc: Johannes Schindelin, git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra On Sun, Jan 8, 2017 at 5:25 PM, Junio C Hamano <gitster@pobox.com> wrote: > So with the above, are you saying "Dscho said 'hopefully', and I > confirm that this change does squelch misdiagnosis by Coverity"? I could not find the coverity issue any more. (It really misses easy access to "recently fixed" problems) > commit c9bb5d101ca657fa466afa8c4368c43ea7b7aca8 > Author: Johannes Schindelin <johannes.schindelin@gmx.de> > Date: Mon Jan 2 17:22:33 2017 +0100 > > git_exec_path: avoid Coverity warning about unfree()d result > > Technically, it is correct that git_exec_path() returns a possibly > malloc()ed string returned from system_path(), and it is sometimes > not allocated. Cache the result in a static variable and make sure > that we call system_path() only once, which plugs a potential leak. > > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> > Signed-off-by: Junio C Hamano <gitster@pobox.com> Sounds good to me, Thanks, Stefan ^ permalink raw reply [flat|nested] 86+ messages in thread
* [PATCH v4 2/4] difftool: add a skeleton for the upcoming builtin 2017-01-02 16:16 ` [PATCH v4 0/4] Show Git Mailing List: a builtin difftool Johannes Schindelin 2017-01-02 16:22 ` [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path() Johannes Schindelin @ 2017-01-02 16:22 ` Johannes Schindelin 2017-01-02 16:22 ` [PATCH v4 3/4] difftool: implement the functionality in the builtin Johannes Schindelin ` (2 subsequent siblings) 4 siblings, 0 replies; 86+ messages in thread From: Johannes Schindelin @ 2017-01-02 16:22 UTC (permalink / raw) To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra This adds a builtin difftool that still falls back to the legacy Perl version, which has been renamed to `legacy-difftool`. The idea is that the new, experimental, builtin difftool immediately hands off to the legacy difftool for now, unless the config variable difftool.useBuiltin is set to true. This feature flag will be used in the upcoming Git for Windows v2.11.0 release, to allow early testers to opt-in to use the builtin difftool and flesh out any bugs. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- .gitignore | 1 + Makefile | 3 +- builtin.h | 1 + builtin/difftool.c | 63 +++++++++++++++++++++++++++ git-difftool.perl => git-legacy-difftool.perl | 0 git.c | 6 +++ t/t7800-difftool.sh | 2 + 7 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 builtin/difftool.c rename git-difftool.perl => git-legacy-difftool.perl (100%) diff --git a/.gitignore b/.gitignore index 6722f78f9a..5555ae025b 100644 --- a/.gitignore +++ b/.gitignore @@ -76,6 +76,7 @@ /git-init-db /git-interpret-trailers /git-instaweb +/git-legacy-difftool /git-log /git-ls-files /git-ls-remote diff --git a/Makefile b/Makefile index d861bd9985..8cf5bef034 100644 --- a/Makefile +++ b/Makefile @@ -522,7 +522,7 @@ SCRIPT_LIB += git-sh-setup SCRIPT_LIB += git-sh-i18n SCRIPT_PERL += git-add--interactive.perl -SCRIPT_PERL += git-difftool.perl +SCRIPT_PERL += git-legacy-difftool.perl SCRIPT_PERL += git-archimport.perl SCRIPT_PERL += git-cvsexportcommit.perl SCRIPT_PERL += git-cvsimport.perl @@ -883,6 +883,7 @@ BUILTIN_OBJS += builtin/diff-files.o BUILTIN_OBJS += builtin/diff-index.o BUILTIN_OBJS += builtin/diff-tree.o BUILTIN_OBJS += builtin/diff.o +BUILTIN_OBJS += builtin/difftool.o BUILTIN_OBJS += builtin/fast-export.o BUILTIN_OBJS += builtin/fetch-pack.o BUILTIN_OBJS += builtin/fetch.o diff --git a/builtin.h b/builtin.h index b9122bc5f4..67f80519da 100644 --- a/builtin.h +++ b/builtin.h @@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix); extern int cmd_diff_index(int argc, const char **argv, const char *prefix); extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); +extern int cmd_difftool(int argc, const char **argv, const char *prefix); extern int cmd_fast_export(int argc, const char **argv, const char *prefix); extern int cmd_fetch(int argc, const char **argv, const char *prefix); extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix); diff --git a/builtin/difftool.c b/builtin/difftool.c new file mode 100644 index 0000000000..53870bbaf7 --- /dev/null +++ b/builtin/difftool.c @@ -0,0 +1,63 @@ +/* + * "git difftool" builtin command + * + * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible + * git-difftool--helper script. + * + * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git. + * The GIT_DIFF* variables are exported for use by git-difftool--helper. + * + * Any arguments that are unknown to this script are forwarded to 'git diff'. + * + * Copyright (C) 2016 Johannes Schindelin + */ +#include "builtin.h" +#include "run-command.h" +#include "exec_cmd.h" + +/* + * NEEDSWORK: this function can go once the legacy-difftool Perl script is + * retired. + * + * We intentionally avoid reading the config directly here, to avoid messing up + * the GIT_* environment variables when we need to fall back to exec()ing the + * Perl script. + */ +static int use_builtin_difftool(void) { + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + int ret; + + argv_array_pushl(&cp.args, + "config", "--bool", "difftool.usebuiltin", NULL); + cp.git_cmd = 1; + if (capture_command(&cp, &out, 6)) + return 0; + strbuf_trim(&out); + ret = !strcmp("true", out.buf); + strbuf_release(&out); + return ret; +} + +int cmd_difftool(int argc, const char **argv, const char *prefix) +{ + /* + * NEEDSWORK: Once the builtin difftool has been tested enough + * and git-legacy-difftool.perl is retired to contrib/, this preamble + * can be removed. + */ + if (!use_builtin_difftool()) { + const char *path = mkpath("%s/git-legacy-difftool", + git_exec_path()); + + if (sane_execvp(path, (char **)argv) < 0) + die_errno("could not exec %s", path); + + return 0; + } + prefix = setup_git_directory(); + trace_repo_setup(prefix); + setup_work_tree(); + + die("TODO"); +} diff --git a/git-difftool.perl b/git-legacy-difftool.perl similarity index 100% rename from git-difftool.perl rename to git-legacy-difftool.perl diff --git a/git.c b/git.c index dce529fcbf..044958a780 100644 --- a/git.c +++ b/git.c @@ -424,6 +424,12 @@ static struct cmd_struct commands[] = { { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE }, { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, + /* + * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in + * builtin/difftool.c has been removed, this entry should be changed to + * RUN_SETUP | NEED_WORK_TREE + */ + { "difftool", cmd_difftool }, { "fast-export", cmd_fast_export, RUN_SETUP }, { "fetch", cmd_fetch, RUN_SETUP }, { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 99d4123461..e94910c563 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -23,6 +23,8 @@ prompt_given () test "$prompt" = "Launch 'test-tool' [Y/n]? branch" } +# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired. + # Create a file on master and change it on branch test_expect_success PERL 'setup' ' echo master >file && -- 2.11.0.rc3.windows.1 ^ permalink raw reply related [flat|nested] 86+ messages in thread
* [PATCH v4 3/4] difftool: implement the functionality in the builtin 2017-01-02 16:16 ` [PATCH v4 0/4] Show Git Mailing List: a builtin difftool Johannes Schindelin 2017-01-02 16:22 ` [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path() Johannes Schindelin 2017-01-02 16:22 ` [PATCH v4 2/4] difftool: add a skeleton for the upcoming builtin Johannes Schindelin @ 2017-01-02 16:22 ` Johannes Schindelin 2017-01-02 16:24 ` [PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now Johannes Schindelin 2017-01-17 15:54 ` [PATCH v5 0/3] Turn the difftool into a builtin Johannes Schindelin 4 siblings, 0 replies; 86+ messages in thread From: Johannes Schindelin @ 2017-01-02 16:22 UTC (permalink / raw) To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra This patch gives life to the skeleton added in the previous patch. The motivation for converting the difftool is that Perl scripts are not at all native on Windows, and that `git difftool` therefore is pretty slow on that platform, when there is no good reason for it to be slow. In addition, Perl does not really have access to Git's internals. That means that any script will always have to jump through unnecessary hoops. The current version of the builtin difftool does not, however, make full use of the internals but instead chooses to spawn a couple of Git processes, still, to make for an easier conversion. There remains a lot of room for improvement, left for a later date. Note: to play it safe, the original difftool is still called unless the config setting difftool.useBuiltin is set to true. The reason: this new, experimental, builtin difftool will be shipped as part of Git for Windows v2.11.0, to allow for easier large-scale testing, but of course as an opt-in feature. Sadly, the speedup is more noticable on Linux than on Windows: a quick test shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s) (real/user/sys) in a Linux VM, down from (6.529s/3.112s/0.644s), while on Windows, it is (36.064s/2.730s/7.194s), down from (47.637s/2.407s/6.863s). The culprit is most likely the overhead incurred from *still* having to shell out to mergetool-lib.sh and difftool--helper.sh. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/difftool.c | 672 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 671 insertions(+), 1 deletion(-) diff --git a/builtin/difftool.c b/builtin/difftool.c index 53870bbaf7..2115e548a5 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -11,9 +11,610 @@ * * Copyright (C) 2016 Johannes Schindelin */ +#include "cache.h" #include "builtin.h" #include "run-command.h" #include "exec_cmd.h" +#include "parse-options.h" +#include "argv-array.h" +#include "strbuf.h" +#include "lockfile.h" +#include "dir.h" + +static char *diff_gui_tool; +static int trust_exit_code; + +static const char *const builtin_difftool_usage[] = { + N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"), + NULL +}; + +static int difftool_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "diff.guitool")) { + diff_gui_tool = xstrdup(value); + return 0; + } + + if (!strcmp(var, "difftool.trustexitcode")) { + trust_exit_code = git_config_bool(var, value); + return 0; + } + + return git_default_config(var, value, cb); +} + +static int print_tool_help(void) +{ + const char *argv[] = { "mergetool", "--tool-help=diff", NULL }; + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int parse_index_info(char *p, int *mode1, int *mode2, + struct object_id *oid1, struct object_id *oid2, + char *status) +{ + if (*p != ':') + return error("expected ':', got '%c'", *p); + *mode1 = (int)strtol(p + 1, &p, 8); + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + *mode2 = (int)strtol(p + 1, &p, 8); + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + if (get_oid_hex(++p, oid1)) + return error("expected object ID, got '%s'", p + 1); + p += GIT_SHA1_HEXSZ; + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + if (get_oid_hex(++p, oid2)) + return error("expected object ID, got '%s'", p + 1); + p += GIT_SHA1_HEXSZ; + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + *status = *++p; + if (!*status) + return error("missing status"); + if (p[1] && !isdigit(p[1])) + return error("unexpected trailer: '%s'", p + 1); + return 0; +} + +/* + * Remove any trailing slash from $workdir + * before starting to avoid double slashes in symlink targets. + */ +static void add_path(struct strbuf *buf, size_t base_len, const char *path) +{ + strbuf_setlen(buf, base_len); + if (buf->len && buf->buf[buf->len - 1] != '/') + strbuf_addch(buf, '/'); + strbuf_addstr(buf, path); +} + +/* + * Determine whether we can simply reuse the file in the worktree. + */ +static int use_wt_file(const char *workdir, const char *name, + struct object_id *oid) +{ + struct strbuf buf = STRBUF_INIT; + struct stat st; + int use = 0; + + strbuf_addstr(&buf, workdir); + add_path(&buf, buf.len, name); + + if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) { + struct object_id wt_oid; + int fd = open(buf.buf, O_RDONLY); + + if (fd >= 0 && + !index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) { + if (is_null_oid(oid)) { + oidcpy(oid, &wt_oid); + use = 1; + } else if (!oidcmp(oid, &wt_oid)) + use = 1; + } + } + + strbuf_release(&buf); + + return use; +} + +struct working_tree_entry { + struct hashmap_entry entry; + char path[FLEX_ARRAY]; +}; + +static int working_tree_entry_cmp(struct working_tree_entry *a, + struct working_tree_entry *b, void *keydata) +{ + return strcmp(a->path, b->path); +} + +/* + * The `left` and `right` entries hold paths for the symlinks hashmap, + * and a SHA-1 surrounded by brief text for submodules. + */ +struct pair_entry { + struct hashmap_entry entry; + char left[PATH_MAX], right[PATH_MAX]; + const char path[FLEX_ARRAY]; +}; + +static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata) +{ + return strcmp(a->path, b->path); +} + +static void add_left_or_right(struct hashmap *map, const char *path, + const char *content, int is_right) +{ + struct pair_entry *e, *existing; + + FLEX_ALLOC_STR(e, path, path); + hashmap_entry_init(e, strhash(path)); + existing = hashmap_get(map, e, NULL); + if (existing) { + free(e); + e = existing; + } else { + e->left[0] = e->right[0] = '\0'; + hashmap_add(map, e); + } + strlcpy(is_right ? e->right : e->left, content, PATH_MAX); +} + +struct path_entry { + struct hashmap_entry entry; + char path[FLEX_ARRAY]; +}; + +static int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key) +{ + return strcmp(a->path, key ? key : b->path); +} + +static void changed_files(struct hashmap *result, const char *index_path, + const char *workdir) +{ + struct child_process update_index = CHILD_PROCESS_INIT; + struct child_process diff_files = CHILD_PROCESS_INIT; + struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT; + const char *git_dir = absolute_path(get_git_dir()), *env[] = { + NULL, NULL + }; + FILE *fp; + + strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path); + env[0] = index_env.buf; + + argv_array_pushl(&update_index.args, + "--git-dir", git_dir, "--work-tree", workdir, + "update-index", "--really-refresh", "-q", + "--unmerged", NULL); + update_index.no_stdin = 1; + update_index.no_stdout = 1; + update_index.no_stderr = 1; + update_index.git_cmd = 1; + update_index.use_shell = 0; + update_index.clean_on_exit = 1; + update_index.dir = workdir; + update_index.env = env; + /* Ignore any errors of update-index */ + run_command(&update_index); + + argv_array_pushl(&diff_files.args, + "--git-dir", git_dir, "--work-tree", workdir, + "diff-files", "--name-only", "-z", NULL); + diff_files.no_stdin = 1; + diff_files.git_cmd = 1; + diff_files.use_shell = 0; + diff_files.clean_on_exit = 1; + diff_files.out = -1; + diff_files.dir = workdir; + diff_files.env = env; + if (start_command(&diff_files)) + die("could not obtain raw diff"); + fp = xfdopen(diff_files.out, "r"); + while (!strbuf_getline_nul(&buf, fp)) { + struct path_entry *entry; + FLEX_ALLOC_STR(entry, path, buf.buf); + hashmap_entry_init(entry, strhash(buf.buf)); + hashmap_add(result, entry); + } + if (finish_command(&diff_files)) + die("diff-files did not exit properly"); + strbuf_release(&index_env); + strbuf_release(&buf); +} + +static NORETURN void exit_cleanup(const char *tmpdir, int exit_code) +{ + struct strbuf buf = STRBUF_INIT; + strbuf_addstr(&buf, tmpdir); + remove_dir_recursively(&buf, 0); + if (exit_code) + warning(_("failed: %d"), exit_code); + exit(exit_code); +} + +static int ensure_leading_directories(char *path) +{ + switch (safe_create_leading_directories(path)) { + case SCLD_OK: + case SCLD_EXISTS: + return 0; + default: + return error(_("could not create leading directories " + "of '%s'"), path); + } +} + +static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, + int argc, const char **argv) +{ + char tmpdir[PATH_MAX]; + struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT; + struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT; + struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT; + struct strbuf wtdir = STRBUF_INIT; + size_t ldir_len, rdir_len, wtdir_len; + struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1); + const char *workdir, *tmp; + int ret = 0, i; + FILE *fp; + struct hashmap working_tree_dups, submodules, symlinks2; + struct hashmap_iter iter; + struct pair_entry *entry; + enum object_type type; + unsigned long size; + struct index_state wtindex; + struct checkout lstate, rstate; + int rc, flags = RUN_GIT_CMD, err = 0; + struct child_process child = CHILD_PROCESS_INIT; + const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL }; + struct hashmap wt_modified, tmp_modified; + int indices_loaded = 0; + + workdir = get_git_work_tree(); + + /* Setup temp directories */ + tmp = getenv("TMPDIR"); + xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp"); + if (!mkdtemp(tmpdir)) + return error("could not create '%s'", tmpdir); + strbuf_addf(&ldir, "%s/left/", tmpdir); + strbuf_addf(&rdir, "%s/right/", tmpdir); + strbuf_addstr(&wtdir, workdir); + if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1])) + strbuf_addch(&wtdir, '/'); + mkdir(ldir.buf, 0700); + mkdir(rdir.buf, 0700); + + memset(&wtindex, 0, sizeof(wtindex)); + + memset(&lstate, 0, sizeof(lstate)); + lstate.base_dir = ldir.buf; + lstate.base_dir_len = ldir.len; + lstate.force = 1; + memset(&rstate, 0, sizeof(rstate)); + rstate.base_dir = rdir.buf; + rstate.base_dir_len = rdir.len; + rstate.force = 1; + + ldir_len = ldir.len; + rdir_len = rdir.len; + wtdir_len = wtdir.len; + + hashmap_init(&working_tree_dups, + (hashmap_cmp_fn)working_tree_entry_cmp, 0); + hashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0); + hashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0); + + child.no_stdin = 1; + child.git_cmd = 1; + child.use_shell = 0; + child.clean_on_exit = 1; + child.dir = prefix; + child.out = -1; + argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z", + NULL); + for (i = 0; i < argc; i++) + argv_array_push(&child.args, argv[i]); + if (start_command(&child)) + die("could not obtain raw diff"); + fp = xfdopen(child.out, "r"); + + /* Build index info for left and right sides of the diff */ + i = 0; + while (!strbuf_getline_nul(&info, fp)) { + int lmode, rmode; + struct object_id loid, roid; + char status; + const char *src_path, *dst_path; + size_t src_path_len, dst_path_len; + + if (starts_with(info.buf, "::")) + die(N_("combined diff formats('-c' and '--cc') are " + "not supported in\n" + "directory diff mode('-d' and '--dir-diff').")); + + if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid, + &status)) + break; + if (strbuf_getline_nul(&lpath, fp)) + break; + src_path = lpath.buf; + src_path_len = lpath.len; + + i++; + if (status != 'C' && status != 'R') { + dst_path = src_path; + dst_path_len = src_path_len; + } else { + if (strbuf_getline_nul(&rpath, fp)) + break; + dst_path = rpath.buf; + dst_path_len = rpath.len; + } + + if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) { + strbuf_reset(&buf); + strbuf_addf(&buf, "Subproject commit %s", + oid_to_hex(&loid)); + add_left_or_right(&submodules, src_path, buf.buf, 0); + strbuf_reset(&buf); + strbuf_addf(&buf, "Subproject commit %s", + oid_to_hex(&roid)); + if (!oidcmp(&loid, &roid)) + strbuf_addstr(&buf, "-dirty"); + add_left_or_right(&submodules, dst_path, buf.buf, 1); + continue; + } + + if (S_ISLNK(lmode)) { + char *content = read_sha1_file(loid.hash, &type, &size); + add_left_or_right(&symlinks2, src_path, content, 0); + free(content); + } + + if (S_ISLNK(rmode)) { + char *content = read_sha1_file(roid.hash, &type, &size); + add_left_or_right(&symlinks2, dst_path, content, 1); + free(content); + } + + if (lmode && status != 'C') { + ce->ce_mode = lmode; + oidcpy(&ce->oid, &loid); + strcpy(ce->name, src_path); + ce->ce_namelen = src_path_len; + if (checkout_entry(ce, &lstate, NULL)) + return error("could not write '%s'", src_path); + } + + if (rmode) { + struct working_tree_entry *entry; + + /* Avoid duplicate working_tree entries */ + FLEX_ALLOC_STR(entry, path, dst_path); + hashmap_entry_init(entry, strhash(dst_path)); + if (hashmap_get(&working_tree_dups, entry, NULL)) { + free(entry); + continue; + } + hashmap_add(&working_tree_dups, entry); + + if (!use_wt_file(workdir, dst_path, &roid)) { + ce->ce_mode = rmode; + oidcpy(&ce->oid, &roid); + strcpy(ce->name, dst_path); + ce->ce_namelen = dst_path_len; + if (checkout_entry(ce, &rstate, NULL)) + return error("could not write '%s'", + dst_path); + } else if (!is_null_oid(&roid)) { + /* + * Changes in the working tree need special + * treatment since they are not part of the + * index. + */ + struct cache_entry *ce2 = + make_cache_entry(rmode, roid.hash, + dst_path, 0, 0); + + add_index_entry(&wtindex, ce2, + ADD_CACHE_JUST_APPEND); + + add_path(&rdir, rdir_len, dst_path); + if (ensure_leading_directories(rdir.buf)) + return error("could not create " + "directory for '%s'", + dst_path); + add_path(&wtdir, wtdir_len, dst_path); + if (symlinks) { + if (symlink(wtdir.buf, rdir.buf)) { + ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf); + goto finish; + } + } else { + struct stat st; + if (stat(wtdir.buf, &st)) + st.st_mode = 0644; + if (copy_file(rdir.buf, wtdir.buf, + st.st_mode)) { + ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf); + goto finish; + } + } + } + } + } + + if (finish_command(&child)) { + ret = error("error occurred running diff --raw"); + goto finish; + } + + if (!i) + return 0; + + /* + * Changes to submodules require special treatment.This loop writes a + * temporary file to both the left and right directories to show the + * change in the recorded SHA1 for the submodule. + */ + hashmap_iter_init(&submodules, &iter); + while ((entry = hashmap_iter_next(&iter))) { + if (*entry->left) { + add_path(&ldir, ldir_len, entry->path); + ensure_leading_directories(ldir.buf); + write_file(ldir.buf, "%s", entry->left); + } + if (*entry->right) { + add_path(&rdir, rdir_len, entry->path); + ensure_leading_directories(rdir.buf); + write_file(rdir.buf, "%s", entry->right); + } + } + + /* + * Symbolic links require special treatment.The standard "git diff" + * shows only the link itself, not the contents of the link target. + * This loop replicates that behavior. + */ + hashmap_iter_init(&symlinks2, &iter); + while ((entry = hashmap_iter_next(&iter))) { + if (*entry->left) { + add_path(&ldir, ldir_len, entry->path); + ensure_leading_directories(ldir.buf); + write_file(ldir.buf, "%s", entry->left); + } + if (*entry->right) { + add_path(&rdir, rdir_len, entry->path); + ensure_leading_directories(rdir.buf); + write_file(rdir.buf, "%s", entry->right); + } + } + + strbuf_release(&buf); + + strbuf_setlen(&ldir, ldir_len); + helper_argv[1] = ldir.buf; + strbuf_setlen(&rdir, rdir_len); + helper_argv[2] = rdir.buf; + + if (extcmd) { + helper_argv[0] = extcmd; + flags = 0; + } else + setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1); + rc = run_command_v_opt(helper_argv, flags); + + /* + * If the diff includes working copy files and those + * files were modified during the diff, then the changes + * should be copied back to the working tree. + * Do not copy back files when symlinks are used and the + * external tool did not replace the original link with a file. + * + * These hashes are loaded lazily since they aren't needed + * in the common case of --symlinks and the difftool updating + * files through the symlink. + */ + hashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp, + wtindex.cache_nr); + hashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp, + wtindex.cache_nr); + + for (i = 0; i < wtindex.cache_nr; i++) { + struct hashmap_entry dummy; + const char *name = wtindex.cache[i]->name; + struct stat st; + + add_path(&rdir, rdir_len, name); + if (lstat(rdir.buf, &st)) + continue; + + if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode)) + continue; + + if (!indices_loaded) { + static struct lock_file lock; + strbuf_reset(&buf); + strbuf_addf(&buf, "%s/wtindex", tmpdir); + if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 || + write_locked_index(&wtindex, &lock, COMMIT_LOCK)) { + ret = error("could not write %s", buf.buf); + rollback_lock_file(&lock); + goto finish; + } + changed_files(&wt_modified, buf.buf, workdir); + strbuf_setlen(&rdir, rdir_len); + changed_files(&tmp_modified, buf.buf, rdir.buf); + add_path(&rdir, rdir_len, name); + indices_loaded = 1; + } + + hashmap_entry_init(&dummy, strhash(name)); + if (hashmap_get(&tmp_modified, &dummy, name)) { + add_path(&wtdir, wtdir_len, name); + if (hashmap_get(&wt_modified, &dummy, name)) { + warning(_("both files modified: '%s' and '%s'."), + wtdir.buf, rdir.buf); + warning(_("working tree file has been left.")); + warning(""); + err = 1; + } else if (unlink(wtdir.buf) || + copy_file(wtdir.buf, rdir.buf, st.st_mode)) + warning_errno(_("could not copy '%s' to '%s'"), + rdir.buf, wtdir.buf); + } + } + + if (err) { + warning(_("temporary files exist in '%s'."), tmpdir); + warning(_("you may want to cleanup or recover these.")); + exit(1); + } else + exit_cleanup(tmpdir, rc); + +finish: + free(ce); + strbuf_release(&ldir); + strbuf_release(&rdir); + strbuf_release(&wtdir); + strbuf_release(&buf); + + return ret; +} + +static int run_file_diff(int prompt, const char *prefix, + int argc, const char **argv) +{ + struct argv_array args = ARGV_ARRAY_INIT; + const char *env[] = { + "GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL, + NULL + }; + int ret = 0, i; + + if (prompt > 0) + env[2] = "GIT_DIFFTOOL_PROMPT=true"; + else if (!prompt) + env[2] = "GIT_DIFFTOOL_NO_PROMPT=true"; + + + argv_array_push(&args, "diff"); + for (i = 0; i < argc; i++) + argv_array_push(&args, argv[i]); + ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env); + exit(ret); +} /* * NEEDSWORK: this function can go once the legacy-difftool Perl script is @@ -41,6 +642,35 @@ static int use_builtin_difftool(void) { int cmd_difftool(int argc, const char **argv, const char *prefix) { + int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0, + tool_help = 0; + static char *difftool_cmd = NULL, *extcmd = NULL; + struct option builtin_difftool_options[] = { + OPT_BOOL('g', "gui", &use_gui_tool, + N_("use `diff.guitool` instead of `diff.tool`")), + OPT_BOOL('d', "dir-diff", &dir_diff, + N_("perform a full-directory diff")), + { OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL, + N_("do not prompt before launching a diff tool"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0}, + { OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL, + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN, + NULL, 1 }, + OPT_BOOL(0, "symlinks", &symlinks, + N_("use symlinks in dir-diff mode")), + OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"), + N_("use the specified diff tool")), + OPT_BOOL(0, "tool-help", &tool_help, + N_("print a list of diff tools that may be used with " + "`--tool`")), + OPT_BOOL(0, "trust-exit-code", &trust_exit_code, + N_("make 'git-difftool' exit when an invoked diff " + "tool returns a non - zero exit code")), + OPT_STRING('x', "extcmd", &extcmd, N_("<command>"), + N_("specify a custom command for viewing diffs")), + OPT_END() + }; + /* * NEEDSWORK: Once the builtin difftool has been tested enough * and git-legacy-difftool.perl is retired to contrib/, this preamble @@ -58,6 +688,46 @@ int cmd_difftool(int argc, const char **argv, const char *prefix) prefix = setup_git_directory(); trace_repo_setup(prefix); setup_work_tree(); + /* NEEDSWORK: once we no longer spawn anything, remove this */ + setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1); + setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1); + + git_config(difftool_config, NULL); + symlinks = has_symlinks; + + argc = parse_options(argc, argv, prefix, builtin_difftool_options, + builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_DASHDASH); - die("TODO"); + if (tool_help) + return print_tool_help(); + + if (use_gui_tool && diff_gui_tool && *diff_gui_tool) + setenv("GIT_DIFF_TOOL", diff_gui_tool, 1); + else if (difftool_cmd) { + if (*difftool_cmd) + setenv("GIT_DIFF_TOOL", difftool_cmd, 1); + else + die(_("no <tool> given for --tool=<tool>")); + } + + if (extcmd) { + if (*extcmd) + setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1); + else + die(_("no <cmd> given for --extcmd=<cmd>")); + } + + setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE", + trust_exit_code ? "true" : "false", 1); + + /* + * In directory diff mode, 'git-difftool--helper' is called once + * to compare the a / b directories. In file diff mode, 'git diff' + * will invoke a separate instance of 'git-difftool--helper' for + * each file that changed. + */ + if (dir_diff) + return run_dir_diff(extcmd, symlinks, prefix, argc, argv); + return run_file_diff(prompt, prefix, argc, argv); } -- 2.11.0.rc3.windows.1 ^ permalink raw reply related [flat|nested] 86+ messages in thread
* [PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now 2017-01-02 16:16 ` [PATCH v4 0/4] Show Git Mailing List: a builtin difftool Johannes Schindelin ` (2 preceding siblings ...) 2017-01-02 16:22 ` [PATCH v4 3/4] difftool: implement the functionality in the builtin Johannes Schindelin @ 2017-01-02 16:24 ` Johannes Schindelin 2017-01-09 1:38 ` Junio C Hamano 2017-01-17 15:54 ` [PATCH v5 0/3] Turn the difftool into a builtin Johannes Schindelin 4 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2017-01-02 16:24 UTC (permalink / raw) To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra This is uglier than a simple touch "$GIT_EXEC_PATH/use-builtin-difftool" of course. But oh well. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- This patch implements the good ole' cross-validation technique (also known as "GitHub Scientist") that I already used for my rebase--helper work. I am not sure whether we want to have that patch in `master`, ever. But at least for the transition time, it may make sense. t/t7800-difftool.sh | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index e94910c563..273ab55723 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -23,6 +23,20 @@ prompt_given () test "$prompt" = "Launch 'test-tool' [Y/n]? branch" } +for use_builtin_difftool in false true +do + +test_expect_success 'verify we are running the correct difftool' ' + if test true = '$use_builtin_difftool' + then + test_must_fail ok=129 git difftool -h >help && + grep "g, --gui" help + else + git difftool -h >help && + grep "g|--gui" help + fi +' + # NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired. # Create a file on master and change it on branch @@ -606,4 +620,17 @@ test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' ' ) ' +test true != $use_builtin_difftool || break + +test_expect_success 'tear down for re-run' ' + rm -rf * .[a-z]* && + git init +' + +# run as builtin difftool now +GIT_CONFIG_PARAMETERS="'difftool.usebuiltin=true'" +export GIT_CONFIG_PARAMETERS + +done + test_done -- 2.11.0.rc3.windows.1 ^ permalink raw reply related [flat|nested] 86+ messages in thread
* Re: [PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now 2017-01-02 16:24 ` [PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now Johannes Schindelin @ 2017-01-09 1:38 ` Junio C Hamano 2017-01-09 7:56 ` Johannes Schindelin 0 siblings, 1 reply; 86+ messages in thread From: Junio C Hamano @ 2017-01-09 1:38 UTC (permalink / raw) To: Johannes Schindelin; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra Johannes Schindelin <johannes.schindelin@gmx.de> writes: > This is uglier than a simple > > touch "$GIT_EXEC_PATH/use-builtin-difftool" > > of course. But oh well. That is totally irrelevant. The more important point is that we do the same set of tests so instead of running just one, we run BOTH. If we did a bit more work, we could even say "even when there is no Perl installed, we run tests with only the new built-in one". This does not go that far yet, but that should not be too hard, I would think. See below. > diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh > index e94910c563..273ab55723 100755 > --- a/t/t7800-difftool.sh > +++ b/t/t7800-difftool.sh > @@ -23,6 +23,20 @@ prompt_given () > test "$prompt" = "Launch 'test-tool' [Y/n]? branch" > } > > +for use_builtin_difftool in false true > +do Instead of the above, I'd suggest to make the loop like so: for use_builtin_difftool in $TESTED_VARIANTS do and before the loop begins, set TESTED_VARIANTS to either "false true" or "true" by checking the PERL prerequisite. > +test_expect_success 'verify we are running the correct difftool' ' > + if test true = '$use_builtin_difftool' > + then > + test_must_fail ok=129 git difftool -h >help && > + grep "g, --gui" help > + else > + git difftool -h >help && > + grep "g|--gui" help > + fi > +' > + > # NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired. And then we can lose this comment. As all existing tests have PERL prereq that can go away with the above suggested change, we'd need to touch quite a many lines with "s/_success PERL /_success /". It may be a good time to indent these existing tests to make it clear that they are inside the new "for use_builtin_difftool" loop. > +test true != $use_builtin_difftool || break > + > +test_expect_success 'tear down for re-run' ' > + rm -rf * .[a-z]* && > + git init > +' > + > +# run as builtin difftool now > +GIT_CONFIG_PARAMETERS="'difftool.usebuiltin=true'" > +export GIT_CONFIG_PARAMETERS ... and indentation would extend to these newly added lines, of course. > +done > + > test_done ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now 2017-01-09 1:38 ` Junio C Hamano @ 2017-01-09 7:56 ` Johannes Schindelin 2017-01-09 9:46 ` Junio C Hamano 0 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2017-01-09 7:56 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra Hi Junio, On Sun, 8 Jan 2017, Junio C Hamano wrote: > Johannes Schindelin <johannes.schindelin@gmx.de> writes: > > > This is uglier than a simple > > > > touch "$GIT_EXEC_PATH/use-builtin-difftool" > > > > of course. But oh well. > > That is totally irrelevant. > > The more important point is that we do the same set of tests so > instead of running just one, we run BOTH. And the most important point is that we do all of this only during a hopefully brief period in time that is mostly spent on reviewing the code and finding serious bugs and fixing them. During that period (which I would expect to be spent completely inside `pu`), the cross-validation does not have to be beautiful, but correct. And even more importantly: it has to come at a minimal integration cost, in case David decides to add another test case to t7800. And after that period, we retire the Perl script and switch to the builtin difftool and simply drop this patch to cross-validate both difftools' outputs with one another. At which point all of this safe-guarding and indenting and all of those changes that are just meant to cross-validate the output during that hopefully brief period of time will become moot and all the work spent on those changes will be worthless. Oh, at that point also all review that went into *this* patch will have been spent in vain. In short: can we please move on to reviewing the *actual* builtin difftool and spend our effort on making sure that it is correct? Ciao, Johannes ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now 2017-01-09 7:56 ` Johannes Schindelin @ 2017-01-09 9:46 ` Junio C Hamano 0 siblings, 0 replies; 86+ messages in thread From: Junio C Hamano @ 2017-01-09 9:46 UTC (permalink / raw) To: Johannes Schindelin; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra Johannes Schindelin <Johannes.Schindelin@gmx.de> writes: > And the most important point is that we do all of this only during a > hopefully brief period in time that is mostly spent on reviewing the code > and finding serious bugs and fixing them. You seem to misunderstand the purpose of the code review. We indeed spot bugs while reviewing patches, especially ones from less experienced folks, but that is the least important part of the review. In general, we review for: - Design. Is the feature make sense? Is it too narrow? Are there better ways? Does it fit well with the rest of the system? - Explanation. Is the purpose of the change in the bigger picture explained well enough to allow future people to answer this question: "We now have an additional requirement to the feature. If the original author knew about that when this was first introduced, would s/he consider that our design for this additional thing consistent with the original design? Should we design our enhancement in a different way?" - Maintainability. Does the implementation avoid reinventing data structures and helper functions that already exist to interact with elements in the system? Would a future change to some elements in the system that are touched by the implementation require changes to both existing code _and_ reinvented ones the patch introduced? - Correctness. Does the implementation actually reflect the design and the way the design was explained? For the "difftool in C" topic, the first two are mostly irrelevant as the goal of the topic is to first replicate what already exists faithfully (even in a bug-to-bug compatible way). The issues in correctness is something your daily use before submission would have caught, use of 'next' users as testers will help, and also caught by running test suite (again, before submission). I honestly do not expect glaring errors in the code from experienced contributors remaining when their patches are polished enough to be submit to the list. ^ permalink raw reply [flat|nested] 86+ messages in thread
* [PATCH v5 0/3] Turn the difftool into a builtin 2017-01-02 16:16 ` [PATCH v4 0/4] Show Git Mailing List: a builtin difftool Johannes Schindelin ` (3 preceding siblings ...) 2017-01-02 16:24 ` [PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now Johannes Schindelin @ 2017-01-17 15:54 ` Johannes Schindelin 2017-01-17 15:54 ` [PATCH v5 1/3] difftool: add a skeleton for the upcoming builtin Johannes Schindelin ` (4 more replies) 4 siblings, 5 replies; 86+ messages in thread From: Johannes Schindelin @ 2017-01-17 15:54 UTC (permalink / raw) To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra This patch series converts the difftool from a Perl script into a builtin, for three reasons: 1. Perl is really not native on Windows. Not only is there a performance penalty to be paid just for running Perl scripts, we also have to deal with the fact that users may have different Perl installations, with different options, and some other Perl installation may decide to set PERL5LIB globally, wreaking havoc with Git for Windows' Perl (which we have to use because almost all other Perl distributions lack the Subversion bindings we need for `git svn`). 2. As the Perl script uses Unix-y paths that are not native to Windows, the Perl interpreter has to go through a POSIX emulation layer (the MSYS2 runtime). This means that paths have to be converted from Unix-y paths to Windows-y paths (and vice versa) whenever crossing the POSIX emulation barrier, leading to quite possibly surprising path translation errors. 3. Perl makes for a rather large reason that Git for Windows' installer weighs in with >30MB. While one Perl script less does not relieve us of that burden, it is one step in the right direction. Changes since v4: - skipped the unrelated Coverity-appeasing patch. - replaced the cross-validation with the Perl script by a patch that retires the Perl script instead. Johannes Schindelin (3): difftool: add a skeleton for the upcoming builtin difftool: implement the functionality in the builtin Retire the scripted difftool Makefile | 2 +- builtin.h | 1 + builtin/difftool.c | 692 +++++++++++++++++++++ .../examples/git-difftool.perl | 0 git.c | 1 + t/t7800-difftool.sh | 92 +-- 6 files changed, 741 insertions(+), 47 deletions(-) create mode 100644 builtin/difftool.c rename git-difftool.perl => contrib/examples/git-difftool.perl (100%) base-commit: d7dffce1cebde29a0c4b309a79e4345450bf352a Published-As: https://github.com/dscho/git/releases/tag/builtin-difftool-v5 Fetch-It-Via: git fetch https://github.com/dscho/git builtin-difftool-v5 Interdiff vs v4: diff --git a/.gitignore b/.gitignore index 5555ae025b..6722f78f9a 100644 --- a/.gitignore +++ b/.gitignore @@ -76,7 +76,6 @@ /git-init-db /git-interpret-trailers /git-instaweb -/git-legacy-difftool /git-log /git-ls-files /git-ls-remote diff --git a/Makefile b/Makefile index 8cf5bef034..e9aa6ae57c 100644 --- a/Makefile +++ b/Makefile @@ -522,7 +522,6 @@ SCRIPT_LIB += git-sh-setup SCRIPT_LIB += git-sh-i18n SCRIPT_PERL += git-add--interactive.perl -SCRIPT_PERL += git-legacy-difftool.perl SCRIPT_PERL += git-archimport.perl SCRIPT_PERL += git-cvsexportcommit.perl SCRIPT_PERL += git-cvsimport.perl diff --git a/builtin/difftool.c b/builtin/difftool.c index 2115e548a5..42ad9e804a 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -616,30 +616,6 @@ static int run_file_diff(int prompt, const char *prefix, exit(ret); } -/* - * NEEDSWORK: this function can go once the legacy-difftool Perl script is - * retired. - * - * We intentionally avoid reading the config directly here, to avoid messing up - * the GIT_* environment variables when we need to fall back to exec()ing the - * Perl script. - */ -static int use_builtin_difftool(void) { - struct child_process cp = CHILD_PROCESS_INIT; - struct strbuf out = STRBUF_INIT; - int ret; - - argv_array_pushl(&cp.args, - "config", "--bool", "difftool.usebuiltin", NULL); - cp.git_cmd = 1; - if (capture_command(&cp, &out, 6)) - return 0; - strbuf_trim(&out); - ret = !strcmp("true", out.buf); - strbuf_release(&out); - return ret; -} - int cmd_difftool(int argc, const char **argv, const char *prefix) { int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0, @@ -671,23 +647,6 @@ int cmd_difftool(int argc, const char **argv, const char *prefix) OPT_END() }; - /* - * NEEDSWORK: Once the builtin difftool has been tested enough - * and git-legacy-difftool.perl is retired to contrib/, this preamble - * can be removed. - */ - if (!use_builtin_difftool()) { - const char *path = mkpath("%s/git-legacy-difftool", - git_exec_path()); - - if (sane_execvp(path, (char **)argv) < 0) - die_errno("could not exec %s", path); - - return 0; - } - prefix = setup_git_directory(); - trace_repo_setup(prefix); - setup_work_tree(); /* NEEDSWORK: once we no longer spawn anything, remove this */ setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1); setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1); diff --git a/git-legacy-difftool.perl b/contrib/examples/git-difftool.perl similarity index 100% rename from git-legacy-difftool.perl rename to contrib/examples/git-difftool.perl diff --git a/exec_cmd.c b/exec_cmd.c index 587bd7eb48..19ac2146d0 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -65,7 +65,6 @@ void git_set_argv_exec_path(const char *exec_path) const char *git_exec_path(void) { const char *env; - static char *system_exec_path; if (argv_exec_path) return argv_exec_path; @@ -75,9 +74,7 @@ const char *git_exec_path(void) return env; } - if (!system_exec_path) - system_exec_path = system_path(GIT_EXEC_PATH); - return system_exec_path; + return system_path(GIT_EXEC_PATH); } static void add_path(struct strbuf *out, const char *path) diff --git a/git.c b/git.c index c58181e5ef..bd4d668a21 100644 --- a/git.c +++ b/git.c @@ -424,12 +424,7 @@ static struct cmd_struct commands[] = { { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE }, { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, - /* - * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in - * builtin/difftool.c has been removed, this entry should be changed to - * RUN_SETUP | NEED_WORK_TREE - */ - { "difftool", cmd_difftool }, + { "difftool", cmd_difftool, RUN_SETUP | NEED_WORK_TREE }, { "fast-export", cmd_fast_export, RUN_SETUP }, { "fetch", cmd_fetch, RUN_SETUP }, { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 273ab55723..aa0ef02597 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -23,24 +23,8 @@ prompt_given () test "$prompt" = "Launch 'test-tool' [Y/n]? branch" } -for use_builtin_difftool in false true -do - -test_expect_success 'verify we are running the correct difftool' ' - if test true = '$use_builtin_difftool' - then - test_must_fail ok=129 git difftool -h >help && - grep "g, --gui" help - else - git difftool -h >help && - grep "g|--gui" help - fi -' - -# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired. - # Create a file on master and change it on branch -test_expect_success PERL 'setup' ' +test_expect_success 'setup' ' echo master >file && git add file && git commit -m "added file" && @@ -52,7 +36,7 @@ test_expect_success PERL 'setup' ' ' # Configure a custom difftool.<tool>.cmd and use it -test_expect_success PERL 'custom commands' ' +test_expect_success 'custom commands' ' difftool_test_setup && test_config difftool.test-tool.cmd "cat \"\$REMOTE\"" && echo master >expect && @@ -65,21 +49,21 @@ test_expect_success PERL 'custom commands' ' test_cmp expect actual ' -test_expect_success PERL 'custom tool commands override built-ins' ' +test_expect_success 'custom tool commands override built-ins' ' test_config difftool.vimdiff.cmd "cat \"\$REMOTE\"" && echo master >expect && git difftool --tool vimdiff --no-prompt branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool ignores bad --tool values' ' +test_expect_success 'difftool ignores bad --tool values' ' : >expect && test_must_fail \ git difftool --no-prompt --tool=bad-tool branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool forwards arguments to diff' ' +test_expect_success 'difftool forwards arguments to diff' ' difftool_test_setup && >for-diff && git add for-diff && @@ -92,40 +76,40 @@ test_expect_success PERL 'difftool forwards arguments to diff' ' rm for-diff ' -test_expect_success PERL 'difftool ignores exit code' ' +test_expect_success 'difftool ignores exit code' ' test_config difftool.error.cmd false && git difftool -y -t error branch ' -test_expect_success PERL 'difftool forwards exit code with --trust-exit-code' ' +test_expect_success 'difftool forwards exit code with --trust-exit-code' ' test_config difftool.error.cmd false && test_must_fail git difftool -y --trust-exit-code -t error branch ' -test_expect_success PERL 'difftool forwards exit code with --trust-exit-code for built-ins' ' +test_expect_success 'difftool forwards exit code with --trust-exit-code for built-ins' ' test_config difftool.vimdiff.path false && test_must_fail git difftool -y --trust-exit-code -t vimdiff branch ' -test_expect_success PERL 'difftool honors difftool.trustExitCode = true' ' +test_expect_success 'difftool honors difftool.trustExitCode = true' ' test_config difftool.error.cmd false && test_config difftool.trustExitCode true && test_must_fail git difftool -y -t error branch ' -test_expect_success PERL 'difftool honors difftool.trustExitCode = false' ' +test_expect_success 'difftool honors difftool.trustExitCode = false' ' test_config difftool.error.cmd false && test_config difftool.trustExitCode false && git difftool -y -t error branch ' -test_expect_success PERL 'difftool ignores exit code with --no-trust-exit-code' ' +test_expect_success 'difftool ignores exit code with --no-trust-exit-code' ' test_config difftool.error.cmd false && test_config difftool.trustExitCode true && git difftool -y --no-trust-exit-code -t error branch ' -test_expect_success PERL 'difftool stops on error with --trust-exit-code' ' +test_expect_success 'difftool stops on error with --trust-exit-code' ' test_when_finished "rm -f for-diff .git/fail-right-file" && test_when_finished "git reset -- for-diff" && write_script .git/fail-right-file <<-\EOF && @@ -140,13 +124,13 @@ test_expect_success PERL 'difftool stops on error with --trust-exit-code' ' test_cmp expect actual ' -test_expect_success PERL 'difftool honors exit status if command not found' ' +test_expect_success 'difftool honors exit status if command not found' ' test_config difftool.nonexistent.cmd i-dont-exist && test_config difftool.trustExitCode false && test_must_fail git difftool -y -t nonexistent branch ' -test_expect_success PERL 'difftool honors --gui' ' +test_expect_success 'difftool honors --gui' ' difftool_test_setup && test_config merge.tool bogus-tool && test_config diff.tool bogus-tool && @@ -157,7 +141,7 @@ test_expect_success PERL 'difftool honors --gui' ' test_cmp expect actual ' -test_expect_success PERL 'difftool --gui last setting wins' ' +test_expect_success 'difftool --gui last setting wins' ' difftool_test_setup && : >expect && git difftool --no-prompt --gui --no-gui >actual && @@ -171,7 +155,7 @@ test_expect_success PERL 'difftool --gui last setting wins' ' test_cmp expect actual ' -test_expect_success PERL 'difftool --gui works without configured diff.guitool' ' +test_expect_success 'difftool --gui works without configured diff.guitool' ' difftool_test_setup && echo branch >expect && git difftool --no-prompt --gui branch >actual && @@ -179,7 +163,7 @@ test_expect_success PERL 'difftool --gui works without configured diff.guitool' ' # Specify the diff tool using $GIT_DIFF_TOOL -test_expect_success PERL 'GIT_DIFF_TOOL variable' ' +test_expect_success 'GIT_DIFF_TOOL variable' ' difftool_test_setup && git config --unset diff.tool && echo branch >expect && @@ -189,7 +173,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL variable' ' # Test the $GIT_*_TOOL variables and ensure # that $GIT_DIFF_TOOL always wins unless --tool is specified -test_expect_success PERL 'GIT_DIFF_TOOL overrides' ' +test_expect_success 'GIT_DIFF_TOOL overrides' ' difftool_test_setup && test_config diff.tool bogus-tool && test_config merge.tool bogus-tool && @@ -207,7 +191,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL overrides' ' # Test that we don't have to pass --no-prompt to difftool # when $GIT_DIFFTOOL_NO_PROMPT is true -test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' ' +test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' ' difftool_test_setup && echo branch >expect && GIT_DIFFTOOL_NO_PROMPT=true git difftool branch >actual && @@ -216,7 +200,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' ' # git-difftool supports the difftool.prompt variable. # Test that GIT_DIFFTOOL_PROMPT can override difftool.prompt = false -test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' ' +test_expect_success 'GIT_DIFFTOOL_PROMPT variable' ' difftool_test_setup && test_config difftool.prompt false && echo >input && @@ -226,7 +210,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' ' ' # Test that we don't have to pass --no-prompt when difftool.prompt is false -test_expect_success PERL 'difftool.prompt config variable is false' ' +test_expect_success 'difftool.prompt config variable is false' ' difftool_test_setup && test_config difftool.prompt false && echo branch >expect && @@ -235,7 +219,7 @@ test_expect_success PERL 'difftool.prompt config variable is false' ' ' # Test that we don't have to pass --no-prompt when mergetool.prompt is false -test_expect_success PERL 'difftool merge.prompt = false' ' +test_expect_success 'difftool merge.prompt = false' ' difftool_test_setup && test_might_fail git config --unset difftool.prompt && test_config mergetool.prompt false && @@ -245,7 +229,7 @@ test_expect_success PERL 'difftool merge.prompt = false' ' ' # Test that the -y flag can override difftool.prompt = true -test_expect_success PERL 'difftool.prompt can overridden with -y' ' +test_expect_success 'difftool.prompt can overridden with -y' ' difftool_test_setup && test_config difftool.prompt true && echo branch >expect && @@ -254,7 +238,7 @@ test_expect_success PERL 'difftool.prompt can overridden with -y' ' ' # Test that the --prompt flag can override difftool.prompt = false -test_expect_success PERL 'difftool.prompt can overridden with --prompt' ' +test_expect_success 'difftool.prompt can overridden with --prompt' ' difftool_test_setup && test_config difftool.prompt false && echo >input && @@ -264,7 +248,7 @@ test_expect_success PERL 'difftool.prompt can overridden with --prompt' ' ' # Test that the last flag passed on the command-line wins -test_expect_success PERL 'difftool last flag wins' ' +test_expect_success 'difftool last flag wins' ' difftool_test_setup && echo branch >expect && git difftool --prompt --no-prompt branch >actual && @@ -277,7 +261,7 @@ test_expect_success PERL 'difftool last flag wins' ' # git-difftool falls back to git-mergetool config variables # so test that behavior here -test_expect_success PERL 'difftool + mergetool config variables' ' +test_expect_success 'difftool + mergetool config variables' ' test_config merge.tool test-tool && test_config mergetool.test-tool.cmd "cat \$LOCAL" && echo branch >expect && @@ -291,49 +275,49 @@ test_expect_success PERL 'difftool + mergetool config variables' ' test_cmp expect actual ' -test_expect_success PERL 'difftool.<tool>.path' ' +test_expect_success 'difftool.<tool>.path' ' test_config difftool.tkdiff.path echo && git difftool --tool=tkdiff --no-prompt branch >output && lines=$(grep file output | wc -l) && test "$lines" -eq 1 ' -test_expect_success PERL 'difftool --extcmd=cat' ' +test_expect_success 'difftool --extcmd=cat' ' echo branch >expect && echo master >>expect && git difftool --no-prompt --extcmd=cat branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd cat' ' +test_expect_success 'difftool --extcmd cat' ' echo branch >expect && echo master >>expect && git difftool --no-prompt --extcmd=cat branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool -x cat' ' +test_expect_success 'difftool -x cat' ' echo branch >expect && echo master >>expect && git difftool --no-prompt -x cat branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd echo arg1' ' +test_expect_success 'difftool --extcmd echo arg1' ' echo file >expect && git difftool --no-prompt \ --extcmd sh\ -c\ \"echo\ \$1\" branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd cat arg1' ' +test_expect_success 'difftool --extcmd cat arg1' ' echo master >expect && git difftool --no-prompt \ --extcmd sh\ -c\ \"cat\ \$1\" branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd cat arg2' ' +test_expect_success 'difftool --extcmd cat arg2' ' echo branch >expect && git difftool --no-prompt \ --extcmd sh\ -c\ \"cat\ \$2\" branch >actual && @@ -341,7 +325,7 @@ test_expect_success PERL 'difftool --extcmd cat arg2' ' ' # Create a second file on master and a different version on branch -test_expect_success PERL 'setup with 2 files different' ' +test_expect_success 'setup with 2 files different' ' echo m2 >file2 && git add file2 && git commit -m "added file2" && @@ -353,7 +337,7 @@ test_expect_success PERL 'setup with 2 files different' ' git checkout master ' -test_expect_success PERL 'say no to the first file' ' +test_expect_success 'say no to the first file' ' (echo n && echo) >input && git difftool -x cat branch <input >output && grep m2 output && @@ -362,7 +346,7 @@ test_expect_success PERL 'say no to the first file' ' ! grep branch output ' -test_expect_success PERL 'say no to the second file' ' +test_expect_success 'say no to the second file' ' (echo && echo n) >input && git difftool -x cat branch <input >output && grep master output && @@ -371,7 +355,7 @@ test_expect_success PERL 'say no to the second file' ' ! grep br2 output ' -test_expect_success PERL 'ending prompt input with EOF' ' +test_expect_success 'ending prompt input with EOF' ' git difftool -x cat branch </dev/null >output && ! grep master output && ! grep branch output && @@ -379,12 +363,12 @@ test_expect_success PERL 'ending prompt input with EOF' ' ! grep br2 output ' -test_expect_success PERL 'difftool --tool-help' ' +test_expect_success 'difftool --tool-help' ' git difftool --tool-help >output && grep tool output ' -test_expect_success PERL 'setup change in subdirectory' ' +test_expect_success 'setup change in subdirectory' ' git checkout master && mkdir sub && echo master >sub/sub && @@ -398,11 +382,11 @@ test_expect_success PERL 'setup change in subdirectory' ' ' run_dir_diff_test () { - test_expect_success PERL "$1 --no-symlinks" " + test_expect_success "$1 --no-symlinks" " symlinks=--no-symlinks && $2 " - test_expect_success PERL,SYMLINKS "$1 --symlinks" " + test_expect_success SYMLINKS "$1 --symlinks" " symlinks=--symlinks && $2 " @@ -524,7 +508,7 @@ do done >actual EOF -test_expect_success PERL,SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' ' +test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' ' cat >expect <<-EOF && file $PWD/file @@ -561,7 +545,7 @@ write_script modify-file <<\EOF echo "new content" >file EOF -test_expect_success PERL 'difftool --no-symlinks does not overwrite working tree file ' ' +test_expect_success 'difftool --no-symlinks does not overwrite working tree file ' ' echo "orig content" >file && git difftool --dir-diff --no-symlinks --extcmd "$PWD/modify-file" branch && echo "new content" >expect && @@ -574,7 +558,7 @@ echo "tmp content" >"$2/file" && echo "$2" >tmpdir EOF -test_expect_success PERL 'difftool --no-symlinks detects conflict ' ' +test_expect_success 'difftool --no-symlinks detects conflict ' ' ( TMPDIR=$TRASH_DIRECTORY && export TMPDIR && @@ -587,7 +571,7 @@ test_expect_success PERL 'difftool --no-symlinks detects conflict ' ' ) ' -test_expect_success PERL 'difftool properly honors gitlink and core.worktree' ' +test_expect_success 'difftool properly honors gitlink and core.worktree' ' git submodule add ./. submod/ule && test_config -C submod/ule diff.tool checktrees && test_config -C submod/ule difftool.checktrees.cmd '\'' @@ -601,7 +585,7 @@ test_expect_success PERL 'difftool properly honors gitlink and core.worktree' ' ) ' -test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' ' +test_expect_success SYMLINKS 'difftool --dir-diff symlinked directories' ' git init dirlinks && ( cd dirlinks && @@ -620,17 +604,4 @@ test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' ' ) ' -test true != $use_builtin_difftool || break - -test_expect_success 'tear down for re-run' ' - rm -rf * .[a-z]* && - git init -' - -# run as builtin difftool now -GIT_CONFIG_PARAMETERS="'difftool.usebuiltin=true'" -export GIT_CONFIG_PARAMETERS - -done - test_done -- 2.11.0.windows.3 ^ permalink raw reply [flat|nested] 86+ messages in thread
* [PATCH v5 1/3] difftool: add a skeleton for the upcoming builtin 2017-01-17 15:54 ` [PATCH v5 0/3] Turn the difftool into a builtin Johannes Schindelin @ 2017-01-17 15:54 ` Johannes Schindelin 2017-01-17 15:55 ` [PATCH v5 2/3] difftool: implement the functionality in the builtin Johannes Schindelin ` (3 subsequent siblings) 4 siblings, 0 replies; 86+ messages in thread From: Johannes Schindelin @ 2017-01-17 15:54 UTC (permalink / raw) To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra This adds a builtin difftool that still falls back to the legacy Perl version, which has been renamed to `legacy-difftool`. The idea is that the new, experimental, builtin difftool immediately hands off to the legacy difftool for now, unless the config variable difftool.useBuiltin is set to true. This feature flag will be used in the upcoming Git for Windows v2.11.0 release, to allow early testers to opt-in to use the builtin difftool and flesh out any bugs. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- .gitignore | 1 + Makefile | 3 +- builtin.h | 1 + builtin/difftool.c | 63 +++++++++++++++++++++++++++ git-difftool.perl => git-legacy-difftool.perl | 0 git.c | 6 +++ t/t7800-difftool.sh | 2 + 7 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 builtin/difftool.c rename git-difftool.perl => git-legacy-difftool.perl (100%) diff --git a/.gitignore b/.gitignore index 6722f78f9a..5555ae025b 100644 --- a/.gitignore +++ b/.gitignore @@ -76,6 +76,7 @@ /git-init-db /git-interpret-trailers /git-instaweb +/git-legacy-difftool /git-log /git-ls-files /git-ls-remote diff --git a/Makefile b/Makefile index d861bd9985..8cf5bef034 100644 --- a/Makefile +++ b/Makefile @@ -522,7 +522,7 @@ SCRIPT_LIB += git-sh-setup SCRIPT_LIB += git-sh-i18n SCRIPT_PERL += git-add--interactive.perl -SCRIPT_PERL += git-difftool.perl +SCRIPT_PERL += git-legacy-difftool.perl SCRIPT_PERL += git-archimport.perl SCRIPT_PERL += git-cvsexportcommit.perl SCRIPT_PERL += git-cvsimport.perl @@ -883,6 +883,7 @@ BUILTIN_OBJS += builtin/diff-files.o BUILTIN_OBJS += builtin/diff-index.o BUILTIN_OBJS += builtin/diff-tree.o BUILTIN_OBJS += builtin/diff.o +BUILTIN_OBJS += builtin/difftool.o BUILTIN_OBJS += builtin/fast-export.o BUILTIN_OBJS += builtin/fetch-pack.o BUILTIN_OBJS += builtin/fetch.o diff --git a/builtin.h b/builtin.h index b9122bc5f4..67f80519da 100644 --- a/builtin.h +++ b/builtin.h @@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix); extern int cmd_diff_index(int argc, const char **argv, const char *prefix); extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); +extern int cmd_difftool(int argc, const char **argv, const char *prefix); extern int cmd_fast_export(int argc, const char **argv, const char *prefix); extern int cmd_fetch(int argc, const char **argv, const char *prefix); extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix); diff --git a/builtin/difftool.c b/builtin/difftool.c new file mode 100644 index 0000000000..53870bbaf7 --- /dev/null +++ b/builtin/difftool.c @@ -0,0 +1,63 @@ +/* + * "git difftool" builtin command + * + * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible + * git-difftool--helper script. + * + * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git. + * The GIT_DIFF* variables are exported for use by git-difftool--helper. + * + * Any arguments that are unknown to this script are forwarded to 'git diff'. + * + * Copyright (C) 2016 Johannes Schindelin + */ +#include "builtin.h" +#include "run-command.h" +#include "exec_cmd.h" + +/* + * NEEDSWORK: this function can go once the legacy-difftool Perl script is + * retired. + * + * We intentionally avoid reading the config directly here, to avoid messing up + * the GIT_* environment variables when we need to fall back to exec()ing the + * Perl script. + */ +static int use_builtin_difftool(void) { + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + int ret; + + argv_array_pushl(&cp.args, + "config", "--bool", "difftool.usebuiltin", NULL); + cp.git_cmd = 1; + if (capture_command(&cp, &out, 6)) + return 0; + strbuf_trim(&out); + ret = !strcmp("true", out.buf); + strbuf_release(&out); + return ret; +} + +int cmd_difftool(int argc, const char **argv, const char *prefix) +{ + /* + * NEEDSWORK: Once the builtin difftool has been tested enough + * and git-legacy-difftool.perl is retired to contrib/, this preamble + * can be removed. + */ + if (!use_builtin_difftool()) { + const char *path = mkpath("%s/git-legacy-difftool", + git_exec_path()); + + if (sane_execvp(path, (char **)argv) < 0) + die_errno("could not exec %s", path); + + return 0; + } + prefix = setup_git_directory(); + trace_repo_setup(prefix); + setup_work_tree(); + + die("TODO"); +} diff --git a/git-difftool.perl b/git-legacy-difftool.perl similarity index 100% rename from git-difftool.perl rename to git-legacy-difftool.perl diff --git a/git.c b/git.c index bbaa949e9c..c58181e5ef 100644 --- a/git.c +++ b/git.c @@ -424,6 +424,12 @@ static struct cmd_struct commands[] = { { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE }, { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, + /* + * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in + * builtin/difftool.c has been removed, this entry should be changed to + * RUN_SETUP | NEED_WORK_TREE + */ + { "difftool", cmd_difftool }, { "fast-export", cmd_fast_export, RUN_SETUP }, { "fetch", cmd_fetch, RUN_SETUP }, { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 99d4123461..e94910c563 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -23,6 +23,8 @@ prompt_given () test "$prompt" = "Launch 'test-tool' [Y/n]? branch" } +# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired. + # Create a file on master and change it on branch test_expect_success PERL 'setup' ' echo master >file && -- 2.11.0.windows.3 ^ permalink raw reply related [flat|nested] 86+ messages in thread
* [PATCH v5 2/3] difftool: implement the functionality in the builtin 2017-01-17 15:54 ` [PATCH v5 0/3] Turn the difftool into a builtin Johannes Schindelin 2017-01-17 15:54 ` [PATCH v5 1/3] difftool: add a skeleton for the upcoming builtin Johannes Schindelin @ 2017-01-17 15:55 ` Johannes Schindelin 2017-01-17 15:55 ` [PATCH v5 3/3] Retire the scripted difftool Johannes Schindelin ` (2 subsequent siblings) 4 siblings, 0 replies; 86+ messages in thread From: Johannes Schindelin @ 2017-01-17 15:55 UTC (permalink / raw) To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra This patch gives life to the skeleton added in the previous patch. The motivation for converting the difftool is that Perl scripts are not at all native on Windows, and that `git difftool` therefore is pretty slow on that platform, when there is no good reason for it to be slow. In addition, Perl does not really have access to Git's internals. That means that any script will always have to jump through unnecessary hoops. The current version of the builtin difftool does not, however, make full use of the internals but instead chooses to spawn a couple of Git processes, still, to make for an easier conversion. There remains a lot of room for improvement, left for a later date. Note: to play it safe, the original difftool is still called unless the config setting difftool.useBuiltin is set to true. The reason: this new, experimental, builtin difftool will be shipped as part of Git for Windows v2.11.0, to allow for easier large-scale testing, but of course as an opt-in feature. Sadly, the speedup is more noticable on Linux than on Windows: a quick test shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s) (real/user/sys) in a Linux VM, down from (6.529s/3.112s/0.644s), while on Windows, it is (36.064s/2.730s/7.194s), down from (47.637s/2.407s/6.863s). The culprit is most likely the overhead incurred from *still* having to shell out to mergetool-lib.sh and difftool--helper.sh. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/difftool.c | 672 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 671 insertions(+), 1 deletion(-) diff --git a/builtin/difftool.c b/builtin/difftool.c index 53870bbaf7..2115e548a5 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -11,9 +11,610 @@ * * Copyright (C) 2016 Johannes Schindelin */ +#include "cache.h" #include "builtin.h" #include "run-command.h" #include "exec_cmd.h" +#include "parse-options.h" +#include "argv-array.h" +#include "strbuf.h" +#include "lockfile.h" +#include "dir.h" + +static char *diff_gui_tool; +static int trust_exit_code; + +static const char *const builtin_difftool_usage[] = { + N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"), + NULL +}; + +static int difftool_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "diff.guitool")) { + diff_gui_tool = xstrdup(value); + return 0; + } + + if (!strcmp(var, "difftool.trustexitcode")) { + trust_exit_code = git_config_bool(var, value); + return 0; + } + + return git_default_config(var, value, cb); +} + +static int print_tool_help(void) +{ + const char *argv[] = { "mergetool", "--tool-help=diff", NULL }; + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int parse_index_info(char *p, int *mode1, int *mode2, + struct object_id *oid1, struct object_id *oid2, + char *status) +{ + if (*p != ':') + return error("expected ':', got '%c'", *p); + *mode1 = (int)strtol(p + 1, &p, 8); + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + *mode2 = (int)strtol(p + 1, &p, 8); + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + if (get_oid_hex(++p, oid1)) + return error("expected object ID, got '%s'", p + 1); + p += GIT_SHA1_HEXSZ; + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + if (get_oid_hex(++p, oid2)) + return error("expected object ID, got '%s'", p + 1); + p += GIT_SHA1_HEXSZ; + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + *status = *++p; + if (!*status) + return error("missing status"); + if (p[1] && !isdigit(p[1])) + return error("unexpected trailer: '%s'", p + 1); + return 0; +} + +/* + * Remove any trailing slash from $workdir + * before starting to avoid double slashes in symlink targets. + */ +static void add_path(struct strbuf *buf, size_t base_len, const char *path) +{ + strbuf_setlen(buf, base_len); + if (buf->len && buf->buf[buf->len - 1] != '/') + strbuf_addch(buf, '/'); + strbuf_addstr(buf, path); +} + +/* + * Determine whether we can simply reuse the file in the worktree. + */ +static int use_wt_file(const char *workdir, const char *name, + struct object_id *oid) +{ + struct strbuf buf = STRBUF_INIT; + struct stat st; + int use = 0; + + strbuf_addstr(&buf, workdir); + add_path(&buf, buf.len, name); + + if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) { + struct object_id wt_oid; + int fd = open(buf.buf, O_RDONLY); + + if (fd >= 0 && + !index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) { + if (is_null_oid(oid)) { + oidcpy(oid, &wt_oid); + use = 1; + } else if (!oidcmp(oid, &wt_oid)) + use = 1; + } + } + + strbuf_release(&buf); + + return use; +} + +struct working_tree_entry { + struct hashmap_entry entry; + char path[FLEX_ARRAY]; +}; + +static int working_tree_entry_cmp(struct working_tree_entry *a, + struct working_tree_entry *b, void *keydata) +{ + return strcmp(a->path, b->path); +} + +/* + * The `left` and `right` entries hold paths for the symlinks hashmap, + * and a SHA-1 surrounded by brief text for submodules. + */ +struct pair_entry { + struct hashmap_entry entry; + char left[PATH_MAX], right[PATH_MAX]; + const char path[FLEX_ARRAY]; +}; + +static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata) +{ + return strcmp(a->path, b->path); +} + +static void add_left_or_right(struct hashmap *map, const char *path, + const char *content, int is_right) +{ + struct pair_entry *e, *existing; + + FLEX_ALLOC_STR(e, path, path); + hashmap_entry_init(e, strhash(path)); + existing = hashmap_get(map, e, NULL); + if (existing) { + free(e); + e = existing; + } else { + e->left[0] = e->right[0] = '\0'; + hashmap_add(map, e); + } + strlcpy(is_right ? e->right : e->left, content, PATH_MAX); +} + +struct path_entry { + struct hashmap_entry entry; + char path[FLEX_ARRAY]; +}; + +static int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key) +{ + return strcmp(a->path, key ? key : b->path); +} + +static void changed_files(struct hashmap *result, const char *index_path, + const char *workdir) +{ + struct child_process update_index = CHILD_PROCESS_INIT; + struct child_process diff_files = CHILD_PROCESS_INIT; + struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT; + const char *git_dir = absolute_path(get_git_dir()), *env[] = { + NULL, NULL + }; + FILE *fp; + + strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path); + env[0] = index_env.buf; + + argv_array_pushl(&update_index.args, + "--git-dir", git_dir, "--work-tree", workdir, + "update-index", "--really-refresh", "-q", + "--unmerged", NULL); + update_index.no_stdin = 1; + update_index.no_stdout = 1; + update_index.no_stderr = 1; + update_index.git_cmd = 1; + update_index.use_shell = 0; + update_index.clean_on_exit = 1; + update_index.dir = workdir; + update_index.env = env; + /* Ignore any errors of update-index */ + run_command(&update_index); + + argv_array_pushl(&diff_files.args, + "--git-dir", git_dir, "--work-tree", workdir, + "diff-files", "--name-only", "-z", NULL); + diff_files.no_stdin = 1; + diff_files.git_cmd = 1; + diff_files.use_shell = 0; + diff_files.clean_on_exit = 1; + diff_files.out = -1; + diff_files.dir = workdir; + diff_files.env = env; + if (start_command(&diff_files)) + die("could not obtain raw diff"); + fp = xfdopen(diff_files.out, "r"); + while (!strbuf_getline_nul(&buf, fp)) { + struct path_entry *entry; + FLEX_ALLOC_STR(entry, path, buf.buf); + hashmap_entry_init(entry, strhash(buf.buf)); + hashmap_add(result, entry); + } + if (finish_command(&diff_files)) + die("diff-files did not exit properly"); + strbuf_release(&index_env); + strbuf_release(&buf); +} + +static NORETURN void exit_cleanup(const char *tmpdir, int exit_code) +{ + struct strbuf buf = STRBUF_INIT; + strbuf_addstr(&buf, tmpdir); + remove_dir_recursively(&buf, 0); + if (exit_code) + warning(_("failed: %d"), exit_code); + exit(exit_code); +} + +static int ensure_leading_directories(char *path) +{ + switch (safe_create_leading_directories(path)) { + case SCLD_OK: + case SCLD_EXISTS: + return 0; + default: + return error(_("could not create leading directories " + "of '%s'"), path); + } +} + +static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, + int argc, const char **argv) +{ + char tmpdir[PATH_MAX]; + struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT; + struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT; + struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT; + struct strbuf wtdir = STRBUF_INIT; + size_t ldir_len, rdir_len, wtdir_len; + struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1); + const char *workdir, *tmp; + int ret = 0, i; + FILE *fp; + struct hashmap working_tree_dups, submodules, symlinks2; + struct hashmap_iter iter; + struct pair_entry *entry; + enum object_type type; + unsigned long size; + struct index_state wtindex; + struct checkout lstate, rstate; + int rc, flags = RUN_GIT_CMD, err = 0; + struct child_process child = CHILD_PROCESS_INIT; + const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL }; + struct hashmap wt_modified, tmp_modified; + int indices_loaded = 0; + + workdir = get_git_work_tree(); + + /* Setup temp directories */ + tmp = getenv("TMPDIR"); + xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp"); + if (!mkdtemp(tmpdir)) + return error("could not create '%s'", tmpdir); + strbuf_addf(&ldir, "%s/left/", tmpdir); + strbuf_addf(&rdir, "%s/right/", tmpdir); + strbuf_addstr(&wtdir, workdir); + if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1])) + strbuf_addch(&wtdir, '/'); + mkdir(ldir.buf, 0700); + mkdir(rdir.buf, 0700); + + memset(&wtindex, 0, sizeof(wtindex)); + + memset(&lstate, 0, sizeof(lstate)); + lstate.base_dir = ldir.buf; + lstate.base_dir_len = ldir.len; + lstate.force = 1; + memset(&rstate, 0, sizeof(rstate)); + rstate.base_dir = rdir.buf; + rstate.base_dir_len = rdir.len; + rstate.force = 1; + + ldir_len = ldir.len; + rdir_len = rdir.len; + wtdir_len = wtdir.len; + + hashmap_init(&working_tree_dups, + (hashmap_cmp_fn)working_tree_entry_cmp, 0); + hashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0); + hashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0); + + child.no_stdin = 1; + child.git_cmd = 1; + child.use_shell = 0; + child.clean_on_exit = 1; + child.dir = prefix; + child.out = -1; + argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z", + NULL); + for (i = 0; i < argc; i++) + argv_array_push(&child.args, argv[i]); + if (start_command(&child)) + die("could not obtain raw diff"); + fp = xfdopen(child.out, "r"); + + /* Build index info for left and right sides of the diff */ + i = 0; + while (!strbuf_getline_nul(&info, fp)) { + int lmode, rmode; + struct object_id loid, roid; + char status; + const char *src_path, *dst_path; + size_t src_path_len, dst_path_len; + + if (starts_with(info.buf, "::")) + die(N_("combined diff formats('-c' and '--cc') are " + "not supported in\n" + "directory diff mode('-d' and '--dir-diff').")); + + if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid, + &status)) + break; + if (strbuf_getline_nul(&lpath, fp)) + break; + src_path = lpath.buf; + src_path_len = lpath.len; + + i++; + if (status != 'C' && status != 'R') { + dst_path = src_path; + dst_path_len = src_path_len; + } else { + if (strbuf_getline_nul(&rpath, fp)) + break; + dst_path = rpath.buf; + dst_path_len = rpath.len; + } + + if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) { + strbuf_reset(&buf); + strbuf_addf(&buf, "Subproject commit %s", + oid_to_hex(&loid)); + add_left_or_right(&submodules, src_path, buf.buf, 0); + strbuf_reset(&buf); + strbuf_addf(&buf, "Subproject commit %s", + oid_to_hex(&roid)); + if (!oidcmp(&loid, &roid)) + strbuf_addstr(&buf, "-dirty"); + add_left_or_right(&submodules, dst_path, buf.buf, 1); + continue; + } + + if (S_ISLNK(lmode)) { + char *content = read_sha1_file(loid.hash, &type, &size); + add_left_or_right(&symlinks2, src_path, content, 0); + free(content); + } + + if (S_ISLNK(rmode)) { + char *content = read_sha1_file(roid.hash, &type, &size); + add_left_or_right(&symlinks2, dst_path, content, 1); + free(content); + } + + if (lmode && status != 'C') { + ce->ce_mode = lmode; + oidcpy(&ce->oid, &loid); + strcpy(ce->name, src_path); + ce->ce_namelen = src_path_len; + if (checkout_entry(ce, &lstate, NULL)) + return error("could not write '%s'", src_path); + } + + if (rmode) { + struct working_tree_entry *entry; + + /* Avoid duplicate working_tree entries */ + FLEX_ALLOC_STR(entry, path, dst_path); + hashmap_entry_init(entry, strhash(dst_path)); + if (hashmap_get(&working_tree_dups, entry, NULL)) { + free(entry); + continue; + } + hashmap_add(&working_tree_dups, entry); + + if (!use_wt_file(workdir, dst_path, &roid)) { + ce->ce_mode = rmode; + oidcpy(&ce->oid, &roid); + strcpy(ce->name, dst_path); + ce->ce_namelen = dst_path_len; + if (checkout_entry(ce, &rstate, NULL)) + return error("could not write '%s'", + dst_path); + } else if (!is_null_oid(&roid)) { + /* + * Changes in the working tree need special + * treatment since they are not part of the + * index. + */ + struct cache_entry *ce2 = + make_cache_entry(rmode, roid.hash, + dst_path, 0, 0); + + add_index_entry(&wtindex, ce2, + ADD_CACHE_JUST_APPEND); + + add_path(&rdir, rdir_len, dst_path); + if (ensure_leading_directories(rdir.buf)) + return error("could not create " + "directory for '%s'", + dst_path); + add_path(&wtdir, wtdir_len, dst_path); + if (symlinks) { + if (symlink(wtdir.buf, rdir.buf)) { + ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf); + goto finish; + } + } else { + struct stat st; + if (stat(wtdir.buf, &st)) + st.st_mode = 0644; + if (copy_file(rdir.buf, wtdir.buf, + st.st_mode)) { + ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf); + goto finish; + } + } + } + } + } + + if (finish_command(&child)) { + ret = error("error occurred running diff --raw"); + goto finish; + } + + if (!i) + return 0; + + /* + * Changes to submodules require special treatment.This loop writes a + * temporary file to both the left and right directories to show the + * change in the recorded SHA1 for the submodule. + */ + hashmap_iter_init(&submodules, &iter); + while ((entry = hashmap_iter_next(&iter))) { + if (*entry->left) { + add_path(&ldir, ldir_len, entry->path); + ensure_leading_directories(ldir.buf); + write_file(ldir.buf, "%s", entry->left); + } + if (*entry->right) { + add_path(&rdir, rdir_len, entry->path); + ensure_leading_directories(rdir.buf); + write_file(rdir.buf, "%s", entry->right); + } + } + + /* + * Symbolic links require special treatment.The standard "git diff" + * shows only the link itself, not the contents of the link target. + * This loop replicates that behavior. + */ + hashmap_iter_init(&symlinks2, &iter); + while ((entry = hashmap_iter_next(&iter))) { + if (*entry->left) { + add_path(&ldir, ldir_len, entry->path); + ensure_leading_directories(ldir.buf); + write_file(ldir.buf, "%s", entry->left); + } + if (*entry->right) { + add_path(&rdir, rdir_len, entry->path); + ensure_leading_directories(rdir.buf); + write_file(rdir.buf, "%s", entry->right); + } + } + + strbuf_release(&buf); + + strbuf_setlen(&ldir, ldir_len); + helper_argv[1] = ldir.buf; + strbuf_setlen(&rdir, rdir_len); + helper_argv[2] = rdir.buf; + + if (extcmd) { + helper_argv[0] = extcmd; + flags = 0; + } else + setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1); + rc = run_command_v_opt(helper_argv, flags); + + /* + * If the diff includes working copy files and those + * files were modified during the diff, then the changes + * should be copied back to the working tree. + * Do not copy back files when symlinks are used and the + * external tool did not replace the original link with a file. + * + * These hashes are loaded lazily since they aren't needed + * in the common case of --symlinks and the difftool updating + * files through the symlink. + */ + hashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp, + wtindex.cache_nr); + hashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp, + wtindex.cache_nr); + + for (i = 0; i < wtindex.cache_nr; i++) { + struct hashmap_entry dummy; + const char *name = wtindex.cache[i]->name; + struct stat st; + + add_path(&rdir, rdir_len, name); + if (lstat(rdir.buf, &st)) + continue; + + if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode)) + continue; + + if (!indices_loaded) { + static struct lock_file lock; + strbuf_reset(&buf); + strbuf_addf(&buf, "%s/wtindex", tmpdir); + if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 || + write_locked_index(&wtindex, &lock, COMMIT_LOCK)) { + ret = error("could not write %s", buf.buf); + rollback_lock_file(&lock); + goto finish; + } + changed_files(&wt_modified, buf.buf, workdir); + strbuf_setlen(&rdir, rdir_len); + changed_files(&tmp_modified, buf.buf, rdir.buf); + add_path(&rdir, rdir_len, name); + indices_loaded = 1; + } + + hashmap_entry_init(&dummy, strhash(name)); + if (hashmap_get(&tmp_modified, &dummy, name)) { + add_path(&wtdir, wtdir_len, name); + if (hashmap_get(&wt_modified, &dummy, name)) { + warning(_("both files modified: '%s' and '%s'."), + wtdir.buf, rdir.buf); + warning(_("working tree file has been left.")); + warning(""); + err = 1; + } else if (unlink(wtdir.buf) || + copy_file(wtdir.buf, rdir.buf, st.st_mode)) + warning_errno(_("could not copy '%s' to '%s'"), + rdir.buf, wtdir.buf); + } + } + + if (err) { + warning(_("temporary files exist in '%s'."), tmpdir); + warning(_("you may want to cleanup or recover these.")); + exit(1); + } else + exit_cleanup(tmpdir, rc); + +finish: + free(ce); + strbuf_release(&ldir); + strbuf_release(&rdir); + strbuf_release(&wtdir); + strbuf_release(&buf); + + return ret; +} + +static int run_file_diff(int prompt, const char *prefix, + int argc, const char **argv) +{ + struct argv_array args = ARGV_ARRAY_INIT; + const char *env[] = { + "GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL, + NULL + }; + int ret = 0, i; + + if (prompt > 0) + env[2] = "GIT_DIFFTOOL_PROMPT=true"; + else if (!prompt) + env[2] = "GIT_DIFFTOOL_NO_PROMPT=true"; + + + argv_array_push(&args, "diff"); + for (i = 0; i < argc; i++) + argv_array_push(&args, argv[i]); + ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env); + exit(ret); +} /* * NEEDSWORK: this function can go once the legacy-difftool Perl script is @@ -41,6 +642,35 @@ static int use_builtin_difftool(void) { int cmd_difftool(int argc, const char **argv, const char *prefix) { + int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0, + tool_help = 0; + static char *difftool_cmd = NULL, *extcmd = NULL; + struct option builtin_difftool_options[] = { + OPT_BOOL('g', "gui", &use_gui_tool, + N_("use `diff.guitool` instead of `diff.tool`")), + OPT_BOOL('d', "dir-diff", &dir_diff, + N_("perform a full-directory diff")), + { OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL, + N_("do not prompt before launching a diff tool"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0}, + { OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL, + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN, + NULL, 1 }, + OPT_BOOL(0, "symlinks", &symlinks, + N_("use symlinks in dir-diff mode")), + OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"), + N_("use the specified diff tool")), + OPT_BOOL(0, "tool-help", &tool_help, + N_("print a list of diff tools that may be used with " + "`--tool`")), + OPT_BOOL(0, "trust-exit-code", &trust_exit_code, + N_("make 'git-difftool' exit when an invoked diff " + "tool returns a non - zero exit code")), + OPT_STRING('x', "extcmd", &extcmd, N_("<command>"), + N_("specify a custom command for viewing diffs")), + OPT_END() + }; + /* * NEEDSWORK: Once the builtin difftool has been tested enough * and git-legacy-difftool.perl is retired to contrib/, this preamble @@ -58,6 +688,46 @@ int cmd_difftool(int argc, const char **argv, const char *prefix) prefix = setup_git_directory(); trace_repo_setup(prefix); setup_work_tree(); + /* NEEDSWORK: once we no longer spawn anything, remove this */ + setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1); + setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1); + + git_config(difftool_config, NULL); + symlinks = has_symlinks; + + argc = parse_options(argc, argv, prefix, builtin_difftool_options, + builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_DASHDASH); - die("TODO"); + if (tool_help) + return print_tool_help(); + + if (use_gui_tool && diff_gui_tool && *diff_gui_tool) + setenv("GIT_DIFF_TOOL", diff_gui_tool, 1); + else if (difftool_cmd) { + if (*difftool_cmd) + setenv("GIT_DIFF_TOOL", difftool_cmd, 1); + else + die(_("no <tool> given for --tool=<tool>")); + } + + if (extcmd) { + if (*extcmd) + setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1); + else + die(_("no <cmd> given for --extcmd=<cmd>")); + } + + setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE", + trust_exit_code ? "true" : "false", 1); + + /* + * In directory diff mode, 'git-difftool--helper' is called once + * to compare the a / b directories. In file diff mode, 'git diff' + * will invoke a separate instance of 'git-difftool--helper' for + * each file that changed. + */ + if (dir_diff) + return run_dir_diff(extcmd, symlinks, prefix, argc, argv); + return run_file_diff(prompt, prefix, argc, argv); } -- 2.11.0.windows.3 ^ permalink raw reply related [flat|nested] 86+ messages in thread
* [PATCH v5 3/3] Retire the scripted difftool 2017-01-17 15:54 ` [PATCH v5 0/3] Turn the difftool into a builtin Johannes Schindelin 2017-01-17 15:54 ` [PATCH v5 1/3] difftool: add a skeleton for the upcoming builtin Johannes Schindelin 2017-01-17 15:55 ` [PATCH v5 2/3] difftool: implement the functionality in the builtin Johannes Schindelin @ 2017-01-17 15:55 ` Johannes Schindelin 2017-01-17 21:46 ` Junio C Hamano 2017-01-17 21:31 ` [PATCH v5 0/3] Turn the difftool into a builtin Junio C Hamano 2017-01-19 20:30 ` [PATCH v6 " Johannes Schindelin 4 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2017-01-17 15:55 UTC (permalink / raw) To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra It served its purpose, but now we have a builtin difftool. Time for the Perl script to enjoy Florida. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- .gitignore | 1 - Makefile | 1 - builtin/difftool.c | 41 ---------- .../examples/git-difftool.perl | 0 git.c | 7 +- t/t7800-difftool.sh | 94 +++++++++++----------- 6 files changed, 47 insertions(+), 97 deletions(-) rename git-legacy-difftool.perl => contrib/examples/git-difftool.perl (100%) diff --git a/.gitignore b/.gitignore index 5555ae025b..6722f78f9a 100644 --- a/.gitignore +++ b/.gitignore @@ -76,7 +76,6 @@ /git-init-db /git-interpret-trailers /git-instaweb -/git-legacy-difftool /git-log /git-ls-files /git-ls-remote diff --git a/Makefile b/Makefile index 8cf5bef034..e9aa6ae57c 100644 --- a/Makefile +++ b/Makefile @@ -522,7 +522,6 @@ SCRIPT_LIB += git-sh-setup SCRIPT_LIB += git-sh-i18n SCRIPT_PERL += git-add--interactive.perl -SCRIPT_PERL += git-legacy-difftool.perl SCRIPT_PERL += git-archimport.perl SCRIPT_PERL += git-cvsexportcommit.perl SCRIPT_PERL += git-cvsimport.perl diff --git a/builtin/difftool.c b/builtin/difftool.c index 2115e548a5..42ad9e804a 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -616,30 +616,6 @@ static int run_file_diff(int prompt, const char *prefix, exit(ret); } -/* - * NEEDSWORK: this function can go once the legacy-difftool Perl script is - * retired. - * - * We intentionally avoid reading the config directly here, to avoid messing up - * the GIT_* environment variables when we need to fall back to exec()ing the - * Perl script. - */ -static int use_builtin_difftool(void) { - struct child_process cp = CHILD_PROCESS_INIT; - struct strbuf out = STRBUF_INIT; - int ret; - - argv_array_pushl(&cp.args, - "config", "--bool", "difftool.usebuiltin", NULL); - cp.git_cmd = 1; - if (capture_command(&cp, &out, 6)) - return 0; - strbuf_trim(&out); - ret = !strcmp("true", out.buf); - strbuf_release(&out); - return ret; -} - int cmd_difftool(int argc, const char **argv, const char *prefix) { int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0, @@ -671,23 +647,6 @@ int cmd_difftool(int argc, const char **argv, const char *prefix) OPT_END() }; - /* - * NEEDSWORK: Once the builtin difftool has been tested enough - * and git-legacy-difftool.perl is retired to contrib/, this preamble - * can be removed. - */ - if (!use_builtin_difftool()) { - const char *path = mkpath("%s/git-legacy-difftool", - git_exec_path()); - - if (sane_execvp(path, (char **)argv) < 0) - die_errno("could not exec %s", path); - - return 0; - } - prefix = setup_git_directory(); - trace_repo_setup(prefix); - setup_work_tree(); /* NEEDSWORK: once we no longer spawn anything, remove this */ setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1); setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1); diff --git a/git-legacy-difftool.perl b/contrib/examples/git-difftool.perl similarity index 100% rename from git-legacy-difftool.perl rename to contrib/examples/git-difftool.perl diff --git a/git.c b/git.c index c58181e5ef..bd4d668a21 100644 --- a/git.c +++ b/git.c @@ -424,12 +424,7 @@ static struct cmd_struct commands[] = { { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE }, { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, - /* - * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in - * builtin/difftool.c has been removed, this entry should be changed to - * RUN_SETUP | NEED_WORK_TREE - */ - { "difftool", cmd_difftool }, + { "difftool", cmd_difftool, RUN_SETUP | NEED_WORK_TREE }, { "fast-export", cmd_fast_export, RUN_SETUP }, { "fetch", cmd_fetch, RUN_SETUP }, { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index e94910c563..aa0ef02597 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -23,10 +23,8 @@ prompt_given () test "$prompt" = "Launch 'test-tool' [Y/n]? branch" } -# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired. - # Create a file on master and change it on branch -test_expect_success PERL 'setup' ' +test_expect_success 'setup' ' echo master >file && git add file && git commit -m "added file" && @@ -38,7 +36,7 @@ test_expect_success PERL 'setup' ' ' # Configure a custom difftool.<tool>.cmd and use it -test_expect_success PERL 'custom commands' ' +test_expect_success 'custom commands' ' difftool_test_setup && test_config difftool.test-tool.cmd "cat \"\$REMOTE\"" && echo master >expect && @@ -51,21 +49,21 @@ test_expect_success PERL 'custom commands' ' test_cmp expect actual ' -test_expect_success PERL 'custom tool commands override built-ins' ' +test_expect_success 'custom tool commands override built-ins' ' test_config difftool.vimdiff.cmd "cat \"\$REMOTE\"" && echo master >expect && git difftool --tool vimdiff --no-prompt branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool ignores bad --tool values' ' +test_expect_success 'difftool ignores bad --tool values' ' : >expect && test_must_fail \ git difftool --no-prompt --tool=bad-tool branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool forwards arguments to diff' ' +test_expect_success 'difftool forwards arguments to diff' ' difftool_test_setup && >for-diff && git add for-diff && @@ -78,40 +76,40 @@ test_expect_success PERL 'difftool forwards arguments to diff' ' rm for-diff ' -test_expect_success PERL 'difftool ignores exit code' ' +test_expect_success 'difftool ignores exit code' ' test_config difftool.error.cmd false && git difftool -y -t error branch ' -test_expect_success PERL 'difftool forwards exit code with --trust-exit-code' ' +test_expect_success 'difftool forwards exit code with --trust-exit-code' ' test_config difftool.error.cmd false && test_must_fail git difftool -y --trust-exit-code -t error branch ' -test_expect_success PERL 'difftool forwards exit code with --trust-exit-code for built-ins' ' +test_expect_success 'difftool forwards exit code with --trust-exit-code for built-ins' ' test_config difftool.vimdiff.path false && test_must_fail git difftool -y --trust-exit-code -t vimdiff branch ' -test_expect_success PERL 'difftool honors difftool.trustExitCode = true' ' +test_expect_success 'difftool honors difftool.trustExitCode = true' ' test_config difftool.error.cmd false && test_config difftool.trustExitCode true && test_must_fail git difftool -y -t error branch ' -test_expect_success PERL 'difftool honors difftool.trustExitCode = false' ' +test_expect_success 'difftool honors difftool.trustExitCode = false' ' test_config difftool.error.cmd false && test_config difftool.trustExitCode false && git difftool -y -t error branch ' -test_expect_success PERL 'difftool ignores exit code with --no-trust-exit-code' ' +test_expect_success 'difftool ignores exit code with --no-trust-exit-code' ' test_config difftool.error.cmd false && test_config difftool.trustExitCode true && git difftool -y --no-trust-exit-code -t error branch ' -test_expect_success PERL 'difftool stops on error with --trust-exit-code' ' +test_expect_success 'difftool stops on error with --trust-exit-code' ' test_when_finished "rm -f for-diff .git/fail-right-file" && test_when_finished "git reset -- for-diff" && write_script .git/fail-right-file <<-\EOF && @@ -126,13 +124,13 @@ test_expect_success PERL 'difftool stops on error with --trust-exit-code' ' test_cmp expect actual ' -test_expect_success PERL 'difftool honors exit status if command not found' ' +test_expect_success 'difftool honors exit status if command not found' ' test_config difftool.nonexistent.cmd i-dont-exist && test_config difftool.trustExitCode false && test_must_fail git difftool -y -t nonexistent branch ' -test_expect_success PERL 'difftool honors --gui' ' +test_expect_success 'difftool honors --gui' ' difftool_test_setup && test_config merge.tool bogus-tool && test_config diff.tool bogus-tool && @@ -143,7 +141,7 @@ test_expect_success PERL 'difftool honors --gui' ' test_cmp expect actual ' -test_expect_success PERL 'difftool --gui last setting wins' ' +test_expect_success 'difftool --gui last setting wins' ' difftool_test_setup && : >expect && git difftool --no-prompt --gui --no-gui >actual && @@ -157,7 +155,7 @@ test_expect_success PERL 'difftool --gui last setting wins' ' test_cmp expect actual ' -test_expect_success PERL 'difftool --gui works without configured diff.guitool' ' +test_expect_success 'difftool --gui works without configured diff.guitool' ' difftool_test_setup && echo branch >expect && git difftool --no-prompt --gui branch >actual && @@ -165,7 +163,7 @@ test_expect_success PERL 'difftool --gui works without configured diff.guitool' ' # Specify the diff tool using $GIT_DIFF_TOOL -test_expect_success PERL 'GIT_DIFF_TOOL variable' ' +test_expect_success 'GIT_DIFF_TOOL variable' ' difftool_test_setup && git config --unset diff.tool && echo branch >expect && @@ -175,7 +173,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL variable' ' # Test the $GIT_*_TOOL variables and ensure # that $GIT_DIFF_TOOL always wins unless --tool is specified -test_expect_success PERL 'GIT_DIFF_TOOL overrides' ' +test_expect_success 'GIT_DIFF_TOOL overrides' ' difftool_test_setup && test_config diff.tool bogus-tool && test_config merge.tool bogus-tool && @@ -193,7 +191,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL overrides' ' # Test that we don't have to pass --no-prompt to difftool # when $GIT_DIFFTOOL_NO_PROMPT is true -test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' ' +test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' ' difftool_test_setup && echo branch >expect && GIT_DIFFTOOL_NO_PROMPT=true git difftool branch >actual && @@ -202,7 +200,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' ' # git-difftool supports the difftool.prompt variable. # Test that GIT_DIFFTOOL_PROMPT can override difftool.prompt = false -test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' ' +test_expect_success 'GIT_DIFFTOOL_PROMPT variable' ' difftool_test_setup && test_config difftool.prompt false && echo >input && @@ -212,7 +210,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' ' ' # Test that we don't have to pass --no-prompt when difftool.prompt is false -test_expect_success PERL 'difftool.prompt config variable is false' ' +test_expect_success 'difftool.prompt config variable is false' ' difftool_test_setup && test_config difftool.prompt false && echo branch >expect && @@ -221,7 +219,7 @@ test_expect_success PERL 'difftool.prompt config variable is false' ' ' # Test that we don't have to pass --no-prompt when mergetool.prompt is false -test_expect_success PERL 'difftool merge.prompt = false' ' +test_expect_success 'difftool merge.prompt = false' ' difftool_test_setup && test_might_fail git config --unset difftool.prompt && test_config mergetool.prompt false && @@ -231,7 +229,7 @@ test_expect_success PERL 'difftool merge.prompt = false' ' ' # Test that the -y flag can override difftool.prompt = true -test_expect_success PERL 'difftool.prompt can overridden with -y' ' +test_expect_success 'difftool.prompt can overridden with -y' ' difftool_test_setup && test_config difftool.prompt true && echo branch >expect && @@ -240,7 +238,7 @@ test_expect_success PERL 'difftool.prompt can overridden with -y' ' ' # Test that the --prompt flag can override difftool.prompt = false -test_expect_success PERL 'difftool.prompt can overridden with --prompt' ' +test_expect_success 'difftool.prompt can overridden with --prompt' ' difftool_test_setup && test_config difftool.prompt false && echo >input && @@ -250,7 +248,7 @@ test_expect_success PERL 'difftool.prompt can overridden with --prompt' ' ' # Test that the last flag passed on the command-line wins -test_expect_success PERL 'difftool last flag wins' ' +test_expect_success 'difftool last flag wins' ' difftool_test_setup && echo branch >expect && git difftool --prompt --no-prompt branch >actual && @@ -263,7 +261,7 @@ test_expect_success PERL 'difftool last flag wins' ' # git-difftool falls back to git-mergetool config variables # so test that behavior here -test_expect_success PERL 'difftool + mergetool config variables' ' +test_expect_success 'difftool + mergetool config variables' ' test_config merge.tool test-tool && test_config mergetool.test-tool.cmd "cat \$LOCAL" && echo branch >expect && @@ -277,49 +275,49 @@ test_expect_success PERL 'difftool + mergetool config variables' ' test_cmp expect actual ' -test_expect_success PERL 'difftool.<tool>.path' ' +test_expect_success 'difftool.<tool>.path' ' test_config difftool.tkdiff.path echo && git difftool --tool=tkdiff --no-prompt branch >output && lines=$(grep file output | wc -l) && test "$lines" -eq 1 ' -test_expect_success PERL 'difftool --extcmd=cat' ' +test_expect_success 'difftool --extcmd=cat' ' echo branch >expect && echo master >>expect && git difftool --no-prompt --extcmd=cat branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd cat' ' +test_expect_success 'difftool --extcmd cat' ' echo branch >expect && echo master >>expect && git difftool --no-prompt --extcmd=cat branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool -x cat' ' +test_expect_success 'difftool -x cat' ' echo branch >expect && echo master >>expect && git difftool --no-prompt -x cat branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd echo arg1' ' +test_expect_success 'difftool --extcmd echo arg1' ' echo file >expect && git difftool --no-prompt \ --extcmd sh\ -c\ \"echo\ \$1\" branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd cat arg1' ' +test_expect_success 'difftool --extcmd cat arg1' ' echo master >expect && git difftool --no-prompt \ --extcmd sh\ -c\ \"cat\ \$1\" branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd cat arg2' ' +test_expect_success 'difftool --extcmd cat arg2' ' echo branch >expect && git difftool --no-prompt \ --extcmd sh\ -c\ \"cat\ \$2\" branch >actual && @@ -327,7 +325,7 @@ test_expect_success PERL 'difftool --extcmd cat arg2' ' ' # Create a second file on master and a different version on branch -test_expect_success PERL 'setup with 2 files different' ' +test_expect_success 'setup with 2 files different' ' echo m2 >file2 && git add file2 && git commit -m "added file2" && @@ -339,7 +337,7 @@ test_expect_success PERL 'setup with 2 files different' ' git checkout master ' -test_expect_success PERL 'say no to the first file' ' +test_expect_success 'say no to the first file' ' (echo n && echo) >input && git difftool -x cat branch <input >output && grep m2 output && @@ -348,7 +346,7 @@ test_expect_success PERL 'say no to the first file' ' ! grep branch output ' -test_expect_success PERL 'say no to the second file' ' +test_expect_success 'say no to the second file' ' (echo && echo n) >input && git difftool -x cat branch <input >output && grep master output && @@ -357,7 +355,7 @@ test_expect_success PERL 'say no to the second file' ' ! grep br2 output ' -test_expect_success PERL 'ending prompt input with EOF' ' +test_expect_success 'ending prompt input with EOF' ' git difftool -x cat branch </dev/null >output && ! grep master output && ! grep branch output && @@ -365,12 +363,12 @@ test_expect_success PERL 'ending prompt input with EOF' ' ! grep br2 output ' -test_expect_success PERL 'difftool --tool-help' ' +test_expect_success 'difftool --tool-help' ' git difftool --tool-help >output && grep tool output ' -test_expect_success PERL 'setup change in subdirectory' ' +test_expect_success 'setup change in subdirectory' ' git checkout master && mkdir sub && echo master >sub/sub && @@ -384,11 +382,11 @@ test_expect_success PERL 'setup change in subdirectory' ' ' run_dir_diff_test () { - test_expect_success PERL "$1 --no-symlinks" " + test_expect_success "$1 --no-symlinks" " symlinks=--no-symlinks && $2 " - test_expect_success PERL,SYMLINKS "$1 --symlinks" " + test_expect_success SYMLINKS "$1 --symlinks" " symlinks=--symlinks && $2 " @@ -510,7 +508,7 @@ do done >actual EOF -test_expect_success PERL,SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' ' +test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' ' cat >expect <<-EOF && file $PWD/file @@ -547,7 +545,7 @@ write_script modify-file <<\EOF echo "new content" >file EOF -test_expect_success PERL 'difftool --no-symlinks does not overwrite working tree file ' ' +test_expect_success 'difftool --no-symlinks does not overwrite working tree file ' ' echo "orig content" >file && git difftool --dir-diff --no-symlinks --extcmd "$PWD/modify-file" branch && echo "new content" >expect && @@ -560,7 +558,7 @@ echo "tmp content" >"$2/file" && echo "$2" >tmpdir EOF -test_expect_success PERL 'difftool --no-symlinks detects conflict ' ' +test_expect_success 'difftool --no-symlinks detects conflict ' ' ( TMPDIR=$TRASH_DIRECTORY && export TMPDIR && @@ -573,7 +571,7 @@ test_expect_success PERL 'difftool --no-symlinks detects conflict ' ' ) ' -test_expect_success PERL 'difftool properly honors gitlink and core.worktree' ' +test_expect_success 'difftool properly honors gitlink and core.worktree' ' git submodule add ./. submod/ule && test_config -C submod/ule diff.tool checktrees && test_config -C submod/ule difftool.checktrees.cmd '\'' @@ -587,7 +585,7 @@ test_expect_success PERL 'difftool properly honors gitlink and core.worktree' ' ) ' -test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' ' +test_expect_success SYMLINKS 'difftool --dir-diff symlinked directories' ' git init dirlinks && ( cd dirlinks && -- 2.11.0.windows.3 ^ permalink raw reply related [flat|nested] 86+ messages in thread
* Re: [PATCH v5 3/3] Retire the scripted difftool 2017-01-17 15:55 ` [PATCH v5 3/3] Retire the scripted difftool Johannes Schindelin @ 2017-01-17 21:46 ` Junio C Hamano 2017-01-18 12:33 ` Johannes Schindelin 0 siblings, 1 reply; 86+ messages in thread From: Junio C Hamano @ 2017-01-17 21:46 UTC (permalink / raw) To: Johannes Schindelin; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra Johannes Schindelin <johannes.schindelin@gmx.de> writes: > It served its purpose, but now we have a builtin difftool. Time for the > Perl script to enjoy Florida. > > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> > --- The endgame makes a lot of sense. Both in the cover letter and in the previous patch you talk about having both in the released version, so do you want this step to proceed slower than the other two? I.e. merge all three to 'next' but graduate only the first two to 'master' and after a while make this last step graduate? ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v5 3/3] Retire the scripted difftool 2017-01-17 21:46 ` Junio C Hamano @ 2017-01-18 12:33 ` Johannes Schindelin 2017-01-18 19:15 ` Junio C Hamano 0 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2017-01-18 12:33 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra Hi Junio, On Tue, 17 Jan 2017, Junio C Hamano wrote: > Johannes Schindelin <johannes.schindelin@gmx.de> writes: > > > It served its purpose, but now we have a builtin difftool. Time for the > > Perl script to enjoy Florida. > > > > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> > > --- > > The endgame makes a lot of sense. Both in the cover letter and in > the previous patch you talk about having both in the released > version, so do you want this step to proceed slower than the other > two? I did proceed that slowly. Already three Git for Windows versions have been released with both. But I submitted this iteration with this patch, so my intent is clearly to retire the Perl script. Ciao, Johannes ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v5 3/3] Retire the scripted difftool 2017-01-18 12:33 ` Johannes Schindelin @ 2017-01-18 19:15 ` Junio C Hamano 2017-01-19 16:30 ` Johannes Schindelin 0 siblings, 1 reply; 86+ messages in thread From: Junio C Hamano @ 2017-01-18 19:15 UTC (permalink / raw) To: Johannes Schindelin; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra Johannes Schindelin <Johannes.Schindelin@gmx.de> writes: > Hi Junio, > > On Tue, 17 Jan 2017, Junio C Hamano wrote: > >> Johannes Schindelin <johannes.schindelin@gmx.de> writes: >> >> > It served its purpose, but now we have a builtin difftool. Time for the >> > Perl script to enjoy Florida. >> > >> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> >> > --- >> >> The endgame makes a lot of sense. Both in the cover letter and in >> the previous patch you talk about having both in the released >> version, so do you want this step to proceed slower than the other >> two? > > I did proceed that slowly. Already three Git for Windows versions have > been released with both. > > But I submitted this iteration with this patch, so my intent is clearly to > retire the Perl script. Ok, I was mostly reacting to 2/3 while I am reading it: The reason: this new, experimental, builtin difftool will be shipped as part of Git for Windows v2.11.0, to allow for easier large-scale testing, but of course as an opt-in feature. as there is no longer an opportunity to participate in this opt-in testing, unless 3/3 is special cased and delayed. ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v5 3/3] Retire the scripted difftool 2017-01-18 19:15 ` Junio C Hamano @ 2017-01-19 16:30 ` Johannes Schindelin 2017-01-19 17:56 ` Junio C Hamano 0 siblings, 1 reply; 86+ messages in thread From: Johannes Schindelin @ 2017-01-19 16:30 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra Hi Junio, On Wed, 18 Jan 2017, Junio C Hamano wrote: > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes: > > > Hi Junio, > > > > On Tue, 17 Jan 2017, Junio C Hamano wrote: > > > >> Johannes Schindelin <johannes.schindelin@gmx.de> writes: > >> > >> > It served its purpose, but now we have a builtin difftool. Time for the > >> > Perl script to enjoy Florida. > >> > > >> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> > >> > --- > >> > >> The endgame makes a lot of sense. Both in the cover letter and in > >> the previous patch you talk about having both in the released > >> version, so do you want this step to proceed slower than the other > >> two? > > > > I did proceed that slowly. Already three Git for Windows versions have > > been released with both. > > > > But I submitted this iteration with this patch, so my intent is clearly to > > retire the Perl script. > > Ok, I was mostly reacting to 2/3 while I am reading it: > > The reason: this new, experimental, builtin difftool will be shipped as > part of Git for Windows v2.11.0, to allow for easier large-scale > testing, but of course as an opt-in feature. > > as there is no longer an opportunity to participate in this opt-in > testing, unless 3/3 is special cased and delayed. Yep, as Git for Windows v2.11.0 is yesteryear's news, it was probably obvious to you that I simply failed to spot and fix that oversight. Ciao, Johannes ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v5 3/3] Retire the scripted difftool 2017-01-19 16:30 ` Johannes Schindelin @ 2017-01-19 17:56 ` Junio C Hamano 2017-01-19 20:32 ` Johannes Schindelin 0 siblings, 1 reply; 86+ messages in thread From: Junio C Hamano @ 2017-01-19 17:56 UTC (permalink / raw) To: Johannes Schindelin; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra Johannes Schindelin <Johannes.Schindelin@gmx.de> writes: >> Ok, I was mostly reacting to 2/3 while I am reading it: >> >> The reason: this new, experimental, builtin difftool will be shipped as >> part of Git for Windows v2.11.0, to allow for easier large-scale >> testing, but of course as an opt-in feature. >> >> as there is no longer an opportunity to participate in this opt-in >> testing, unless 3/3 is special cased and delayed. > > Yep, as Git for Windows v2.11.0 is yesteryear's news, it was probably > obvious to you that I simply failed to spot and fix that oversight. OK, if you want to tweak log message of either 2/3 or 3/3 to correct, there is still time, as they are still outside 'next'. I am hoping we can merge it and others to 'next' by the end of the week, though. ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v5 3/3] Retire the scripted difftool 2017-01-19 17:56 ` Junio C Hamano @ 2017-01-19 20:32 ` Johannes Schindelin 0 siblings, 0 replies; 86+ messages in thread From: Johannes Schindelin @ 2017-01-19 20:32 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra Hi Junio, On Thu, 19 Jan 2017, Junio C Hamano wrote: > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes: > > > Yep, as Git for Windows v2.11.0 is yesteryear's news, it was probably > > obvious to you that I simply failed to spot and fix that oversight. > > OK, if you want to tweak log message of either 2/3 or 3/3 to correct, > there is still time, as they are still outside 'next'. Sent out v6 with the commit message reworded. Ciao, Johannes ^ permalink raw reply [flat|nested] 86+ messages in thread
* Re: [PATCH v5 0/3] Turn the difftool into a builtin 2017-01-17 15:54 ` [PATCH v5 0/3] Turn the difftool into a builtin Johannes Schindelin ` (2 preceding siblings ...) 2017-01-17 15:55 ` [PATCH v5 3/3] Retire the scripted difftool Johannes Schindelin @ 2017-01-17 21:31 ` Junio C Hamano 2017-01-19 20:30 ` [PATCH v6 " Johannes Schindelin 4 siblings, 0 replies; 86+ messages in thread From: Junio C Hamano @ 2017-01-17 21:31 UTC (permalink / raw) To: Johannes Schindelin; +Cc: git, David Aguilar, Dennis Kaarsemaker, Paul Sbarra Johannes Schindelin <johannes.schindelin@gmx.de> writes: > - replaced the cross-validation with the Perl script by a patch that > retires the Perl script instead. Yup. That makes things a lot simpler. While we try to be careful during major rewrite, we usually do not go extra careful to leave two competing implementations in-tree. Will replace js/difftool-builtin topic. Thanks. ^ permalink raw reply [flat|nested] 86+ messages in thread
* [PATCH v6 0/3] Turn the difftool into a builtin 2017-01-17 15:54 ` [PATCH v5 0/3] Turn the difftool into a builtin Johannes Schindelin ` (3 preceding siblings ...) 2017-01-17 21:31 ` [PATCH v5 0/3] Turn the difftool into a builtin Junio C Hamano @ 2017-01-19 20:30 ` Johannes Schindelin 2017-01-19 20:30 ` [PATCH v6 1/3] difftool: add a skeleton for the upcoming builtin Johannes Schindelin ` (2 more replies) 4 siblings, 3 replies; 86+ messages in thread From: Johannes Schindelin @ 2017-01-19 20:30 UTC (permalink / raw) To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra This patch series converts the difftool from a Perl script into a builtin, for three reasons: 1. Perl is really not native on Windows. Not only is there a performance penalty to be paid just for running Perl scripts, we also have to deal with the fact that users may have different Perl installations, with different options, and some other Perl installation may decide to set PERL5LIB globally, wreaking havoc with Git for Windows' Perl (which we have to use because almost all other Perl distributions lack the Subversion bindings we need for `git svn`). 2. As the Perl script uses Unix-y paths that are not native to Windows, the Perl interpreter has to go through a POSIX emulation layer (the MSYS2 runtime). This means that paths have to be converted from Unix-y paths to Windows-y paths (and vice versa) whenever crossing the POSIX emulation barrier, leading to quite possibly surprising path translation errors. 3. Perl makes for a rather large reason that Git for Windows' installer weighs in with >30MB. While one Perl script less does not relieve us of that burden, it is one step in the right direction. Changes since v5: - reworded the commit message of 2/3 to account for the change in v4 where we no longer keep both scripted and builtin difftool working (with the switch difftool.useBuiltin deciding which one is used). Johannes Schindelin (3): difftool: add a skeleton for the upcoming builtin difftool: implement the functionality in the builtin Retire the scripted difftool Makefile | 2 +- builtin.h | 1 + builtin/difftool.c | 692 +++++++++++++++++++++ .../examples/git-difftool.perl | 0 git.c | 1 + t/t7800-difftool.sh | 92 +-- 6 files changed, 741 insertions(+), 47 deletions(-) create mode 100644 builtin/difftool.c rename git-difftool.perl => contrib/examples/git-difftool.perl (100%) base-commit: ffac48d093d4b518a0cc0e8bf1b7cb53e0c3d7a2 Published-As: https://github.com/dscho/git/releases/tag/builtin-difftool-v6 Fetch-It-Via: git fetch https://github.com/dscho/git builtin-difftool-v6 -- 2.11.0.windows.3 ^ permalink raw reply [flat|nested] 86+ messages in thread
* [PATCH v6 1/3] difftool: add a skeleton for the upcoming builtin 2017-01-19 20:30 ` [PATCH v6 " Johannes Schindelin @ 2017-01-19 20:30 ` Johannes Schindelin 2017-01-19 20:30 ` [PATCH v6 2/3] difftool: implement the functionality in the builtin Johannes Schindelin 2017-01-19 20:30 ` [PATCH v6 3/3] Retire the scripted difftool Johannes Schindelin 2 siblings, 0 replies; 86+ messages in thread From: Johannes Schindelin @ 2017-01-19 20:30 UTC (permalink / raw) To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra This adds a builtin difftool that still falls back to the legacy Perl version, which has been renamed to `legacy-difftool`. The idea is that the new, experimental, builtin difftool immediately hands off to the legacy difftool for now, unless the config variable difftool.useBuiltin is set to true. This feature flag will be used in the upcoming Git for Windows v2.11.0 release, to allow early testers to opt-in to use the builtin difftool and flesh out any bugs. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- .gitignore | 1 + Makefile | 3 +- builtin.h | 1 + builtin/difftool.c | 63 +++++++++++++++++++++++++++ git-difftool.perl => git-legacy-difftool.perl | 0 git.c | 6 +++ t/t7800-difftool.sh | 2 + 7 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 builtin/difftool.c rename git-difftool.perl => git-legacy-difftool.perl (100%) diff --git a/.gitignore b/.gitignore index 6722f78f9a..5555ae025b 100644 --- a/.gitignore +++ b/.gitignore @@ -76,6 +76,7 @@ /git-init-db /git-interpret-trailers /git-instaweb +/git-legacy-difftool /git-log /git-ls-files /git-ls-remote diff --git a/Makefile b/Makefile index d861bd9985..8cf5bef034 100644 --- a/Makefile +++ b/Makefile @@ -522,7 +522,7 @@ SCRIPT_LIB += git-sh-setup SCRIPT_LIB += git-sh-i18n SCRIPT_PERL += git-add--interactive.perl -SCRIPT_PERL += git-difftool.perl +SCRIPT_PERL += git-legacy-difftool.perl SCRIPT_PERL += git-archimport.perl SCRIPT_PERL += git-cvsexportcommit.perl SCRIPT_PERL += git-cvsimport.perl @@ -883,6 +883,7 @@ BUILTIN_OBJS += builtin/diff-files.o BUILTIN_OBJS += builtin/diff-index.o BUILTIN_OBJS += builtin/diff-tree.o BUILTIN_OBJS += builtin/diff.o +BUILTIN_OBJS += builtin/difftool.o BUILTIN_OBJS += builtin/fast-export.o BUILTIN_OBJS += builtin/fetch-pack.o BUILTIN_OBJS += builtin/fetch.o diff --git a/builtin.h b/builtin.h index b9122bc5f4..67f80519da 100644 --- a/builtin.h +++ b/builtin.h @@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix); extern int cmd_diff_index(int argc, const char **argv, const char *prefix); extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); +extern int cmd_difftool(int argc, const char **argv, const char *prefix); extern int cmd_fast_export(int argc, const char **argv, const char *prefix); extern int cmd_fetch(int argc, const char **argv, const char *prefix); extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix); diff --git a/builtin/difftool.c b/builtin/difftool.c new file mode 100644 index 0000000000..53870bbaf7 --- /dev/null +++ b/builtin/difftool.c @@ -0,0 +1,63 @@ +/* + * "git difftool" builtin command + * + * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible + * git-difftool--helper script. + * + * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git. + * The GIT_DIFF* variables are exported for use by git-difftool--helper. + * + * Any arguments that are unknown to this script are forwarded to 'git diff'. + * + * Copyright (C) 2016 Johannes Schindelin + */ +#include "builtin.h" +#include "run-command.h" +#include "exec_cmd.h" + +/* + * NEEDSWORK: this function can go once the legacy-difftool Perl script is + * retired. + * + * We intentionally avoid reading the config directly here, to avoid messing up + * the GIT_* environment variables when we need to fall back to exec()ing the + * Perl script. + */ +static int use_builtin_difftool(void) { + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + int ret; + + argv_array_pushl(&cp.args, + "config", "--bool", "difftool.usebuiltin", NULL); + cp.git_cmd = 1; + if (capture_command(&cp, &out, 6)) + return 0; + strbuf_trim(&out); + ret = !strcmp("true", out.buf); + strbuf_release(&out); + return ret; +} + +int cmd_difftool(int argc, const char **argv, const char *prefix) +{ + /* + * NEEDSWORK: Once the builtin difftool has been tested enough + * and git-legacy-difftool.perl is retired to contrib/, this preamble + * can be removed. + */ + if (!use_builtin_difftool()) { + const char *path = mkpath("%s/git-legacy-difftool", + git_exec_path()); + + if (sane_execvp(path, (char **)argv) < 0) + die_errno("could not exec %s", path); + + return 0; + } + prefix = setup_git_directory(); + trace_repo_setup(prefix); + setup_work_tree(); + + die("TODO"); +} diff --git a/git-difftool.perl b/git-legacy-difftool.perl similarity index 100% rename from git-difftool.perl rename to git-legacy-difftool.perl diff --git a/git.c b/git.c index bbaa949e9c..c58181e5ef 100644 --- a/git.c +++ b/git.c @@ -424,6 +424,12 @@ static struct cmd_struct commands[] = { { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE }, { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, + /* + * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in + * builtin/difftool.c has been removed, this entry should be changed to + * RUN_SETUP | NEED_WORK_TREE + */ + { "difftool", cmd_difftool }, { "fast-export", cmd_fast_export, RUN_SETUP }, { "fetch", cmd_fetch, RUN_SETUP }, { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 99d4123461..e94910c563 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -23,6 +23,8 @@ prompt_given () test "$prompt" = "Launch 'test-tool' [Y/n]? branch" } +# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired. + # Create a file on master and change it on branch test_expect_success PERL 'setup' ' echo master >file && -- 2.11.0.windows.3 ^ permalink raw reply related [flat|nested] 86+ messages in thread
* [PATCH v6 2/3] difftool: implement the functionality in the builtin 2017-01-19 20:30 ` [PATCH v6 " Johannes Schindelin 2017-01-19 20:30 ` [PATCH v6 1/3] difftool: add a skeleton for the upcoming builtin Johannes Schindelin @ 2017-01-19 20:30 ` Johannes Schindelin 2017-01-19 20:30 ` [PATCH v6 3/3] Retire the scripted difftool Johannes Schindelin 2 siblings, 0 replies; 86+ messages in thread From: Johannes Schindelin @ 2017-01-19 20:30 UTC (permalink / raw) To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra This patch gives life to the skeleton added in the previous patch. The motivation for converting the difftool is that Perl scripts are not at all native on Windows, and that `git difftool` therefore is pretty slow on that platform, when there is no good reason for it to be slow. In addition, Perl does not really have access to Git's internals. That means that any script will always have to jump through unnecessary hoops, and it will often need to perform unnecessary work (e.g. when reading the entire config every time `git config` is called to query a single config value). The current version of the builtin difftool does not, however, make full use of the internals but instead chooses to spawn a couple of Git processes, still, to make for an easier conversion. There remains a lot of room for improvement, left later. Note: to play it safe, the original difftool is still called unless the config setting difftool.useBuiltin is set to true. The reason: this new, experimental, builtin difftool was shipped as part of Git for Windows v2.11.0, to allow for easier large-scale testing, but of course as an opt-in feature. The speedup is actually more noticable on Linux than on Windows: a quick test shows that t7800-difftool.sh runs in (2.183s/0.052s/0.108s) (real/user/sys) in a Linux VM, down from (6.529s/3.112s/0.644s), while on Windows, it is (36.064s/2.730s/7.194s), down from (47.637s/2.407s/6.863s). The culprit is most likely the overhead incurred from *still* having to shell out to mergetool-lib.sh and difftool--helper.sh. Still, it is an improvement. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/difftool.c | 672 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 671 insertions(+), 1 deletion(-) diff --git a/builtin/difftool.c b/builtin/difftool.c index 53870bbaf7..2115e548a5 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -11,9 +11,610 @@ * * Copyright (C) 2016 Johannes Schindelin */ +#include "cache.h" #include "builtin.h" #include "run-command.h" #include "exec_cmd.h" +#include "parse-options.h" +#include "argv-array.h" +#include "strbuf.h" +#include "lockfile.h" +#include "dir.h" + +static char *diff_gui_tool; +static int trust_exit_code; + +static const char *const builtin_difftool_usage[] = { + N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"), + NULL +}; + +static int difftool_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "diff.guitool")) { + diff_gui_tool = xstrdup(value); + return 0; + } + + if (!strcmp(var, "difftool.trustexitcode")) { + trust_exit_code = git_config_bool(var, value); + return 0; + } + + return git_default_config(var, value, cb); +} + +static int print_tool_help(void) +{ + const char *argv[] = { "mergetool", "--tool-help=diff", NULL }; + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int parse_index_info(char *p, int *mode1, int *mode2, + struct object_id *oid1, struct object_id *oid2, + char *status) +{ + if (*p != ':') + return error("expected ':', got '%c'", *p); + *mode1 = (int)strtol(p + 1, &p, 8); + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + *mode2 = (int)strtol(p + 1, &p, 8); + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + if (get_oid_hex(++p, oid1)) + return error("expected object ID, got '%s'", p + 1); + p += GIT_SHA1_HEXSZ; + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + if (get_oid_hex(++p, oid2)) + return error("expected object ID, got '%s'", p + 1); + p += GIT_SHA1_HEXSZ; + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + *status = *++p; + if (!*status) + return error("missing status"); + if (p[1] && !isdigit(p[1])) + return error("unexpected trailer: '%s'", p + 1); + return 0; +} + +/* + * Remove any trailing slash from $workdir + * before starting to avoid double slashes in symlink targets. + */ +static void add_path(struct strbuf *buf, size_t base_len, const char *path) +{ + strbuf_setlen(buf, base_len); + if (buf->len && buf->buf[buf->len - 1] != '/') + strbuf_addch(buf, '/'); + strbuf_addstr(buf, path); +} + +/* + * Determine whether we can simply reuse the file in the worktree. + */ +static int use_wt_file(const char *workdir, const char *name, + struct object_id *oid) +{ + struct strbuf buf = STRBUF_INIT; + struct stat st; + int use = 0; + + strbuf_addstr(&buf, workdir); + add_path(&buf, buf.len, name); + + if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) { + struct object_id wt_oid; + int fd = open(buf.buf, O_RDONLY); + + if (fd >= 0 && + !index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) { + if (is_null_oid(oid)) { + oidcpy(oid, &wt_oid); + use = 1; + } else if (!oidcmp(oid, &wt_oid)) + use = 1; + } + } + + strbuf_release(&buf); + + return use; +} + +struct working_tree_entry { + struct hashmap_entry entry; + char path[FLEX_ARRAY]; +}; + +static int working_tree_entry_cmp(struct working_tree_entry *a, + struct working_tree_entry *b, void *keydata) +{ + return strcmp(a->path, b->path); +} + +/* + * The `left` and `right` entries hold paths for the symlinks hashmap, + * and a SHA-1 surrounded by brief text for submodules. + */ +struct pair_entry { + struct hashmap_entry entry; + char left[PATH_MAX], right[PATH_MAX]; + const char path[FLEX_ARRAY]; +}; + +static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata) +{ + return strcmp(a->path, b->path); +} + +static void add_left_or_right(struct hashmap *map, const char *path, + const char *content, int is_right) +{ + struct pair_entry *e, *existing; + + FLEX_ALLOC_STR(e, path, path); + hashmap_entry_init(e, strhash(path)); + existing = hashmap_get(map, e, NULL); + if (existing) { + free(e); + e = existing; + } else { + e->left[0] = e->right[0] = '\0'; + hashmap_add(map, e); + } + strlcpy(is_right ? e->right : e->left, content, PATH_MAX); +} + +struct path_entry { + struct hashmap_entry entry; + char path[FLEX_ARRAY]; +}; + +static int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key) +{ + return strcmp(a->path, key ? key : b->path); +} + +static void changed_files(struct hashmap *result, const char *index_path, + const char *workdir) +{ + struct child_process update_index = CHILD_PROCESS_INIT; + struct child_process diff_files = CHILD_PROCESS_INIT; + struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT; + const char *git_dir = absolute_path(get_git_dir()), *env[] = { + NULL, NULL + }; + FILE *fp; + + strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path); + env[0] = index_env.buf; + + argv_array_pushl(&update_index.args, + "--git-dir", git_dir, "--work-tree", workdir, + "update-index", "--really-refresh", "-q", + "--unmerged", NULL); + update_index.no_stdin = 1; + update_index.no_stdout = 1; + update_index.no_stderr = 1; + update_index.git_cmd = 1; + update_index.use_shell = 0; + update_index.clean_on_exit = 1; + update_index.dir = workdir; + update_index.env = env; + /* Ignore any errors of update-index */ + run_command(&update_index); + + argv_array_pushl(&diff_files.args, + "--git-dir", git_dir, "--work-tree", workdir, + "diff-files", "--name-only", "-z", NULL); + diff_files.no_stdin = 1; + diff_files.git_cmd = 1; + diff_files.use_shell = 0; + diff_files.clean_on_exit = 1; + diff_files.out = -1; + diff_files.dir = workdir; + diff_files.env = env; + if (start_command(&diff_files)) + die("could not obtain raw diff"); + fp = xfdopen(diff_files.out, "r"); + while (!strbuf_getline_nul(&buf, fp)) { + struct path_entry *entry; + FLEX_ALLOC_STR(entry, path, buf.buf); + hashmap_entry_init(entry, strhash(buf.buf)); + hashmap_add(result, entry); + } + if (finish_command(&diff_files)) + die("diff-files did not exit properly"); + strbuf_release(&index_env); + strbuf_release(&buf); +} + +static NORETURN void exit_cleanup(const char *tmpdir, int exit_code) +{ + struct strbuf buf = STRBUF_INIT; + strbuf_addstr(&buf, tmpdir); + remove_dir_recursively(&buf, 0); + if (exit_code) + warning(_("failed: %d"), exit_code); + exit(exit_code); +} + +static int ensure_leading_directories(char *path) +{ + switch (safe_create_leading_directories(path)) { + case SCLD_OK: + case SCLD_EXISTS: + return 0; + default: + return error(_("could not create leading directories " + "of '%s'"), path); + } +} + +static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, + int argc, const char **argv) +{ + char tmpdir[PATH_MAX]; + struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT; + struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT; + struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT; + struct strbuf wtdir = STRBUF_INIT; + size_t ldir_len, rdir_len, wtdir_len; + struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1); + const char *workdir, *tmp; + int ret = 0, i; + FILE *fp; + struct hashmap working_tree_dups, submodules, symlinks2; + struct hashmap_iter iter; + struct pair_entry *entry; + enum object_type type; + unsigned long size; + struct index_state wtindex; + struct checkout lstate, rstate; + int rc, flags = RUN_GIT_CMD, err = 0; + struct child_process child = CHILD_PROCESS_INIT; + const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL }; + struct hashmap wt_modified, tmp_modified; + int indices_loaded = 0; + + workdir = get_git_work_tree(); + + /* Setup temp directories */ + tmp = getenv("TMPDIR"); + xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp"); + if (!mkdtemp(tmpdir)) + return error("could not create '%s'", tmpdir); + strbuf_addf(&ldir, "%s/left/", tmpdir); + strbuf_addf(&rdir, "%s/right/", tmpdir); + strbuf_addstr(&wtdir, workdir); + if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1])) + strbuf_addch(&wtdir, '/'); + mkdir(ldir.buf, 0700); + mkdir(rdir.buf, 0700); + + memset(&wtindex, 0, sizeof(wtindex)); + + memset(&lstate, 0, sizeof(lstate)); + lstate.base_dir = ldir.buf; + lstate.base_dir_len = ldir.len; + lstate.force = 1; + memset(&rstate, 0, sizeof(rstate)); + rstate.base_dir = rdir.buf; + rstate.base_dir_len = rdir.len; + rstate.force = 1; + + ldir_len = ldir.len; + rdir_len = rdir.len; + wtdir_len = wtdir.len; + + hashmap_init(&working_tree_dups, + (hashmap_cmp_fn)working_tree_entry_cmp, 0); + hashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0); + hashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0); + + child.no_stdin = 1; + child.git_cmd = 1; + child.use_shell = 0; + child.clean_on_exit = 1; + child.dir = prefix; + child.out = -1; + argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z", + NULL); + for (i = 0; i < argc; i++) + argv_array_push(&child.args, argv[i]); + if (start_command(&child)) + die("could not obtain raw diff"); + fp = xfdopen(child.out, "r"); + + /* Build index info for left and right sides of the diff */ + i = 0; + while (!strbuf_getline_nul(&info, fp)) { + int lmode, rmode; + struct object_id loid, roid; + char status; + const char *src_path, *dst_path; + size_t src_path_len, dst_path_len; + + if (starts_with(info.buf, "::")) + die(N_("combined diff formats('-c' and '--cc') are " + "not supported in\n" + "directory diff mode('-d' and '--dir-diff').")); + + if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid, + &status)) + break; + if (strbuf_getline_nul(&lpath, fp)) + break; + src_path = lpath.buf; + src_path_len = lpath.len; + + i++; + if (status != 'C' && status != 'R') { + dst_path = src_path; + dst_path_len = src_path_len; + } else { + if (strbuf_getline_nul(&rpath, fp)) + break; + dst_path = rpath.buf; + dst_path_len = rpath.len; + } + + if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) { + strbuf_reset(&buf); + strbuf_addf(&buf, "Subproject commit %s", + oid_to_hex(&loid)); + add_left_or_right(&submodules, src_path, buf.buf, 0); + strbuf_reset(&buf); + strbuf_addf(&buf, "Subproject commit %s", + oid_to_hex(&roid)); + if (!oidcmp(&loid, &roid)) + strbuf_addstr(&buf, "-dirty"); + add_left_or_right(&submodules, dst_path, buf.buf, 1); + continue; + } + + if (S_ISLNK(lmode)) { + char *content = read_sha1_file(loid.hash, &type, &size); + add_left_or_right(&symlinks2, src_path, content, 0); + free(content); + } + + if (S_ISLNK(rmode)) { + char *content = read_sha1_file(roid.hash, &type, &size); + add_left_or_right(&symlinks2, dst_path, content, 1); + free(content); + } + + if (lmode && status != 'C') { + ce->ce_mode = lmode; + oidcpy(&ce->oid, &loid); + strcpy(ce->name, src_path); + ce->ce_namelen = src_path_len; + if (checkout_entry(ce, &lstate, NULL)) + return error("could not write '%s'", src_path); + } + + if (rmode) { + struct working_tree_entry *entry; + + /* Avoid duplicate working_tree entries */ + FLEX_ALLOC_STR(entry, path, dst_path); + hashmap_entry_init(entry, strhash(dst_path)); + if (hashmap_get(&working_tree_dups, entry, NULL)) { + free(entry); + continue; + } + hashmap_add(&working_tree_dups, entry); + + if (!use_wt_file(workdir, dst_path, &roid)) { + ce->ce_mode = rmode; + oidcpy(&ce->oid, &roid); + strcpy(ce->name, dst_path); + ce->ce_namelen = dst_path_len; + if (checkout_entry(ce, &rstate, NULL)) + return error("could not write '%s'", + dst_path); + } else if (!is_null_oid(&roid)) { + /* + * Changes in the working tree need special + * treatment since they are not part of the + * index. + */ + struct cache_entry *ce2 = + make_cache_entry(rmode, roid.hash, + dst_path, 0, 0); + + add_index_entry(&wtindex, ce2, + ADD_CACHE_JUST_APPEND); + + add_path(&rdir, rdir_len, dst_path); + if (ensure_leading_directories(rdir.buf)) + return error("could not create " + "directory for '%s'", + dst_path); + add_path(&wtdir, wtdir_len, dst_path); + if (symlinks) { + if (symlink(wtdir.buf, rdir.buf)) { + ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf); + goto finish; + } + } else { + struct stat st; + if (stat(wtdir.buf, &st)) + st.st_mode = 0644; + if (copy_file(rdir.buf, wtdir.buf, + st.st_mode)) { + ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf); + goto finish; + } + } + } + } + } + + if (finish_command(&child)) { + ret = error("error occurred running diff --raw"); + goto finish; + } + + if (!i) + return 0; + + /* + * Changes to submodules require special treatment.This loop writes a + * temporary file to both the left and right directories to show the + * change in the recorded SHA1 for the submodule. + */ + hashmap_iter_init(&submodules, &iter); + while ((entry = hashmap_iter_next(&iter))) { + if (*entry->left) { + add_path(&ldir, ldir_len, entry->path); + ensure_leading_directories(ldir.buf); + write_file(ldir.buf, "%s", entry->left); + } + if (*entry->right) { + add_path(&rdir, rdir_len, entry->path); + ensure_leading_directories(rdir.buf); + write_file(rdir.buf, "%s", entry->right); + } + } + + /* + * Symbolic links require special treatment.The standard "git diff" + * shows only the link itself, not the contents of the link target. + * This loop replicates that behavior. + */ + hashmap_iter_init(&symlinks2, &iter); + while ((entry = hashmap_iter_next(&iter))) { + if (*entry->left) { + add_path(&ldir, ldir_len, entry->path); + ensure_leading_directories(ldir.buf); + write_file(ldir.buf, "%s", entry->left); + } + if (*entry->right) { + add_path(&rdir, rdir_len, entry->path); + ensure_leading_directories(rdir.buf); + write_file(rdir.buf, "%s", entry->right); + } + } + + strbuf_release(&buf); + + strbuf_setlen(&ldir, ldir_len); + helper_argv[1] = ldir.buf; + strbuf_setlen(&rdir, rdir_len); + helper_argv[2] = rdir.buf; + + if (extcmd) { + helper_argv[0] = extcmd; + flags = 0; + } else + setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1); + rc = run_command_v_opt(helper_argv, flags); + + /* + * If the diff includes working copy files and those + * files were modified during the diff, then the changes + * should be copied back to the working tree. + * Do not copy back files when symlinks are used and the + * external tool did not replace the original link with a file. + * + * These hashes are loaded lazily since they aren't needed + * in the common case of --symlinks and the difftool updating + * files through the symlink. + */ + hashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp, + wtindex.cache_nr); + hashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp, + wtindex.cache_nr); + + for (i = 0; i < wtindex.cache_nr; i++) { + struct hashmap_entry dummy; + const char *name = wtindex.cache[i]->name; + struct stat st; + + add_path(&rdir, rdir_len, name); + if (lstat(rdir.buf, &st)) + continue; + + if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode)) + continue; + + if (!indices_loaded) { + static struct lock_file lock; + strbuf_reset(&buf); + strbuf_addf(&buf, "%s/wtindex", tmpdir); + if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 || + write_locked_index(&wtindex, &lock, COMMIT_LOCK)) { + ret = error("could not write %s", buf.buf); + rollback_lock_file(&lock); + goto finish; + } + changed_files(&wt_modified, buf.buf, workdir); + strbuf_setlen(&rdir, rdir_len); + changed_files(&tmp_modified, buf.buf, rdir.buf); + add_path(&rdir, rdir_len, name); + indices_loaded = 1; + } + + hashmap_entry_init(&dummy, strhash(name)); + if (hashmap_get(&tmp_modified, &dummy, name)) { + add_path(&wtdir, wtdir_len, name); + if (hashmap_get(&wt_modified, &dummy, name)) { + warning(_("both files modified: '%s' and '%s'."), + wtdir.buf, rdir.buf); + warning(_("working tree file has been left.")); + warning(""); + err = 1; + } else if (unlink(wtdir.buf) || + copy_file(wtdir.buf, rdir.buf, st.st_mode)) + warning_errno(_("could not copy '%s' to '%s'"), + rdir.buf, wtdir.buf); + } + } + + if (err) { + warning(_("temporary files exist in '%s'."), tmpdir); + warning(_("you may want to cleanup or recover these.")); + exit(1); + } else + exit_cleanup(tmpdir, rc); + +finish: + free(ce); + strbuf_release(&ldir); + strbuf_release(&rdir); + strbuf_release(&wtdir); + strbuf_release(&buf); + + return ret; +} + +static int run_file_diff(int prompt, const char *prefix, + int argc, const char **argv) +{ + struct argv_array args = ARGV_ARRAY_INIT; + const char *env[] = { + "GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL, + NULL + }; + int ret = 0, i; + + if (prompt > 0) + env[2] = "GIT_DIFFTOOL_PROMPT=true"; + else if (!prompt) + env[2] = "GIT_DIFFTOOL_NO_PROMPT=true"; + + + argv_array_push(&args, "diff"); + for (i = 0; i < argc; i++) + argv_array_push(&args, argv[i]); + ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env); + exit(ret); +} /* * NEEDSWORK: this function can go once the legacy-difftool Perl script is @@ -41,6 +642,35 @@ static int use_builtin_difftool(void) { int cmd_difftool(int argc, const char **argv, const char *prefix) { + int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0, + tool_help = 0; + static char *difftool_cmd = NULL, *extcmd = NULL; + struct option builtin_difftool_options[] = { + OPT_BOOL('g', "gui", &use_gui_tool, + N_("use `diff.guitool` instead of `diff.tool`")), + OPT_BOOL('d', "dir-diff", &dir_diff, + N_("perform a full-directory diff")), + { OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL, + N_("do not prompt before launching a diff tool"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0}, + { OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL, + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN, + NULL, 1 }, + OPT_BOOL(0, "symlinks", &symlinks, + N_("use symlinks in dir-diff mode")), + OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"), + N_("use the specified diff tool")), + OPT_BOOL(0, "tool-help", &tool_help, + N_("print a list of diff tools that may be used with " + "`--tool`")), + OPT_BOOL(0, "trust-exit-code", &trust_exit_code, + N_("make 'git-difftool' exit when an invoked diff " + "tool returns a non - zero exit code")), + OPT_STRING('x', "extcmd", &extcmd, N_("<command>"), + N_("specify a custom command for viewing diffs")), + OPT_END() + }; + /* * NEEDSWORK: Once the builtin difftool has been tested enough * and git-legacy-difftool.perl is retired to contrib/, this preamble @@ -58,6 +688,46 @@ int cmd_difftool(int argc, const char **argv, const char *prefix) prefix = setup_git_directory(); trace_repo_setup(prefix); setup_work_tree(); + /* NEEDSWORK: once we no longer spawn anything, remove this */ + setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1); + setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1); + + git_config(difftool_config, NULL); + symlinks = has_symlinks; + + argc = parse_options(argc, argv, prefix, builtin_difftool_options, + builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_DASHDASH); - die("TODO"); + if (tool_help) + return print_tool_help(); + + if (use_gui_tool && diff_gui_tool && *diff_gui_tool) + setenv("GIT_DIFF_TOOL", diff_gui_tool, 1); + else if (difftool_cmd) { + if (*difftool_cmd) + setenv("GIT_DIFF_TOOL", difftool_cmd, 1); + else + die(_("no <tool> given for --tool=<tool>")); + } + + if (extcmd) { + if (*extcmd) + setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1); + else + die(_("no <cmd> given for --extcmd=<cmd>")); + } + + setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE", + trust_exit_code ? "true" : "false", 1); + + /* + * In directory diff mode, 'git-difftool--helper' is called once + * to compare the a / b directories. In file diff mode, 'git diff' + * will invoke a separate instance of 'git-difftool--helper' for + * each file that changed. + */ + if (dir_diff) + return run_dir_diff(extcmd, symlinks, prefix, argc, argv); + return run_file_diff(prompt, prefix, argc, argv); } -- 2.11.0.windows.3 ^ permalink raw reply related [flat|nested] 86+ messages in thread
* [PATCH v6 3/3] Retire the scripted difftool 2017-01-19 20:30 ` [PATCH v6 " Johannes Schindelin 2017-01-19 20:30 ` [PATCH v6 1/3] difftool: add a skeleton for the upcoming builtin Johannes Schindelin 2017-01-19 20:30 ` [PATCH v6 2/3] difftool: implement the functionality in the builtin Johannes Schindelin @ 2017-01-19 20:30 ` Johannes Schindelin 2 siblings, 0 replies; 86+ messages in thread From: Johannes Schindelin @ 2017-01-19 20:30 UTC (permalink / raw) To: git; +Cc: Junio C Hamano, David Aguilar, Dennis Kaarsemaker, Paul Sbarra It served its purpose, but now we have a builtin difftool. Time for the Perl script to enjoy Florida. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- .gitignore | 1 - Makefile | 1 - builtin/difftool.c | 41 ---------- .../examples/git-difftool.perl | 0 git.c | 7 +- t/t7800-difftool.sh | 94 +++++++++++----------- 6 files changed, 47 insertions(+), 97 deletions(-) rename git-legacy-difftool.perl => contrib/examples/git-difftool.perl (100%) diff --git a/.gitignore b/.gitignore index 5555ae025b..6722f78f9a 100644 --- a/.gitignore +++ b/.gitignore @@ -76,7 +76,6 @@ /git-init-db /git-interpret-trailers /git-instaweb -/git-legacy-difftool /git-log /git-ls-files /git-ls-remote diff --git a/Makefile b/Makefile index 8cf5bef034..e9aa6ae57c 100644 --- a/Makefile +++ b/Makefile @@ -522,7 +522,6 @@ SCRIPT_LIB += git-sh-setup SCRIPT_LIB += git-sh-i18n SCRIPT_PERL += git-add--interactive.perl -SCRIPT_PERL += git-legacy-difftool.perl SCRIPT_PERL += git-archimport.perl SCRIPT_PERL += git-cvsexportcommit.perl SCRIPT_PERL += git-cvsimport.perl diff --git a/builtin/difftool.c b/builtin/difftool.c index 2115e548a5..42ad9e804a 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -616,30 +616,6 @@ static int run_file_diff(int prompt, const char *prefix, exit(ret); } -/* - * NEEDSWORK: this function can go once the legacy-difftool Perl script is - * retired. - * - * We intentionally avoid reading the config directly here, to avoid messing up - * the GIT_* environment variables when we need to fall back to exec()ing the - * Perl script. - */ -static int use_builtin_difftool(void) { - struct child_process cp = CHILD_PROCESS_INIT; - struct strbuf out = STRBUF_INIT; - int ret; - - argv_array_pushl(&cp.args, - "config", "--bool", "difftool.usebuiltin", NULL); - cp.git_cmd = 1; - if (capture_command(&cp, &out, 6)) - return 0; - strbuf_trim(&out); - ret = !strcmp("true", out.buf); - strbuf_release(&out); - return ret; -} - int cmd_difftool(int argc, const char **argv, const char *prefix) { int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0, @@ -671,23 +647,6 @@ int cmd_difftool(int argc, const char **argv, const char *prefix) OPT_END() }; - /* - * NEEDSWORK: Once the builtin difftool has been tested enough - * and git-legacy-difftool.perl is retired to contrib/, this preamble - * can be removed. - */ - if (!use_builtin_difftool()) { - const char *path = mkpath("%s/git-legacy-difftool", - git_exec_path()); - - if (sane_execvp(path, (char **)argv) < 0) - die_errno("could not exec %s", path); - - return 0; - } - prefix = setup_git_directory(); - trace_repo_setup(prefix); - setup_work_tree(); /* NEEDSWORK: once we no longer spawn anything, remove this */ setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1); setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1); diff --git a/git-legacy-difftool.perl b/contrib/examples/git-difftool.perl similarity index 100% rename from git-legacy-difftool.perl rename to contrib/examples/git-difftool.perl diff --git a/git.c b/git.c index c58181e5ef..bd4d668a21 100644 --- a/git.c +++ b/git.c @@ -424,12 +424,7 @@ static struct cmd_struct commands[] = { { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE }, { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, - /* - * NEEDSWORK: Once the redirection to git-legacy-difftool.perl in - * builtin/difftool.c has been removed, this entry should be changed to - * RUN_SETUP | NEED_WORK_TREE - */ - { "difftool", cmd_difftool }, + { "difftool", cmd_difftool, RUN_SETUP | NEED_WORK_TREE }, { "fast-export", cmd_fast_export, RUN_SETUP }, { "fetch", cmd_fetch, RUN_SETUP }, { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index e94910c563..aa0ef02597 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -23,10 +23,8 @@ prompt_given () test "$prompt" = "Launch 'test-tool' [Y/n]? branch" } -# NEEDSWORK: lose all the PERL prereqs once legacy-difftool is retired. - # Create a file on master and change it on branch -test_expect_success PERL 'setup' ' +test_expect_success 'setup' ' echo master >file && git add file && git commit -m "added file" && @@ -38,7 +36,7 @@ test_expect_success PERL 'setup' ' ' # Configure a custom difftool.<tool>.cmd and use it -test_expect_success PERL 'custom commands' ' +test_expect_success 'custom commands' ' difftool_test_setup && test_config difftool.test-tool.cmd "cat \"\$REMOTE\"" && echo master >expect && @@ -51,21 +49,21 @@ test_expect_success PERL 'custom commands' ' test_cmp expect actual ' -test_expect_success PERL 'custom tool commands override built-ins' ' +test_expect_success 'custom tool commands override built-ins' ' test_config difftool.vimdiff.cmd "cat \"\$REMOTE\"" && echo master >expect && git difftool --tool vimdiff --no-prompt branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool ignores bad --tool values' ' +test_expect_success 'difftool ignores bad --tool values' ' : >expect && test_must_fail \ git difftool --no-prompt --tool=bad-tool branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool forwards arguments to diff' ' +test_expect_success 'difftool forwards arguments to diff' ' difftool_test_setup && >for-diff && git add for-diff && @@ -78,40 +76,40 @@ test_expect_success PERL 'difftool forwards arguments to diff' ' rm for-diff ' -test_expect_success PERL 'difftool ignores exit code' ' +test_expect_success 'difftool ignores exit code' ' test_config difftool.error.cmd false && git difftool -y -t error branch ' -test_expect_success PERL 'difftool forwards exit code with --trust-exit-code' ' +test_expect_success 'difftool forwards exit code with --trust-exit-code' ' test_config difftool.error.cmd false && test_must_fail git difftool -y --trust-exit-code -t error branch ' -test_expect_success PERL 'difftool forwards exit code with --trust-exit-code for built-ins' ' +test_expect_success 'difftool forwards exit code with --trust-exit-code for built-ins' ' test_config difftool.vimdiff.path false && test_must_fail git difftool -y --trust-exit-code -t vimdiff branch ' -test_expect_success PERL 'difftool honors difftool.trustExitCode = true' ' +test_expect_success 'difftool honors difftool.trustExitCode = true' ' test_config difftool.error.cmd false && test_config difftool.trustExitCode true && test_must_fail git difftool -y -t error branch ' -test_expect_success PERL 'difftool honors difftool.trustExitCode = false' ' +test_expect_success 'difftool honors difftool.trustExitCode = false' ' test_config difftool.error.cmd false && test_config difftool.trustExitCode false && git difftool -y -t error branch ' -test_expect_success PERL 'difftool ignores exit code with --no-trust-exit-code' ' +test_expect_success 'difftool ignores exit code with --no-trust-exit-code' ' test_config difftool.error.cmd false && test_config difftool.trustExitCode true && git difftool -y --no-trust-exit-code -t error branch ' -test_expect_success PERL 'difftool stops on error with --trust-exit-code' ' +test_expect_success 'difftool stops on error with --trust-exit-code' ' test_when_finished "rm -f for-diff .git/fail-right-file" && test_when_finished "git reset -- for-diff" && write_script .git/fail-right-file <<-\EOF && @@ -126,13 +124,13 @@ test_expect_success PERL 'difftool stops on error with --trust-exit-code' ' test_cmp expect actual ' -test_expect_success PERL 'difftool honors exit status if command not found' ' +test_expect_success 'difftool honors exit status if command not found' ' test_config difftool.nonexistent.cmd i-dont-exist && test_config difftool.trustExitCode false && test_must_fail git difftool -y -t nonexistent branch ' -test_expect_success PERL 'difftool honors --gui' ' +test_expect_success 'difftool honors --gui' ' difftool_test_setup && test_config merge.tool bogus-tool && test_config diff.tool bogus-tool && @@ -143,7 +141,7 @@ test_expect_success PERL 'difftool honors --gui' ' test_cmp expect actual ' -test_expect_success PERL 'difftool --gui last setting wins' ' +test_expect_success 'difftool --gui last setting wins' ' difftool_test_setup && : >expect && git difftool --no-prompt --gui --no-gui >actual && @@ -157,7 +155,7 @@ test_expect_success PERL 'difftool --gui last setting wins' ' test_cmp expect actual ' -test_expect_success PERL 'difftool --gui works without configured diff.guitool' ' +test_expect_success 'difftool --gui works without configured diff.guitool' ' difftool_test_setup && echo branch >expect && git difftool --no-prompt --gui branch >actual && @@ -165,7 +163,7 @@ test_expect_success PERL 'difftool --gui works without configured diff.guitool' ' # Specify the diff tool using $GIT_DIFF_TOOL -test_expect_success PERL 'GIT_DIFF_TOOL variable' ' +test_expect_success 'GIT_DIFF_TOOL variable' ' difftool_test_setup && git config --unset diff.tool && echo branch >expect && @@ -175,7 +173,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL variable' ' # Test the $GIT_*_TOOL variables and ensure # that $GIT_DIFF_TOOL always wins unless --tool is specified -test_expect_success PERL 'GIT_DIFF_TOOL overrides' ' +test_expect_success 'GIT_DIFF_TOOL overrides' ' difftool_test_setup && test_config diff.tool bogus-tool && test_config merge.tool bogus-tool && @@ -193,7 +191,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL overrides' ' # Test that we don't have to pass --no-prompt to difftool # when $GIT_DIFFTOOL_NO_PROMPT is true -test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' ' +test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' ' difftool_test_setup && echo branch >expect && GIT_DIFFTOOL_NO_PROMPT=true git difftool branch >actual && @@ -202,7 +200,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' ' # git-difftool supports the difftool.prompt variable. # Test that GIT_DIFFTOOL_PROMPT can override difftool.prompt = false -test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' ' +test_expect_success 'GIT_DIFFTOOL_PROMPT variable' ' difftool_test_setup && test_config difftool.prompt false && echo >input && @@ -212,7 +210,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' ' ' # Test that we don't have to pass --no-prompt when difftool.prompt is false -test_expect_success PERL 'difftool.prompt config variable is false' ' +test_expect_success 'difftool.prompt config variable is false' ' difftool_test_setup && test_config difftool.prompt false && echo branch >expect && @@ -221,7 +219,7 @@ test_expect_success PERL 'difftool.prompt config variable is false' ' ' # Test that we don't have to pass --no-prompt when mergetool.prompt is false -test_expect_success PERL 'difftool merge.prompt = false' ' +test_expect_success 'difftool merge.prompt = false' ' difftool_test_setup && test_might_fail git config --unset difftool.prompt && test_config mergetool.prompt false && @@ -231,7 +229,7 @@ test_expect_success PERL 'difftool merge.prompt = false' ' ' # Test that the -y flag can override difftool.prompt = true -test_expect_success PERL 'difftool.prompt can overridden with -y' ' +test_expect_success 'difftool.prompt can overridden with -y' ' difftool_test_setup && test_config difftool.prompt true && echo branch >expect && @@ -240,7 +238,7 @@ test_expect_success PERL 'difftool.prompt can overridden with -y' ' ' # Test that the --prompt flag can override difftool.prompt = false -test_expect_success PERL 'difftool.prompt can overridden with --prompt' ' +test_expect_success 'difftool.prompt can overridden with --prompt' ' difftool_test_setup && test_config difftool.prompt false && echo >input && @@ -250,7 +248,7 @@ test_expect_success PERL 'difftool.prompt can overridden with --prompt' ' ' # Test that the last flag passed on the command-line wins -test_expect_success PERL 'difftool last flag wins' ' +test_expect_success 'difftool last flag wins' ' difftool_test_setup && echo branch >expect && git difftool --prompt --no-prompt branch >actual && @@ -263,7 +261,7 @@ test_expect_success PERL 'difftool last flag wins' ' # git-difftool falls back to git-mergetool config variables # so test that behavior here -test_expect_success PERL 'difftool + mergetool config variables' ' +test_expect_success 'difftool + mergetool config variables' ' test_config merge.tool test-tool && test_config mergetool.test-tool.cmd "cat \$LOCAL" && echo branch >expect && @@ -277,49 +275,49 @@ test_expect_success PERL 'difftool + mergetool config variables' ' test_cmp expect actual ' -test_expect_success PERL 'difftool.<tool>.path' ' +test_expect_success 'difftool.<tool>.path' ' test_config difftool.tkdiff.path echo && git difftool --tool=tkdiff --no-prompt branch >output && lines=$(grep file output | wc -l) && test "$lines" -eq 1 ' -test_expect_success PERL 'difftool --extcmd=cat' ' +test_expect_success 'difftool --extcmd=cat' ' echo branch >expect && echo master >>expect && git difftool --no-prompt --extcmd=cat branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd cat' ' +test_expect_success 'difftool --extcmd cat' ' echo branch >expect && echo master >>expect && git difftool --no-prompt --extcmd=cat branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool -x cat' ' +test_expect_success 'difftool -x cat' ' echo branch >expect && echo master >>expect && git difftool --no-prompt -x cat branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd echo arg1' ' +test_expect_success 'difftool --extcmd echo arg1' ' echo file >expect && git difftool --no-prompt \ --extcmd sh\ -c\ \"echo\ \$1\" branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd cat arg1' ' +test_expect_success 'difftool --extcmd cat arg1' ' echo master >expect && git difftool --no-prompt \ --extcmd sh\ -c\ \"cat\ \$1\" branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd cat arg2' ' +test_expect_success 'difftool --extcmd cat arg2' ' echo branch >expect && git difftool --no-prompt \ --extcmd sh\ -c\ \"cat\ \$2\" branch >actual && @@ -327,7 +325,7 @@ test_expect_success PERL 'difftool --extcmd cat arg2' ' ' # Create a second file on master and a different version on branch -test_expect_success PERL 'setup with 2 files different' ' +test_expect_success 'setup with 2 files different' ' echo m2 >file2 && git add file2 && git commit -m "added file2" && @@ -339,7 +337,7 @@ test_expect_success PERL 'setup with 2 files different' ' git checkout master ' -test_expect_success PERL 'say no to the first file' ' +test_expect_success 'say no to the first file' ' (echo n && echo) >input && git difftool -x cat branch <input >output && grep m2 output && @@ -348,7 +346,7 @@ test_expect_success PERL 'say no to the first file' ' ! grep branch output ' -test_expect_success PERL 'say no to the second file' ' +test_expect_success 'say no to the second file' ' (echo && echo n) >input && git difftool -x cat branch <input >output && grep master output && @@ -357,7 +355,7 @@ test_expect_success PERL 'say no to the second file' ' ! grep br2 output ' -test_expect_success PERL 'ending prompt input with EOF' ' +test_expect_success 'ending prompt input with EOF' ' git difftool -x cat branch </dev/null >output && ! grep master output && ! grep branch output && @@ -365,12 +363,12 @@ test_expect_success PERL 'ending prompt input with EOF' ' ! grep br2 output ' -test_expect_success PERL 'difftool --tool-help' ' +test_expect_success 'difftool --tool-help' ' git difftool --tool-help >output && grep tool output ' -test_expect_success PERL 'setup change in subdirectory' ' +test_expect_success 'setup change in subdirectory' ' git checkout master && mkdir sub && echo master >sub/sub && @@ -384,11 +382,11 @@ test_expect_success PERL 'setup change in subdirectory' ' ' run_dir_diff_test () { - test_expect_success PERL "$1 --no-symlinks" " + test_expect_success "$1 --no-symlinks" " symlinks=--no-symlinks && $2 " - test_expect_success PERL,SYMLINKS "$1 --symlinks" " + test_expect_success SYMLINKS "$1 --symlinks" " symlinks=--symlinks && $2 " @@ -510,7 +508,7 @@ do done >actual EOF -test_expect_success PERL,SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' ' +test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' ' cat >expect <<-EOF && file $PWD/file @@ -547,7 +545,7 @@ write_script modify-file <<\EOF echo "new content" >file EOF -test_expect_success PERL 'difftool --no-symlinks does not overwrite working tree file ' ' +test_expect_success 'difftool --no-symlinks does not overwrite working tree file ' ' echo "orig content" >file && git difftool --dir-diff --no-symlinks --extcmd "$PWD/modify-file" branch && echo "new content" >expect && @@ -560,7 +558,7 @@ echo "tmp content" >"$2/file" && echo "$2" >tmpdir EOF -test_expect_success PERL 'difftool --no-symlinks detects conflict ' ' +test_expect_success 'difftool --no-symlinks detects conflict ' ' ( TMPDIR=$TRASH_DIRECTORY && export TMPDIR && @@ -573,7 +571,7 @@ test_expect_success PERL 'difftool --no-symlinks detects conflict ' ' ) ' -test_expect_success PERL 'difftool properly honors gitlink and core.worktree' ' +test_expect_success 'difftool properly honors gitlink and core.worktree' ' git submodule add ./. submod/ule && test_config -C submod/ule diff.tool checktrees && test_config -C submod/ule difftool.checktrees.cmd '\'' @@ -587,7 +585,7 @@ test_expect_success PERL 'difftool properly honors gitlink and core.worktree' ' ) ' -test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' ' +test_expect_success SYMLINKS 'difftool --dir-diff symlinked directories' ' git init dirlinks && ( cd dirlinks && -- 2.11.0.windows.3 ^ permalink raw reply related [flat|nested] 86+ messages in thread
end of thread, other threads:[~2017-01-19 20:40 UTC | newest] Thread overview: 86+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2016-11-22 17:01 [PATCH 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin 2016-11-22 17:01 ` [PATCH 1/2] difftool: add the builtin Johannes Schindelin 2016-11-23 8:08 ` David Aguilar 2016-11-23 11:34 ` Johannes Schindelin 2016-11-22 17:01 ` [PATCH 2/2] difftool: add a feature flag for the builtin vs scripted version Johannes Schindelin 2016-11-23 14:51 ` Dennis Kaarsemaker 2016-11-23 17:29 ` Johannes Schindelin 2016-11-23 17:40 ` Junio C Hamano 2016-11-23 18:18 ` Junio C Hamano 2016-11-23 19:55 ` Johannes Schindelin 2016-11-23 20:04 ` Junio C Hamano 2016-11-23 22:01 ` Johannes Schindelin 2016-11-23 22:03 ` [PATCH v2 0/1] Show Git Mailing List: a builtin difftool Johannes Schindelin 2016-11-23 22:03 ` [PATCH v2 1/1] difftool: add the builtin Johannes Schindelin 2016-11-23 22:25 ` Junio C Hamano 2016-11-23 22:30 ` Junio C Hamano 2016-11-24 10:38 ` Johannes Schindelin 2016-11-24 20:55 ` [PATCH v3 0/2] Show Git Mailing List: a builtin difftool Johannes Schindelin 2016-11-24 20:55 ` [PATCH v3 1/2] difftool: add a skeleton for the upcoming builtin Johannes Schindelin 2016-11-24 21:08 ` Jeff King 2016-11-24 21:56 ` Johannes Schindelin 2016-11-25 3:18 ` Jeff King 2016-11-25 11:05 ` Johannes Schindelin 2016-11-25 17:19 ` Jeff King 2016-11-25 17:41 ` Johannes Schindelin 2016-11-25 17:47 ` Jeff King 2016-11-26 12:22 ` Johannes Schindelin 2016-11-26 16:19 ` Jeff King 2016-11-26 13:01 ` Johannes Schindelin 2016-11-27 16:50 ` Jeff King 2016-11-28 17:06 ` Junio C Hamano 2016-11-28 17:34 ` Johannes Schindelin 2016-11-28 19:27 ` Junio C Hamano 2016-11-29 20:36 ` Johannes Schindelin 2016-11-29 20:49 ` Jeff King 2016-11-30 12:30 ` Johannes Schindelin 2016-11-30 12:35 ` Jeff King 2016-11-29 20:55 ` Junio C Hamano 2016-11-30 12:30 ` Johannes Schindelin 2016-12-01 23:33 ` Junio C Hamano 2016-12-05 10:36 ` Johannes Schindelin 2016-12-05 18:37 ` Junio C Hamano 2016-12-06 13:16 ` Johannes Schindelin 2016-12-06 13:36 ` Jeff King 2016-12-06 14:48 ` Johannes Schindelin 2016-12-06 15:09 ` Jeff King 2016-12-06 18:22 ` Stefan Beller 2016-12-06 18:35 ` Jeff King 2017-01-18 22:38 ` Brandon Williams 2016-11-30 16:02 ` Jakub Narębski 2016-11-30 18:39 ` Junio C Hamano 2016-11-24 20:55 ` [PATCH v3 2/2] difftool: implement the functionality in the builtin Johannes Schindelin 2016-11-25 21:24 ` Jakub Narębski 2016-11-27 11:10 ` Johannes Schindelin 2016-11-27 11:20 ` Jakub Narębski 2017-01-02 16:16 ` [PATCH v4 0/4] Show Git Mailing List: a builtin difftool Johannes Schindelin 2017-01-02 16:22 ` [PATCH v4 1/4] Avoid Coverity warning about unfree()d git_exec_path() Johannes Schindelin 2017-01-03 20:11 ` Stefan Beller 2017-01-03 21:33 ` Johannes Schindelin 2017-01-04 18:09 ` Stefan Beller 2017-01-04 1:13 ` Jeff King 2017-01-09 1:25 ` Junio C Hamano 2017-01-09 6:00 ` Jeff King 2017-01-09 7:49 ` Johannes Schindelin 2017-01-09 19:21 ` Stefan Beller 2017-01-02 16:22 ` [PATCH v4 2/4] difftool: add a skeleton for the upcoming builtin Johannes Schindelin 2017-01-02 16:22 ` [PATCH v4 3/4] difftool: implement the functionality in the builtin Johannes Schindelin 2017-01-02 16:24 ` [PATCH v4 4/4] t7800: run both builtin and scripted difftool, for now Johannes Schindelin 2017-01-09 1:38 ` Junio C Hamano 2017-01-09 7:56 ` Johannes Schindelin 2017-01-09 9:46 ` Junio C Hamano 2017-01-17 15:54 ` [PATCH v5 0/3] Turn the difftool into a builtin Johannes Schindelin 2017-01-17 15:54 ` [PATCH v5 1/3] difftool: add a skeleton for the upcoming builtin Johannes Schindelin 2017-01-17 15:55 ` [PATCH v5 2/3] difftool: implement the functionality in the builtin Johannes Schindelin 2017-01-17 15:55 ` [PATCH v5 3/3] Retire the scripted difftool Johannes Schindelin 2017-01-17 21:46 ` Junio C Hamano 2017-01-18 12:33 ` Johannes Schindelin 2017-01-18 19:15 ` Junio C Hamano 2017-01-19 16:30 ` Johannes Schindelin 2017-01-19 17:56 ` Junio C Hamano 2017-01-19 20:32 ` Johannes Schindelin 2017-01-17 21:31 ` [PATCH v5 0/3] Turn the difftool into a builtin Junio C Hamano 2017-01-19 20:30 ` [PATCH v6 " Johannes Schindelin 2017-01-19 20:30 ` [PATCH v6 1/3] difftool: add a skeleton for the upcoming builtin Johannes Schindelin 2017-01-19 20:30 ` [PATCH v6 2/3] difftool: implement the functionality in the builtin Johannes Schindelin 2017-01-19 20:30 ` [PATCH v6 3/3] Retire the scripted difftool Johannes Schindelin
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.