Skip to content

Commit d516c2d

Browse files
dschoJunio C Hamano
authored andcommitted
Teach git-diff-files the new option --no-index
With this flag and given two paths, git-diff-files behaves as a GNU diff lookalike (plus the git goodies like --check, colour, etc.). This flag is also available in git-diff. It also works outside of a git repository. In addition, if git-diff{,-files} is called without revision or stage parameter, and with exactly two paths at least one of which is not tracked, the default is --no-index. So, you can now say git diff /etc/inittab /etc/fstab and it actually works! This also unifies the duplicated argument parsing between cmd_diff_files() and builtin_diff_files(). Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <junkio@cox.net>
1 parent f5a9264 commit d516c2d

File tree

8 files changed

+227
-64
lines changed

8 files changed

+227
-64
lines changed

Documentation/git-diff-files.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ git-diff-files - Compares files in the working tree and the index
88

99
SYNOPSIS
1010
--------
11-
'git-diff-files' [-q] [-0|-1|-2|-3|-c|--cc] [<common diff options>] [<path>...]
11+
'git-diff-files' [-q] [-0|-1|-2|-3|-c|--cc|-n|--no-index] [<common diff options>] [<path>...]
1212

1313
DESCRIPTION
1414
-----------
@@ -36,6 +36,9 @@ omit diff output for unmerged entries and just show "Unmerged".
3636
diff, similar to the way 'diff-tree' shows a merge
3737
commit with these flags.
3838

39+
\-n,\--no-index::
40+
Compare the two given files / directories.
41+
3942
-q::
4043
Remain silent even on nonexistent files
4144

Documentation/git-diff.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ tree and the index file, or the index file and the working tree.
2323
further add to the index but you still haven't. You can
2424
stage these changes by using gitlink:git-add[1].
2525

26+
If exactly two paths are given, and at least one is untracked,
27+
compare the two files / directories. This behavior can be
28+
forced by --no-index.
29+
2630
'git-diff' [--options] --cached [<commit>] [--] [<path>...]::
2731

2832
This form is to view the changes you staged for the next

builtin-diff-files.c

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,42 +10,21 @@
1010
#include "builtin.h"
1111

1212
static const char diff_files_usage[] =
13-
"git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]"
13+
"git-diff-files [-q] [-0/-1/2/3 |-c|--cc|-n|--no-index] [<common diff options>] [<path>...]"
1414
COMMON_DIFF_OPTIONS_HELP;
1515

1616
int cmd_diff_files(int argc, const char **argv, const char *prefix)
1717
{
1818
struct rev_info rev;
19-
int silent = 0;
19+
int nongit = 0;
2020

21+
prefix = setup_git_directory_gently(&nongit);
2122
init_revisions(&rev, prefix);
2223
git_config(git_default_config); /* no "diff" UI options */
2324
rev.abbrev = 0;
2425

2526
argc = setup_revisions(argc, argv, &rev, NULL);
26-
while (1 < argc && argv[1][0] == '-') {
27-
if (!strcmp(argv[1], "--base"))
28-
rev.max_count = 1;
29-
else if (!strcmp(argv[1], "--ours"))
30-
rev.max_count = 2;
31-
else if (!strcmp(argv[1], "--theirs"))
32-
rev.max_count = 3;
33-
else if (!strcmp(argv[1], "-q"))
34-
silent = 1;
35-
else
36-
usage(diff_files_usage);
37-
argv++; argc--;
38-
}
3927
if (!rev.diffopt.output_format)
4028
rev.diffopt.output_format = DIFF_FORMAT_RAW;
41-
42-
/*
43-
* Make sure there are NO revision (i.e. pending object) parameter,
44-
* rev.max_count is reasonable (0 <= n <= 3),
45-
* there is no other revision filtering parameters.
46-
*/
47-
if (rev.pending.nr ||
48-
rev.min_age != -1 || rev.max_age != -1)
49-
usage(diff_files_usage);
50-
return run_diff_files(&rev, silent);
29+
return run_diff_files_cmd(&rev, argc, argv);
5130
}

builtin-diff.c

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,40 +25,6 @@ struct blobinfo {
2525
static const char builtin_diff_usage[] =
2626
"git-diff <options> <rev>{0,2} -- <path>*";
2727

28-
static int builtin_diff_files(struct rev_info *revs,
29-
int argc, const char **argv)
30-
{
31-
int silent = 0;
32-
while (1 < argc) {
33-
const char *arg = argv[1];
34-
if (!strcmp(arg, "--base"))
35-
revs->max_count = 1;
36-
else if (!strcmp(arg, "--ours"))
37-
revs->max_count = 2;
38-
else if (!strcmp(arg, "--theirs"))
39-
revs->max_count = 3;
40-
else if (!strcmp(arg, "-q"))
41-
silent = 1;
42-
else
43-
usage(builtin_diff_usage);
44-
argv++; argc--;
45-
}
46-
/*
47-
* Make sure there are NO revision (i.e. pending object) parameter,
48-
* specified rev.max_count is reasonable (0 <= n <= 3), and
49-
* there is no other revision filtering parameter.
50-
*/
51-
if (revs->pending.nr ||
52-
revs->min_age != -1 ||
53-
revs->max_age != -1 ||
54-
3 < revs->max_count)
55-
usage(builtin_diff_usage);
56-
if (revs->max_count < 0 &&
57-
(revs->diffopt.output_format & DIFF_FORMAT_PATCH))
58-
revs->combine_merges = revs->dense_combined_merges = 1;
59-
return run_diff_files(revs, silent);
60-
}
61-
6228
static void stuff_change(struct diff_options *opt,
6329
unsigned old_mode, unsigned new_mode,
6430
const unsigned char *old_sha1,
@@ -218,6 +184,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
218184
int ents = 0, blobs = 0, paths = 0;
219185
const char *path = NULL;
220186
struct blobinfo blob[2];
187+
int nongit = 0;
221188

222189
/*
223190
* We could get N tree-ish in the rev.pending_objects list.
@@ -239,6 +206,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
239206
* Other cases are errors.
240207
*/
241208

209+
prefix = setup_git_directory_gently(&nongit);
242210
git_config(git_diff_ui_config);
243211
init_revisions(&rev, prefix);
244212

@@ -314,7 +282,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
314282
if (!ents) {
315283
switch (blobs) {
316284
case 0:
317-
return builtin_diff_files(&rev, argc, argv);
285+
return run_diff_files_cmd(&rev, argc, argv);
318286
break;
319287
case 1:
320288
if (paths != 1)

diff-lib.c

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,218 @@
88
#include "diffcore.h"
99
#include "revision.h"
1010
#include "cache-tree.h"
11+
#include "path-list.h"
1112

1213
/*
1314
* diff-files
1415
*/
1516

17+
static int read_directory(const char *path, struct path_list *list)
18+
{
19+
DIR *dir;
20+
struct dirent *e;
21+
22+
if (!(dir = opendir(path)))
23+
return error("Could not open directory %s", path);
24+
25+
while ((e = readdir(dir)))
26+
if (strcmp(".", e->d_name) && strcmp("..", e->d_name))
27+
path_list_insert(xstrdup(e->d_name), list);
28+
29+
closedir(dir);
30+
return 0;
31+
}
32+
33+
static int queue_diff(struct diff_options *o,
34+
const char *name1, const char *name2)
35+
{
36+
struct stat st;
37+
int mode1 = 0, mode2 = 0;
38+
39+
if (name1) {
40+
if (stat(name1, &st))
41+
return error("Could not access '%s'", name1);
42+
mode1 = st.st_mode;
43+
}
44+
if (name2) {
45+
if (stat(name2, &st))
46+
return error("Could not access '%s'", name1);
47+
mode2 = st.st_mode;
48+
}
49+
50+
if (mode1 && mode2 && S_ISDIR(mode1) != S_ISDIR(mode2))
51+
return error("file/directory conflict: %s, %s", name1, name2);
52+
53+
if (S_ISDIR(mode1) || S_ISDIR(mode2)) {
54+
char buffer1[PATH_MAX], buffer2[PATH_MAX];
55+
struct path_list p1 = {NULL, 0, 0, 1}, p2 = {NULL, 0, 0, 1};
56+
int len1 = 0, len2 = 0, i1, i2, ret = 0;
57+
58+
if (name1 && read_directory(name1, &p1))
59+
return -1;
60+
if (name2 && read_directory(name2, &p2)) {
61+
path_list_clear(&p1, 0);
62+
return -1;
63+
}
64+
65+
if (name1) {
66+
len1 = strlen(name1);
67+
if (len1 > 0 && name1[len1 - 1] == '/')
68+
len1--;
69+
memcpy(buffer1, name1, len1);
70+
buffer1[len1++] = '/';
71+
}
72+
73+
if (name2) {
74+
len2 = strlen(name2);
75+
if (len2 > 0 && name2[len2 - 1] == '/')
76+
len2--;
77+
memcpy(buffer2, name2, len2);
78+
buffer2[len2++] = '/';
79+
}
80+
81+
for (i1 = i2 = 0; !ret && (i1 < p1.nr || i2 < p2.nr); ) {
82+
const char *n1, *n2;
83+
int comp;
84+
85+
if (i1 == p1.nr)
86+
comp = 1;
87+
else if (i2 == p2.nr)
88+
comp = -1;
89+
else
90+
comp = strcmp(p1.items[i1].path,
91+
p2.items[i2].path);
92+
93+
if (comp > 0)
94+
n1 = NULL;
95+
else {
96+
n1 = buffer1;
97+
strncpy(buffer1 + len1, p1.items[i1++].path,
98+
PATH_MAX - len1);
99+
}
100+
101+
if (comp < 0)
102+
n2 = NULL;
103+
else {
104+
n2 = buffer2;
105+
strncpy(buffer2 + len2, p2.items[i2++].path,
106+
PATH_MAX - len2);
107+
}
108+
109+
ret = queue_diff(o, n1, n2);
110+
}
111+
path_list_clear(&p1, 0);
112+
path_list_clear(&p2, 0);
113+
114+
return ret;
115+
} else {
116+
struct diff_filespec *d1, *d2;
117+
118+
if (o->reverse_diff) {
119+
unsigned tmp;
120+
const char *tmp_c;
121+
tmp = mode1; mode1 = mode2; mode2 = tmp;
122+
tmp_c = name1; name1 = name2; name2 = tmp_c;
123+
}
124+
125+
if (!name1)
126+
name1 = "/dev/null";
127+
if (!name2)
128+
name2 = "/dev/null";
129+
d1 = alloc_filespec(name1);
130+
d2 = alloc_filespec(name2);
131+
fill_filespec(d1, null_sha1, mode1);
132+
fill_filespec(d2, null_sha1, mode2);
133+
134+
diff_queue(&diff_queued_diff, d1, d2);
135+
return 0;
136+
}
137+
}
138+
139+
static int is_in_index(const char *path)
140+
{
141+
int len = strlen(path);
142+
int pos = cache_name_pos(path, len);
143+
char c;
144+
145+
if (pos < 0)
146+
return 0;
147+
if (strncmp(active_cache[pos]->name, path, len))
148+
return 0;
149+
c = active_cache[pos]->name[len];
150+
return c == '\0' || c == '/';
151+
}
152+
153+
static int handle_diff_files_args(struct rev_info *revs,
154+
int argc, const char **argv, int *silent)
155+
{
156+
*silent = 0;
157+
158+
/* revs->max_count == -2 means --no-index */
159+
while (1 < argc && argv[1][0] == '-') {
160+
if (!strcmp(argv[1], "--base"))
161+
revs->max_count = 1;
162+
else if (!strcmp(argv[1], "--ours"))
163+
revs->max_count = 2;
164+
else if (!strcmp(argv[1], "--theirs"))
165+
revs->max_count = 3;
166+
else if (!strcmp(argv[1], "-n") ||
167+
!strcmp(argv[1], "--no-index"))
168+
revs->max_count = -2;
169+
else if (!strcmp(argv[1], "-q"))
170+
*silent = 1;
171+
else
172+
return error("invalid option: %s", argv[1]);
173+
argv++; argc--;
174+
}
175+
176+
if (revs->max_count == -1 && revs->diffopt.nr_paths == 2) {
177+
/*
178+
* If two files are specified, and at least one is untracked,
179+
* default to no-index.
180+
*/
181+
read_cache();
182+
if (!is_in_index(revs->diffopt.paths[0]) ||
183+
!is_in_index(revs->diffopt.paths[1]))
184+
revs->max_count = -2;
185+
}
186+
187+
/*
188+
* Make sure there are NO revision (i.e. pending object) parameter,
189+
* rev.max_count is reasonable (0 <= n <= 3),
190+
* there is no other revision filtering parameters.
191+
*/
192+
if (revs->pending.nr || revs->max_count > 3 ||
193+
revs->min_age != -1 || revs->max_age != -1)
194+
return error("no revision allowed with diff-files");
195+
196+
if (revs->max_count == -1 &&
197+
(revs->diffopt.output_format & DIFF_FORMAT_PATCH))
198+
revs->combine_merges = revs->dense_combined_merges = 1;
199+
200+
return 0;
201+
}
202+
203+
int run_diff_files_cmd(struct rev_info *revs, int argc, const char **argv)
204+
{
205+
int silent_on_removed;
206+
207+
if (handle_diff_files_args(revs, argc, argv, &silent_on_removed))
208+
return -1;
209+
210+
if (revs->max_count == -2) {
211+
if (revs->diffopt.nr_paths != 2)
212+
return error("need two files/directories with --no-index");
213+
queue_diff(&revs->diffopt, revs->diffopt.paths[0],
214+
revs->diffopt.paths[1]);
215+
diffcore_std(&revs->diffopt);
216+
diff_flush(&revs->diffopt);
217+
return 0;
218+
}
219+
220+
return run_diff_files(revs, silent_on_removed);
221+
}
222+
16223
int run_diff_files(struct rev_info *revs, int silent_on_removed)
17224
{
18225
int entries, i;

diff.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2406,7 +2406,8 @@ static void diff_resolve_rename_copy(void)
24062406
p->status = DIFF_STATUS_RENAMED;
24072407
}
24082408
else if (hashcmp(p->one->sha1, p->two->sha1) ||
2409-
p->one->mode != p->two->mode)
2409+
p->one->mode != p->two->mode ||
2410+
is_null_sha1(p->one->sha1))
24102411
p->status = DIFF_STATUS_MODIFIED;
24112412
else {
24122413
/* This is a "no-change" entry and should not

diff.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ extern void diff_flush(struct diff_options*);
219219
extern const char *diff_unique_abbrev(const unsigned char *, int);
220220

221221
extern int run_diff_files(struct rev_info *revs, int silent_on_removed);
222+
extern int run_diff_files_cmd(struct rev_info *revs, int argc, const char **argv);
222223

223224
extern int run_diff_index(struct rev_info *revs, int cached);
224225

git.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
237237
{ "config", cmd_config },
238238
{ "count-objects", cmd_count_objects, RUN_SETUP },
239239
{ "describe", cmd_describe, RUN_SETUP },
240-
{ "diff", cmd_diff, RUN_SETUP | USE_PAGER },
241-
{ "diff-files", cmd_diff_files, RUN_SETUP },
240+
{ "diff", cmd_diff, USE_PAGER },
241+
{ "diff-files", cmd_diff_files },
242242
{ "diff-index", cmd_diff_index, RUN_SETUP },
243243
{ "diff-tree", cmd_diff_tree, RUN_SETUP },
244244
{ "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP },

0 commit comments

Comments
 (0)