submodule: convert check_for_new_submodule_commits to object_id
[gitweb.git] / builtin / difftool.c
index 2115e548a50e8fdf32bbbd940d83dfb239e84868..25e54ad3edb7dfd7d5e5e9f1ded4dc508048ddc6 100644 (file)
@@ -254,6 +254,49 @@ static int ensure_leading_directories(char *path)
        }
 }
 
+/*
+ * Unconditional writing of a plain regular file is what
+ * "git difftool --dir-diff" wants to do for symlinks.  We are preparing two
+ * temporary directories to be fed to a Git-unaware tool that knows how to
+ * show a diff of two directories (e.g. "diff -r A B").
+ *
+ * Because the tool is Git-unaware, if a symbolic link appears in either of
+ * these temporary directories, it will try to dereference and show the
+ * difference of the target of the symbolic link, which is not what we want,
+ * as the goal of the dir-diff mode is to produce an output that is logically
+ * equivalent to what "git diff" produces.
+ *
+ * Most importantly, we want to get textual comparison of the result of the
+ * readlink(2).  get_symlink() provides that---it returns the contents of
+ * the symlink that gets written to a regular file to force the external tool
+ * to compare the readlink(2) result as text, even on a filesystem that is
+ * capable of doing a symbolic link.
+ */
+static char *get_symlink(const struct object_id *oid, const char *path)
+{
+       char *data;
+       if (is_null_oid(oid)) {
+               /* The symlink is unknown to Git so read from the filesystem */
+               struct strbuf link = STRBUF_INIT;
+               if (has_symlinks) {
+                       if (strbuf_readlink(&link, path, strlen(path)))
+                               die(_("could not read symlink %s"), path);
+               } else if (strbuf_read_file(&link, path, 128))
+                       die(_("could not read symlink file %s"), path);
+
+               data = strbuf_detach(&link, NULL);
+       } else {
+               enum object_type type;
+               unsigned long size;
+               data = read_sha1_file(oid->hash, &type, &size);
+               if (!data)
+                       die(_("could not read object %s for symlink %s"),
+                               oid_to_hex(oid), path);
+       }
+
+       return data;
+}
+
 static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
                        int argc, const char **argv)
 {
@@ -270,8 +313,6 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
        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;
@@ -377,13 +418,13 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
                }
 
                if (S_ISLNK(lmode)) {
-                       char *content = read_sha1_file(loid.hash, &type, &size);
+                       char *content = get_symlink(&loid, src_path);
                        add_left_or_right(&symlinks2, src_path, content, 0);
                        free(content);
                }
 
                if (S_ISLNK(rmode)) {
-                       char *content = read_sha1_file(roid.hash, &type, &size);
+                       char *content = get_symlink(&roid, dst_path);
                        add_left_or_right(&symlinks2, dst_path, content, 1);
                        free(content);
                }
@@ -397,7 +438,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
                                return error("could not write '%s'", src_path);
                }
 
-               if (rmode) {
+               if (rmode && !S_ISLNK(rmode)) {
                        struct working_tree_entry *entry;
 
                        /* Avoid duplicate working_tree entries */
@@ -567,7 +608,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
                                warning(_("both files modified: '%s' and '%s'."),
                                        wtdir.buf, rdir.buf);
                                warning(_("working tree file has been left."));
-                               warning("");
+                               warning("%s", "");
                                err = 1;
                        } else if (unlink(wtdir.buf) ||
                                   copy_file(wtdir.buf, rdir.buf, st.st_mode))
@@ -616,30 +657,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,27 +688,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);
-
        git_config(difftool_config, NULL);
        symlinks = has_symlinks;
 
@@ -702,6 +698,10 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)
        if (tool_help)
                return print_tool_help();
 
+       /* 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);
+
        if (use_gui_tool && diff_gui_tool && *diff_gui_tool)
                setenv("GIT_DIFF_TOOL", diff_gui_tool, 1);
        else if (difftool_cmd) {