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.schindelin@xxxxxx> Signed-off-by: David Aguilar <davvid@xxxxxxxxx> --- 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