Skip to content

Commit c9e1f2c

Browse files
committed
diff-no-index: DWIM "diff D F" into "diff D/F F"
"git diff --no-index" was supposed to be a poor-man's approach to allow using Git diff goodies outside of a Git repository, without having to patch mainstream diff implementations. Unlike a POSIX diff that treats "diff D F" (or "diff F D") as a request to compare D/F and F (or F and D/F) when D is a directory and F is a file, however, we did not accept such a command line and instead barfed with "file/directory conflict". Imitate what POSIX diff does and append the basename of the file after the name of the directory before comparing. Signed-off-by: Junio C Hamano <[email protected]>
1 parent 282616c commit c9e1f2c

File tree

2 files changed

+65
-0
lines changed

2 files changed

+65
-0
lines changed

diff-no-index.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,50 @@ static int queue_diff(struct diff_options *o,
182182
}
183183
}
184184

185+
/* append basename of F to D */
186+
static void append_basename(struct strbuf *path, const char *dir, const char *file)
187+
{
188+
const char *tail = strrchr(file, '/');
189+
190+
strbuf_addstr(path, dir);
191+
while (path->len && path->buf[path->len - 1] == '/')
192+
path->len--;
193+
strbuf_addch(path, '/');
194+
strbuf_addstr(path, tail ? tail + 1 : file);
195+
}
196+
197+
/*
198+
* DWIM "diff D F" into "diff D/F F" and "diff F D" into "diff F D/F"
199+
* Note that we append the basename of F to D/, so "diff a/b/file D"
200+
* becomes "diff a/b/file D/file", not "diff a/b/file D/a/b/file".
201+
*/
202+
static void fixup_paths(const char **path, struct strbuf *replacement)
203+
{
204+
unsigned int isdir0, isdir1;
205+
206+
if (path[0] == file_from_standard_input ||
207+
path[1] == file_from_standard_input)
208+
return;
209+
isdir0 = is_directory(path[0]);
210+
isdir1 = is_directory(path[1]);
211+
if (isdir0 == isdir1)
212+
return;
213+
if (isdir0) {
214+
append_basename(replacement, path[0], path[1]);
215+
path[0] = replacement->buf;
216+
} else {
217+
append_basename(replacement, path[1], path[0]);
218+
path[1] = replacement->buf;
219+
}
220+
}
221+
185222
void diff_no_index(struct rev_info *revs,
186223
int argc, const char **argv,
187224
const char *prefix)
188225
{
189226
int i, prefixlen;
190227
const char *paths[2];
228+
struct strbuf replacement = STRBUF_INIT;
191229

192230
diff_setup(&revs->diffopt);
193231
for (i = 1; i < argc - 2; ) {
@@ -217,6 +255,9 @@ void diff_no_index(struct rev_info *revs,
217255
p = xstrdup(prefix_filename(prefix, prefixlen, p));
218256
paths[i] = p;
219257
}
258+
259+
fixup_paths(paths, &replacement);
260+
220261
revs->diffopt.skip_stat_unmatch = 1;
221262
if (!revs->diffopt.output_format)
222263
revs->diffopt.output_format = DIFF_FORMAT_PATCH;
@@ -235,6 +276,8 @@ void diff_no_index(struct rev_info *revs,
235276
diffcore_std(&revs->diffopt);
236277
diff_flush(&revs->diffopt);
237278

279+
strbuf_release(&replacement);
280+
238281
/*
239282
* The return code for --no-index imitates diff(1):
240283
* 0 = no changes, 1 = changes, else error

t/t4053-diff-no-index.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,26 @@ test_expect_success 'git diff --no-index executed outside repo gives correct err
5555
)
5656
'
5757

58+
test_expect_success 'diff D F and diff F D' '
59+
(
60+
cd repo &&
61+
echo in-repo >a &&
62+
echo non-repo >../non/git/a &&
63+
mkdir sub &&
64+
echo sub-repo >sub/a &&
65+
66+
test_must_fail git diff --no-index sub/a ../non/git/a >expect &&
67+
test_must_fail git diff --no-index sub/a ../non/git/ >actual &&
68+
test_cmp expect actual &&
69+
70+
test_must_fail git diff --no-index a ../non/git/a >expect &&
71+
test_must_fail git diff --no-index a ../non/git/ >actual &&
72+
test_cmp expect actual &&
73+
74+
test_must_fail git diff --no-index ../non/git/a a >expect &&
75+
test_must_fail git diff --no-index ../non/git a >actual &&
76+
test_cmp expect actual
77+
)
78+
'
79+
5880
test_done

0 commit comments

Comments
 (0)