| 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 | |
| 22 | static 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 | */ |
| 69 | static 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 | */ |
| 75 | enum special { |
| 76 | SPECIAL_NONE, |
| 77 | SPECIAL_STDIN, |
| 78 | SPECIAL_PIPE, |
| 79 | }; |
| 80 | |
| 81 | static 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 | |
| 113 | static 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 | |
| 124 | static 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 | |
| 135 | static 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 | |
| 144 | static 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 | |
| 161 | static 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 */ |
| 293 | static 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 | */ |
| 311 | static 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 | |
| 349 | static const char * const diff_no_index_usage[] = { |
| 350 | N_("git diff --no-index [<options>] <path> <path> [<pathspec>...]"), |
| 351 | NULL |
| 352 | }; |
| 353 | |
| 354 | int 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 | |
| 435 | out: |
| 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 | } |