wrapper.c: add and use fopen_or_warn()
[gitweb.git] / builtin / difftool.c
index d13350ce832cd8c0064c8a5b2d3c9be64f876b8d..1354d0e4625c855676e7812be92d7c77bdecb407 100644 (file)
@@ -254,6 +254,62 @@ 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 checkout_path(unsigned mode, struct object_id *oid,
+                        const char *path, const struct checkout *state)
+{
+       struct cache_entry *ce;
+       int ret;
+
+       ce = make_cache_entry(mode, oid->hash, path, 0, 0);
+       ret = checkout_entry(ce, state, NULL);
+
+       free(ce);
+       return ret;
+}
+
 static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
                        int argc, const char **argv)
 {
@@ -262,16 +318,14 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
        struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;
        struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;
        struct strbuf wtdir = STRBUF_INIT;
+       char *lbase_dir, *rbase_dir;
        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;
@@ -298,11 +352,11 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
        memset(&wtindex, 0, sizeof(wtindex));
 
        memset(&lstate, 0, sizeof(lstate));
-       lstate.base_dir = ldir.buf;
+       lstate.base_dir = lbase_dir = xstrdup(ldir.buf);
        lstate.base_dir_len = ldir.len;
        lstate.force = 1;
        memset(&rstate, 0, sizeof(rstate));
-       rstate.base_dir = rdir.buf;
+       rstate.base_dir = rbase_dir = xstrdup(rdir.buf);
        rstate.base_dir_len = rdir.len;
        rstate.force = 1;
 
@@ -336,7 +390,6 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
                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 "
@@ -349,17 +402,14 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
                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)) {
@@ -377,27 +427,23 @@ 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);
                }
 
                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))
+                       if (checkout_path(lmode, &loid, src_path, &lstate))
                                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 */
@@ -410,11 +456,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
                        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))
+                               if (checkout_path(rmode, &roid, dst_path, &rstate))
                                        return error("could not write '%s'",
                                                     dst_path);
                        } else if (!is_null_oid(&roid)) {
@@ -584,7 +626,8 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
                exit_cleanup(tmpdir, rc);
 
 finish:
-       free(ce);
+       free(lbase_dir);
+       free(rbase_dir);
        strbuf_release(&ldir);
        strbuf_release(&rdir);
        strbuf_release(&wtdir);