@@ -97,8 +97,27 @@ static int queue_diff(struct diff_options *o,
9797 if (get_mode (name1 , & mode1 ) || get_mode (name2 , & mode2 ))
9898 return -1 ;
9999
100- if (mode1 && mode2 && S_ISDIR (mode1 ) != S_ISDIR (mode2 ))
101- return error ("file/directory conflict: %s, %s" , name1 , name2 );
100+ if (mode1 && mode2 && S_ISDIR (mode1 ) != S_ISDIR (mode2 )) {
101+ struct diff_filespec * d1 , * d2 ;
102+
103+ if (S_ISDIR (mode1 )) {
104+ /* 2 is file that is created */
105+ d1 = noindex_filespec (NULL , 0 );
106+ d2 = noindex_filespec (name2 , mode2 );
107+ name2 = NULL ;
108+ mode2 = 0 ;
109+ } else {
110+ /* 1 is file that is deleted */
111+ d1 = noindex_filespec (name1 , mode1 );
112+ d2 = noindex_filespec (NULL , 0 );
113+ name1 = NULL ;
114+ mode1 = 0 ;
115+ }
116+ /* emit that file */
117+ diff_queue (& diff_queued_diff , d1 , d2 );
118+
119+ /* and then let the entire directory be created or deleted */
120+ }
102121
103122 if (S_ISDIR (mode1 ) || S_ISDIR (mode2 )) {
104123 struct strbuf buffer1 = STRBUF_INIT ;
@@ -182,12 +201,50 @@ static int queue_diff(struct diff_options *o,
182201 }
183202}
184203
204+ /* append basename of F to D */
205+ static void append_basename (struct strbuf * path , const char * dir , const char * file )
206+ {
207+ const char * tail = strrchr (file , '/' );
208+
209+ strbuf_addstr (path , dir );
210+ while (path -> len && path -> buf [path -> len - 1 ] == '/' )
211+ path -> len -- ;
212+ strbuf_addch (path , '/' );
213+ strbuf_addstr (path , tail ? tail + 1 : file );
214+ }
215+
216+ /*
217+ * DWIM "diff D F" into "diff D/F F" and "diff F D" into "diff F D/F"
218+ * Note that we append the basename of F to D/, so "diff a/b/file D"
219+ * becomes "diff a/b/file D/file", not "diff a/b/file D/a/b/file".
220+ */
221+ static void fixup_paths (const char * * path , struct strbuf * replacement )
222+ {
223+ unsigned int isdir0 , isdir1 ;
224+
225+ if (path [0 ] == file_from_standard_input ||
226+ path [1 ] == file_from_standard_input )
227+ return ;
228+ isdir0 = is_directory (path [0 ]);
229+ isdir1 = is_directory (path [1 ]);
230+ if (isdir0 == isdir1 )
231+ return ;
232+ if (isdir0 ) {
233+ append_basename (replacement , path [0 ], path [1 ]);
234+ path [0 ] = replacement -> buf ;
235+ } else {
236+ append_basename (replacement , path [1 ], path [0 ]);
237+ path [1 ] = replacement -> buf ;
238+ }
239+ }
240+
185241void diff_no_index (struct rev_info * revs ,
186242 int argc , const char * * argv ,
187243 const char * prefix )
188244{
189245 int i , prefixlen ;
190246 const char * paths [2 ];
247+ struct strbuf replacement = STRBUF_INIT ;
191248
192249 diff_setup (& revs -> diffopt );
193250 for (i = 1 ; i < argc - 2 ; ) {
@@ -217,6 +274,9 @@ void diff_no_index(struct rev_info *revs,
217274 p = xstrdup (prefix_filename (prefix , prefixlen , p ));
218275 paths [i ] = p ;
219276 }
277+
278+ fixup_paths (paths , & replacement );
279+
220280 revs -> diffopt .skip_stat_unmatch = 1 ;
221281 if (!revs -> diffopt .output_format )
222282 revs -> diffopt .output_format = DIFF_FORMAT_PATCH ;
@@ -235,6 +295,8 @@ void diff_no_index(struct rev_info *revs,
235295 diffcore_std (& revs -> diffopt );
236296 diff_flush (& revs -> diffopt );
237297
298+ strbuf_release (& replacement );
299+
238300 /*
239301 * The return code for --no-index imitates diff(1):
240302 * 0 = no changes, 1 = changes, else error
0 commit comments