@@ -254,6 +254,49 @@ static int ensure_leading_directories(char *path)
254254 }
255255}
256256
257+ /*
258+ * Unconditional writing of a plain regular file is what
259+ * "git difftool --dir-diff" wants to do for symlinks. We are preparing two
260+ * temporary directories to be fed to a Git-unaware tool that knows how to
261+ * show a diff of two directories (e.g. "diff -r A B").
262+ *
263+ * Because the tool is Git-unaware, if a symbolic link appears in either of
264+ * these temporary directories, it will try to dereference and show the
265+ * difference of the target of the symbolic link, which is not what we want,
266+ * as the goal of the dir-diff mode is to produce an output that is logically
267+ * equivalent to what "git diff" produces.
268+ *
269+ * Most importantly, we want to get textual comparison of the result of the
270+ * readlink(2). get_symlink() provides that---it returns the contents of
271+ * the symlink that gets written to a regular file to force the external tool
272+ * to compare the readlink(2) result as text, even on a filesystem that is
273+ * capable of doing a symbolic link.
274+ */
275+ static char * get_symlink (const struct object_id * oid , const char * path )
276+ {
277+ char * data ;
278+ if (is_null_oid (oid )) {
279+ /* The symlink is unknown to Git so read from the filesystem */
280+ struct strbuf link = STRBUF_INIT ;
281+ if (has_symlinks ) {
282+ if (strbuf_readlink (& link , path , strlen (path )))
283+ die (_ ("could not read symlink %s" ), path );
284+ } else if (strbuf_read_file (& link , path , 128 ))
285+ die (_ ("could not read symlink file %s" ), path );
286+
287+ data = strbuf_detach (& link , NULL );
288+ } else {
289+ enum object_type type ;
290+ unsigned long size ;
291+ data = read_sha1_file (oid -> hash , & type , & size );
292+ if (!data )
293+ die (_ ("could not read object %s for symlink %s" ),
294+ oid_to_hex (oid ), path );
295+ }
296+
297+ return data ;
298+ }
299+
257300static int run_dir_diff (const char * extcmd , int symlinks , const char * prefix ,
258301 int argc , const char * * argv )
259302{
@@ -270,8 +313,6 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
270313 struct hashmap working_tree_dups , submodules , symlinks2 ;
271314 struct hashmap_iter iter ;
272315 struct pair_entry * entry ;
273- enum object_type type ;
274- unsigned long size ;
275316 struct index_state wtindex ;
276317 struct checkout lstate , rstate ;
277318 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,
377418 }
378419
379420 if (S_ISLNK (lmode )) {
380- char * content = read_sha1_file ( loid . hash , & type , & size );
421+ char * content = get_symlink ( & loid , src_path );
381422 add_left_or_right (& symlinks2 , src_path , content , 0 );
382423 free (content );
383424 }
384425
385426 if (S_ISLNK (rmode )) {
386- char * content = read_sha1_file ( roid . hash , & type , & size );
427+ char * content = get_symlink ( & roid , dst_path );
387428 add_left_or_right (& symlinks2 , dst_path , content , 1 );
388429 free (content );
389430 }
@@ -397,7 +438,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
397438 return error ("could not write '%s'" , src_path );
398439 }
399440
400- if (rmode ) {
441+ if (rmode && ! S_ISLNK ( rmode ) ) {
401442 struct working_tree_entry * entry ;
402443
403444 /* Avoid duplicate working_tree entries */
0 commit comments