Detect the null object ID for symlinks in dir-diff so that difftool can
prepare temporary files that matches how git handles symlinks.

Previously, a null object ID would crash difftool.  We now detect null
object IDs and write the symlink's content into the temporary symlink
stand-in file.

Original-patch-by: Johannes Schindelin <johannes.schinde...@gmx.de>
Signed-off-by: David Aguilar <dav...@gmail.com>
---
 builtin/difftool.c  | 36 +++++++++++++++++++++++++++++++++---
 t/t7800-difftool.sh | 40 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 73 insertions(+), 3 deletions(-)

diff --git a/builtin/difftool.c b/builtin/difftool.c
index d13350ce83..6c20e20b45 100644
--- a/builtin/difftool.c
+++ b/builtin/difftool.c
@@ -254,6 +254,31 @@ static int ensure_leading_directories(char *path)
        }
 }
 
+static int create_symlink_file(struct cache_entry* ce, struct checkout* state)
+{
+       /*
+        * Dereference a worktree symlink and writes its contents
+        * into the checkout state's path.
+        */
+       struct strbuf path = STRBUF_INIT;
+       struct strbuf link = STRBUF_INIT;
+
+       int ok = 0;
+
+       if (strbuf_readlink(&link, ce->name, ce_namelen(ce)) == 0) {
+               strbuf_add(&path, state->base_dir, state->base_dir_len);
+               strbuf_add(&path, ce->name, ce_namelen(ce));
+
+               write_file_buf(path.buf, link.buf, link.len);
+               ok = 1;
+       }
+
+       strbuf_release(&path);
+       strbuf_release(&link);
+
+       return ok;
+}
+
 static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
                        int argc, const char **argv)
 {
@@ -376,13 +401,13 @@ static int run_dir_diff(const char *extcmd, int symlinks, 
const char *prefix,
                        continue;
                }
 
-               if (S_ISLNK(lmode)) {
+               if (S_ISLNK(lmode) && !is_null_oid(&loid)) {
                        char *content = read_sha1_file(loid.hash, &type, &size);
                        add_left_or_right(&symlinks2, src_path, content, 0);
                        free(content);
                }
 
-               if (S_ISLNK(rmode)) {
+               if (S_ISLNK(rmode) && !is_null_oid(&roid)) {
                        char *content = read_sha1_file(roid.hash, &type, &size);
                        add_left_or_right(&symlinks2, dst_path, content, 1);
                        free(content);
@@ -414,7 +439,12 @@ static int run_dir_diff(const char *extcmd, int symlinks, 
const char *prefix,
                                oidcpy(&ce->oid, &roid);
                                strcpy(ce->name, dst_path);
                                ce->ce_namelen = dst_path_len;
-                               if (checkout_entry(ce, &rstate, NULL))
+
+                               if (S_ISLNK(rmode) && is_null_oid(&roid)) {
+                                       if (!create_symlink_file(ce, &rstate))
+                                               return error("unable to create 
symlink file %s",
+                                                            dst_path);
+                               } else if (checkout_entry(ce, &rstate, NULL))
                                        return error("could not write '%s'",
                                                     dst_path);
                        } else if (!is_null_oid(&roid)) {
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index e1ec292718..64f8e451b5 100755
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
@@ -623,4 +623,44 @@ test_expect_success SYMLINKS 'difftool --dir-diff 
symlinked directories' '
        )
 '
 
+test_expect_success SYMLINKS 'difftool --dir-diff' '
+       touch b &&
+       ln -s b c &&
+       git add . &&
+       test_tick &&
+       git commit -m initial &&
+       touch d &&
+       rm c &&
+       ln -s d c &&
+
+       git difftool --dir-diff --extcmd ls >output &&
+       grep -v ^/ output >actual &&
+       cat >expect <<-EOF &&
+               b
+               c
+               dirlinks
+               output
+               submod
+
+               c
+               dirlinks
+               output
+               submod
+       EOF
+       test_cmp expect actual &&
+
+       # The left side contains symlink "c" that points to "b"
+       test_config difftool.cat.cmd "cat \$LOCAL/c" &&
+       git difftool --dir-diff --tool cat >actual &&
+       echo b >expect &&
+       test_cmp expect actual &&
+
+       # The right side contains symlink "c" that points to "d",
+       # which mimics the state of the worktree.
+       test_config difftool.cat.cmd "cat \$REMOTE/c" &&
+       git difftool --dir-diff --tool cat >actual &&
+       echo -n d >expect &&
+       test_cmp expect actual
+'
+
 test_done
-- 
2.12.0.266.g44c9eec009

Reply via email to