Git fork
1/*
2 * "diff --no-index" support
3 * Copyright (c) 2007 by Johannes Schindelin
4 * Copyright (c) 2008 by Junio C Hamano
5 */
6
7#define DISABLE_SIGN_COMPARE_WARNINGS
8
9#include "git-compat-util.h"
10#include "abspath.h"
11#include "color.h"
12#include "commit.h"
13#include "diff.h"
14#include "diffcore.h"
15#include "gettext.h"
16#include "revision.h"
17#include "parse-options.h"
18#include "pathspec.h"
19#include "string-list.h"
20#include "dir.h"
21
22static int read_directory_contents(const char *path, struct string_list *list,
23 const struct pathspec *pathspec,
24 struct strbuf *match)
25{
26 int len = match->len;
27 DIR *dir;
28 struct dirent *e;
29
30 if (!(dir = opendir(path)))
31 return error("Could not open directory %s", path);
32
33 while ((e = readdir_skip_dot_and_dotdot(dir))) {
34 if (pathspec) {
35 int is_dir = 0;
36
37 strbuf_setlen(match, len);
38 strbuf_addstr(match, e->d_name);
39 if (NOT_CONSTANT(DTYPE(e)) != DT_UNKNOWN) {
40 is_dir = (DTYPE(e) == DT_DIR);
41 } else {
42 struct strbuf pathbuf = STRBUF_INIT;
43
44 strbuf_addstr(&pathbuf, path);
45 strbuf_complete(&pathbuf, '/');
46 is_dir = get_dtype(e, &pathbuf, 0) == DT_DIR;
47 strbuf_release(&pathbuf);
48 }
49
50 if (!match_leading_pathspec(NULL, pathspec,
51 match->buf, match->len,
52 0, NULL, is_dir))
53 continue;
54 }
55
56 string_list_insert(list, e->d_name);
57 }
58
59 strbuf_setlen(match, len);
60 closedir(dir);
61 return 0;
62}
63
64/*
65 * This should be "(standard input)" or something, but it will
66 * probably expose many more breakages in the way no-index code
67 * is bolted onto the diff callchain.
68 */
69static const char file_from_standard_input[] = "-";
70
71/*
72 * For paths given on the command-line we treat "-" as stdin and named
73 * pipes and symbolic links to named pipes specially.
74 */
75enum special {
76 SPECIAL_NONE,
77 SPECIAL_STDIN,
78 SPECIAL_PIPE,
79};
80
81static int get_mode(const char *path, int *mode, enum special *special)
82{
83 struct stat st;
84
85 if (!path || !strcmp(path, "/dev/null")) {
86 *mode = 0;
87#ifdef GIT_WINDOWS_NATIVE
88 } else if (!strcasecmp(path, "nul")) {
89 *mode = 0;
90#endif
91 } else if (path == file_from_standard_input) {
92 *mode = create_ce_mode(0666);
93 *special = SPECIAL_STDIN;
94 } else if (lstat(path, &st)) {
95 return error("Could not access '%s'", path);
96 } else {
97 *mode = st.st_mode;
98 }
99 /*
100 * For paths on the command-line treat named pipes and symbolic
101 * links that resolve to a named pipe specially.
102 */
103 if (special &&
104 (S_ISFIFO(*mode) ||
105 (S_ISLNK(*mode) && !stat(path, &st) && S_ISFIFO(st.st_mode)))) {
106 *mode = create_ce_mode(0666);
107 *special = SPECIAL_PIPE;
108 }
109
110 return 0;
111}
112
113static void populate_common(struct diff_filespec *s, struct strbuf *buf)
114{
115 size_t size = 0;
116
117 s->should_munmap = 0;
118 s->data = strbuf_detach(buf, &size);
119 s->size = size;
120 s->should_free = 1;
121 s->is_stdin = 1;
122}
123
124static void populate_from_pipe(struct diff_filespec *s)
125{
126 struct strbuf buf = STRBUF_INIT;
127 int fd = xopen(s->path, O_RDONLY);
128
129 if (strbuf_read(&buf, fd, 0) < 0)
130 die_errno("error while reading from '%s'", s->path);
131 close(fd);
132 populate_common(s, &buf);
133}
134
135static void populate_from_stdin(struct diff_filespec *s)
136{
137 struct strbuf buf = STRBUF_INIT;
138
139 if (strbuf_read(&buf, 0, 0) < 0)
140 die_errno("error while reading from stdin");
141 populate_common(s, &buf);
142}
143
144static struct diff_filespec *noindex_filespec(const struct git_hash_algo *algop,
145 const char *name, int mode,
146 enum special special)
147{
148 struct diff_filespec *s;
149
150 if (!name)
151 name = "/dev/null";
152 s = alloc_filespec(name);
153 fill_filespec(s, null_oid(algop), 0, mode);
154 if (special == SPECIAL_STDIN)
155 populate_from_stdin(s);
156 else if (special == SPECIAL_PIPE)
157 populate_from_pipe(s);
158 return s;
159}
160
161static int queue_diff(struct diff_options *o, const struct git_hash_algo *algop,
162 const char *name1, const char *name2, int recursing,
163 const struct pathspec *ps,
164 struct strbuf *ps_match1, struct strbuf *ps_match2)
165{
166 int mode1 = 0, mode2 = 0;
167 enum special special1 = SPECIAL_NONE, special2 = SPECIAL_NONE;
168
169 /* Paths can only be special if we're not recursing. */
170 if (get_mode(name1, &mode1, recursing ? NULL : &special1) ||
171 get_mode(name2, &mode2, recursing ? NULL : &special2))
172 return -1;
173
174 if (mode1 && mode2 && S_ISDIR(mode1) != S_ISDIR(mode2)) {
175 struct diff_filespec *d1, *d2;
176
177 if (S_ISDIR(mode1)) {
178 /* 2 is file that is created */
179 d1 = noindex_filespec(algop, NULL, 0, SPECIAL_NONE);
180 d2 = noindex_filespec(algop, name2, mode2, special2);
181 name2 = NULL;
182 mode2 = 0;
183 } else {
184 /* 1 is file that is deleted */
185 d1 = noindex_filespec(algop, name1, mode1, special1);
186 d2 = noindex_filespec(algop, NULL, 0, SPECIAL_NONE);
187 name1 = NULL;
188 mode1 = 0;
189 }
190 /* emit that file */
191 diff_queue(&diff_queued_diff, d1, d2);
192
193 /* and then let the entire directory be created or deleted */
194 }
195
196 if (S_ISDIR(mode1) || S_ISDIR(mode2)) {
197 struct strbuf buffer1 = STRBUF_INIT;
198 struct strbuf buffer2 = STRBUF_INIT;
199 struct string_list p1 = STRING_LIST_INIT_DUP;
200 struct string_list p2 = STRING_LIST_INIT_DUP;
201 int i1, i2, ret = 0;
202 size_t len1 = 0, len2 = 0;
203 size_t match1_len = ps_match1->len;
204 size_t match2_len = ps_match2->len;
205
206 if (name1 && read_directory_contents(name1, &p1, ps, ps_match1))
207 return -1;
208 if (name2 && read_directory_contents(name2, &p2, ps, ps_match2)) {
209 string_list_clear(&p1, 0);
210 return -1;
211 }
212
213 if (name1) {
214 strbuf_addstr(&buffer1, name1);
215 strbuf_complete(&buffer1, '/');
216 len1 = buffer1.len;
217 }
218
219 if (name2) {
220 strbuf_addstr(&buffer2, name2);
221 strbuf_complete(&buffer2, '/');
222 len2 = buffer2.len;
223 }
224
225 for (i1 = i2 = 0; !ret && (i1 < p1.nr || i2 < p2.nr); ) {
226 const char *n1, *n2;
227 int comp;
228
229 strbuf_setlen(&buffer1, len1);
230 strbuf_setlen(&buffer2, len2);
231
232 if (ps) {
233 strbuf_setlen(ps_match1, match1_len);
234 strbuf_setlen(ps_match2, match2_len);
235 }
236
237 if (i1 == p1.nr)
238 comp = 1;
239 else if (i2 == p2.nr)
240 comp = -1;
241 else
242 comp = strcmp(p1.items[i1].string, p2.items[i2].string);
243
244 if (comp > 0)
245 n1 = NULL;
246 else {
247 strbuf_addstr(&buffer1, p1.items[i1].string);
248 if (ps) {
249 strbuf_addstr(ps_match1, p1.items[i1].string);
250 strbuf_complete(ps_match1, '/');
251 }
252 n1 = buffer1.buf;
253 i1++;
254 }
255
256 if (comp < 0)
257 n2 = NULL;
258 else {
259 strbuf_addstr(&buffer2, p2.items[i2].string);
260 if (ps) {
261 strbuf_addstr(ps_match2, p2.items[i2].string);
262 strbuf_complete(ps_match2, '/');
263 }
264 n2 = buffer2.buf;
265 i2++;
266 }
267
268 ret = queue_diff(o, algop, n1, n2, 1, ps, ps_match1, ps_match2);
269 }
270 string_list_clear(&p1, 0);
271 string_list_clear(&p2, 0);
272 strbuf_release(&buffer1);
273 strbuf_release(&buffer2);
274
275 return ret;
276 } else {
277 struct diff_filespec *d1, *d2;
278
279 if (o->flags.reverse_diff) {
280 SWAP(mode1, mode2);
281 SWAP(name1, name2);
282 SWAP(special1, special2);
283 }
284
285 d1 = noindex_filespec(algop, name1, mode1, special1);
286 d2 = noindex_filespec(algop, name2, mode2, special2);
287 diff_queue(&diff_queued_diff, d1, d2);
288 return 0;
289 }
290}
291
292/* append basename of F to D */
293static void append_basename(struct strbuf *path, const char *dir, const char *file)
294{
295 const char *tail = strrchr(file, '/');
296
297 strbuf_addstr(path, dir);
298 while (path->len && path->buf[path->len - 1] == '/')
299 path->len--;
300 strbuf_addch(path, '/');
301 strbuf_addstr(path, tail ? tail + 1 : file);
302}
303
304/*
305 * DWIM "diff D F" into "diff D/F F" and "diff F D" into "diff F D/F"
306 * Note that we append the basename of F to D/, so "diff a/b/file D"
307 * becomes "diff a/b/file D/file", not "diff a/b/file D/a/b/file".
308 *
309 * Return 1 if both paths are directories, 0 otherwise.
310 */
311static int fixup_paths(const char **path, struct strbuf *replacement)
312{
313 struct stat st;
314 unsigned int isdir0 = 0, isdir1 = 0;
315 unsigned int ispipe0 = 0, ispipe1 = 0;
316
317 if (path[0] != file_from_standard_input && !stat(path[0], &st)) {
318 isdir0 = S_ISDIR(st.st_mode);
319 ispipe0 = S_ISFIFO(st.st_mode);
320 }
321
322 if (path[1] != file_from_standard_input && !stat(path[1], &st)) {
323 isdir1 = S_ISDIR(st.st_mode);
324 ispipe1 = S_ISFIFO(st.st_mode);
325 }
326
327 if ((path[0] == file_from_standard_input && isdir1) ||
328 (isdir0 && path[1] == file_from_standard_input))
329 die(_("cannot compare stdin to a directory"));
330
331 if ((isdir0 && ispipe1) || (ispipe0 && isdir1))
332 die(_("cannot compare a named pipe to a directory"));
333
334 /* if both paths are directories, we will enable pathspecs */
335 if (isdir0 && isdir1)
336 return 1;
337
338 if (isdir0) {
339 append_basename(replacement, path[0], path[1]);
340 path[0] = replacement->buf;
341 } else if (isdir1) {
342 append_basename(replacement, path[1], path[0]);
343 path[1] = replacement->buf;
344 }
345
346 return 0;
347}
348
349static const char * const diff_no_index_usage[] = {
350 N_("git diff --no-index [<options>] <path> <path> [<pathspec>...]"),
351 NULL
352};
353
354int diff_no_index(struct rev_info *revs, const struct git_hash_algo *algop,
355 int implicit_no_index, int argc, const char **argv)
356{
357 struct pathspec pathspec, *ps = NULL;
358 struct strbuf ps_match1 = STRBUF_INIT, ps_match2 = STRBUF_INIT;
359 int i, no_index;
360 int ret = 1;
361 const char *paths[2];
362 char *to_free[ARRAY_SIZE(paths)] = { 0 };
363 struct strbuf replacement = STRBUF_INIT;
364 const char *prefix = revs->prefix;
365 struct option no_index_options[] = {
366 OPT_BOOL_F(0, "no-index", &no_index, "",
367 PARSE_OPT_NONEG | PARSE_OPT_HIDDEN),
368 OPT_END(),
369 };
370 struct option *options;
371
372 options = add_diff_options(no_index_options, &revs->diffopt);
373 argc = parse_options(argc, argv, revs->prefix, options,
374 diff_no_index_usage, 0);
375 if (argc < 2) {
376 if (implicit_no_index)
377 warning(_("Not a git repository. Use --no-index to "
378 "compare two paths outside a working tree"));
379 usage_with_options(diff_no_index_usage, options);
380 }
381 for (i = 0; i < 2; i++) {
382 const char *p = argv[i];
383 if (!strcmp(p, "-"))
384 /*
385 * stdin should be spelled as "-"; if you have
386 * path that is "-", spell it as "./-".
387 */
388 p = file_from_standard_input;
389 else if (prefix)
390 p = to_free[i] = prefix_filename(prefix, p);
391 paths[i] = p;
392 }
393
394 if (fixup_paths(paths, &replacement)) {
395 parse_pathspec(&pathspec, PATHSPEC_FROMTOP | PATHSPEC_ATTR,
396 PATHSPEC_PREFER_FULL | PATHSPEC_NO_REPOSITORY,
397 NULL, &argv[2]);
398 if (pathspec.nr)
399 ps = &pathspec;
400 } else if (argc > 2) {
401 warning(_("Limiting comparison with pathspecs is only "
402 "supported if both paths are directories."));
403 usage_with_options(diff_no_index_usage, options);
404 }
405 FREE_AND_NULL(options);
406
407 revs->diffopt.skip_stat_unmatch = 1;
408 if (!revs->diffopt.output_format)
409 revs->diffopt.output_format = DIFF_FORMAT_PATCH;
410
411 revs->diffopt.flags.no_index = 1;
412
413 revs->diffopt.flags.relative_name = 1;
414 revs->diffopt.prefix = prefix;
415
416 revs->max_count = -2;
417 diff_setup_done(&revs->diffopt);
418
419 setup_diff_pager(&revs->diffopt);
420 revs->diffopt.flags.exit_with_status = 1;
421
422 if (queue_diff(&revs->diffopt, algop, paths[0], paths[1], 0, ps,
423 &ps_match1, &ps_match2))
424 goto out;
425 diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/");
426 diffcore_std(&revs->diffopt);
427 diff_flush(&revs->diffopt);
428
429 /*
430 * The return code for --no-index imitates diff(1):
431 * 0 = no changes, 1 = changes, else error
432 */
433 ret = diff_result_code(revs);
434
435out:
436 for (i = 0; i < ARRAY_SIZE(to_free); i++)
437 free(to_free[i]);
438 strbuf_release(&replacement);
439 strbuf_release(&ps_match1);
440 strbuf_release(&ps_match2);
441 if (ps)
442 clear_pathspec(ps);
443 return ret;
444}