Git fork
at reftables-rust 651 lines 18 kB view raw
1/* 2 * Builtin "git diff" 3 * 4 * Copyright (c) 2006 Junio C Hamano 5 */ 6 7#define USE_THE_REPOSITORY_VARIABLE 8#define DISABLE_SIGN_COMPARE_WARNINGS 9 10#include "builtin.h" 11#include "config.h" 12#include "ewah/ewok.h" 13#include "lockfile.h" 14#include "color.h" 15#include "commit.h" 16#include "gettext.h" 17#include "tag.h" 18#include "diff.h" 19#include "diff-merges.h" 20#include "diffcore.h" 21#include "preload-index.h" 22#include "read-cache-ll.h" 23#include "revision.h" 24#include "log-tree.h" 25#include "setup.h" 26#include "oid-array.h" 27#include "tree.h" 28 29#define DIFF_NO_INDEX_EXPLICIT 1 30#define DIFF_NO_INDEX_IMPLICIT 2 31 32static const char builtin_diff_usage[] = 33"git diff [<options>] [<commit>] [--] [<path>...]\n" 34" or: git diff [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]\n" 35" or: git diff [<options>] [--merge-base] <commit> [<commit>...] <commit> [--] [<path>...]\n" 36" or: git diff [<options>] <commit>...<commit> [--] [<path>...]\n" 37" or: git diff [<options>] <blob> <blob>\n" 38" or: git diff [<options>] --no-index [--] <path> <path> [<pathspec>...]" 39"\n" 40COMMON_DIFF_OPTIONS_HELP; 41 42static const char *blob_path(struct object_array_entry *entry) 43{ 44 return entry->path ? entry->path : entry->name; 45} 46 47static void stuff_change(struct diff_options *opt, 48 unsigned old_mode, unsigned new_mode, 49 const struct object_id *old_oid, 50 const struct object_id *new_oid, 51 int old_oid_valid, 52 int new_oid_valid, 53 const char *old_path, 54 const char *new_path) 55{ 56 struct diff_filespec *one, *two; 57 58 if (!is_null_oid(old_oid) && !is_null_oid(new_oid) && 59 oideq(old_oid, new_oid) && (old_mode == new_mode)) 60 return; 61 62 if (opt->flags.reverse_diff) { 63 SWAP(old_mode, new_mode); 64 SWAP(old_oid, new_oid); 65 SWAP(old_path, new_path); 66 } 67 68 if (opt->prefix && 69 (strncmp(old_path, opt->prefix, opt->prefix_length) || 70 strncmp(new_path, opt->prefix, opt->prefix_length))) 71 return; 72 73 one = alloc_filespec(old_path); 74 two = alloc_filespec(new_path); 75 fill_filespec(one, old_oid, old_oid_valid, old_mode); 76 fill_filespec(two, new_oid, new_oid_valid, new_mode); 77 78 diff_queue(&diff_queued_diff, one, two); 79} 80 81static void builtin_diff_b_f(struct rev_info *revs, 82 int argc, const char **argv UNUSED, 83 struct object_array_entry **blob) 84{ 85 /* Blob vs file in the working tree*/ 86 struct stat st; 87 const char *path; 88 89 if (argc > 1) 90 usage(builtin_diff_usage); 91 92 GUARD_PATHSPEC(&revs->prune_data, PATHSPEC_FROMTOP | PATHSPEC_LITERAL); 93 path = revs->prune_data.items[0].match; 94 95 if (lstat(path, &st)) 96 die_errno(_("failed to stat '%s'"), path); 97 if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) 98 die(_("'%s': not a regular file or symlink"), path); 99 100 diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/"); 101 102 if (blob[0]->mode == S_IFINVALID) 103 blob[0]->mode = canon_mode(st.st_mode); 104 105 stuff_change(&revs->diffopt, 106 blob[0]->mode, canon_mode(st.st_mode), 107 &blob[0]->item->oid, null_oid(the_hash_algo), 108 1, 0, 109 blob[0]->path ? blob[0]->path : path, 110 path); 111 diffcore_std(&revs->diffopt); 112 diff_flush(&revs->diffopt); 113} 114 115static void builtin_diff_blobs(struct rev_info *revs, 116 int argc, const char **argv UNUSED, 117 struct object_array_entry **blob) 118{ 119 const unsigned mode = canon_mode(S_IFREG | 0644); 120 121 if (argc > 1) 122 usage(builtin_diff_usage); 123 124 if (blob[0]->mode == S_IFINVALID) 125 blob[0]->mode = mode; 126 127 if (blob[1]->mode == S_IFINVALID) 128 blob[1]->mode = mode; 129 130 stuff_change(&revs->diffopt, 131 blob[0]->mode, blob[1]->mode, 132 &blob[0]->item->oid, &blob[1]->item->oid, 133 1, 1, 134 blob_path(blob[0]), blob_path(blob[1])); 135 diffcore_std(&revs->diffopt); 136 diff_flush(&revs->diffopt); 137} 138 139static void builtin_diff_index(struct rev_info *revs, 140 int argc, const char **argv) 141{ 142 unsigned int option = 0; 143 while (1 < argc) { 144 const char *arg = argv[1]; 145 if (!strcmp(arg, "--cached") || !strcmp(arg, "--staged")) 146 option |= DIFF_INDEX_CACHED; 147 else if (!strcmp(arg, "--merge-base")) 148 option |= DIFF_INDEX_MERGE_BASE; 149 else 150 usage(builtin_diff_usage); 151 argv++; argc--; 152 } 153 /* 154 * Make sure there is one revision (i.e. pending object), 155 * and there is no revision filtering parameters. 156 */ 157 if (revs->pending.nr != 1 || 158 revs->max_count != -1 || revs->min_age != -1 || 159 revs->max_age != -1) 160 usage(builtin_diff_usage); 161 if (!(option & DIFF_INDEX_CACHED)) { 162 setup_work_tree(); 163 if (repo_read_index_preload(the_repository, 164 &revs->diffopt.pathspec, 0) < 0) { 165 die_errno("repo_read_index_preload"); 166 } 167 } else if (repo_read_index(the_repository) < 0) { 168 die_errno("repo_read_cache"); 169 } 170 run_diff_index(revs, option); 171} 172 173static void builtin_diff_tree(struct rev_info *revs, 174 int argc, const char **argv, 175 struct object_array_entry *ent0, 176 struct object_array_entry *ent1) 177{ 178 const struct object_id *(oid[2]); 179 struct object_id mb_oid; 180 int merge_base = 0; 181 182 while (1 < argc) { 183 const char *arg = argv[1]; 184 if (!strcmp(arg, "--merge-base")) 185 merge_base = 1; 186 else 187 usage(builtin_diff_usage); 188 argv++; argc--; 189 } 190 191 if (merge_base) { 192 diff_get_merge_base(revs, &mb_oid); 193 oid[0] = &mb_oid; 194 oid[1] = &revs->pending.objects[1].item->oid; 195 } else { 196 int swap = 0; 197 198 /* 199 * We saw two trees, ent0 and ent1. If ent1 is uninteresting, 200 * swap them. 201 */ 202 if (ent1->item->flags & UNINTERESTING) 203 swap = 1; 204 oid[swap] = &ent0->item->oid; 205 oid[1 - swap] = &ent1->item->oid; 206 } 207 diff_tree_oid(oid[0], oid[1], "", &revs->diffopt); 208 log_tree_diff_flush(revs); 209} 210 211static void builtin_diff_combined(struct rev_info *revs, 212 int argc, const char **argv UNUSED, 213 struct object_array_entry *ent, 214 int ents, int first_non_parent) 215{ 216 struct oid_array parents = OID_ARRAY_INIT; 217 int i; 218 219 if (argc > 1) 220 usage(builtin_diff_usage); 221 222 if (first_non_parent < 0) 223 die(_("no merge given, only parents.")); 224 if (first_non_parent >= ents) 225 BUG("first_non_parent out of range: %d", first_non_parent); 226 227 diff_merges_set_dense_combined_if_unset(revs); 228 229 for (i = 0; i < ents; i++) { 230 if (i != first_non_parent) 231 oid_array_append(&parents, &ent[i].item->oid); 232 } 233 diff_tree_combined(&ent[first_non_parent].item->oid, &parents, revs); 234 oid_array_clear(&parents); 235} 236 237static void refresh_index_quietly(void) 238{ 239 struct lock_file lock_file = LOCK_INIT; 240 int fd; 241 242 fd = repo_hold_locked_index(the_repository, &lock_file, 0); 243 if (fd < 0) 244 return; 245 discard_index(the_repository->index); 246 repo_read_index(the_repository); 247 refresh_index(the_repository->index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, 248 NULL); 249 repo_update_index_if_able(the_repository, &lock_file); 250} 251 252static void builtin_diff_files(struct rev_info *revs, int argc, const char **argv) 253{ 254 unsigned int options = 0; 255 256 while (1 < argc && argv[1][0] == '-') { 257 if (!strcmp(argv[1], "--base")) 258 revs->max_count = 1; 259 else if (!strcmp(argv[1], "--ours")) 260 revs->max_count = 2; 261 else if (!strcmp(argv[1], "--theirs")) 262 revs->max_count = 3; 263 else if (!strcmp(argv[1], "-q")) 264 options |= DIFF_SILENT_ON_REMOVED; 265 else if (!strcmp(argv[1], "-h")) 266 usage(builtin_diff_usage); 267 else { 268 error(_("invalid option: %s"), argv[1]); 269 usage(builtin_diff_usage); 270 } 271 argv++; argc--; 272 } 273 274 /* 275 * "diff --base" should not combine merges because it was not 276 * asked to. "diff -c" should not densify (if the user wants 277 * dense one, --cc can be explicitly asked for, or just rely 278 * on the default). 279 */ 280 if (revs->max_count == -1 && 281 (revs->diffopt.output_format & DIFF_FORMAT_PATCH)) 282 diff_merges_set_dense_combined_if_unset(revs); 283 284 setup_work_tree(); 285 if (repo_read_index_preload(the_repository, &revs->diffopt.pathspec, 286 0) < 0) { 287 die_errno("repo_read_index_preload"); 288 } 289 run_diff_files(revs, options); 290} 291 292struct symdiff { 293 struct bitmap *skip; 294 int warn; 295 const char *base, *left, *right; 296}; 297 298/* 299 * Check for symmetric-difference arguments, and if present, arrange 300 * everything we need to know to handle them correctly. As a bonus, 301 * weed out all bogus range-based revision specifications, e.g., 302 * "git diff A..B C..D" or "git diff A..B C" get rejected. 303 * 304 * For an actual symmetric diff, *symdiff is set this way: 305 * 306 * - its skip is non-NULL and marks *all* rev->pending.objects[i] 307 * indices that the caller should ignore (extra merge bases, of 308 * which there might be many, and A in A...B). Note that the 309 * chosen merge base and right side are NOT marked. 310 * - warn is set if there are multiple merge bases. 311 * - base, left, and right point to the names to use in a 312 * warning about multiple merge bases. 313 * 314 * If there is no symmetric diff argument, sym->skip is NULL and 315 * sym->warn is cleared. The remaining fields are not set. 316 */ 317static void symdiff_prepare(struct rev_info *rev, struct symdiff *sym) 318{ 319 int i, is_symdiff = 0, basecount = 0, othercount = 0; 320 int lpos = -1, rpos = -1, basepos = -1; 321 struct bitmap *map = NULL; 322 323 /* 324 * Use the whence fields to find merge bases and left and 325 * right parts of symmetric difference, so that we do not 326 * depend on the order that revisions are parsed. If there 327 * are any revs that aren't from these sources, we have a 328 * "git diff C A...B" or "git diff A...B C" case. Or we 329 * could even get "git diff A...B C...E", for instance. 330 * 331 * If we don't have just one merge base, we pick one 332 * at random. 333 * 334 * NB: REV_CMD_LEFT, REV_CMD_RIGHT are also used for A..B, 335 * so we must check for SYMMETRIC_LEFT too. The two arrays 336 * rev->pending.objects and rev->cmdline.rev are parallel. 337 */ 338 for (i = 0; i < rev->cmdline.nr; i++) { 339 struct object *obj = rev->pending.objects[i].item; 340 switch (rev->cmdline.rev[i].whence) { 341 case REV_CMD_MERGE_BASE: 342 if (basepos < 0) 343 basepos = i; 344 basecount++; 345 break; /* do mark all bases */ 346 case REV_CMD_LEFT: 347 if (lpos >= 0) 348 usage(builtin_diff_usage); 349 lpos = i; 350 if (obj->flags & SYMMETRIC_LEFT) { 351 is_symdiff = 1; 352 break; /* do mark A */ 353 } 354 continue; 355 case REV_CMD_RIGHT: 356 if (rpos >= 0) 357 usage(builtin_diff_usage); 358 rpos = i; 359 continue; /* don't mark B */ 360 case REV_CMD_PARENTS_ONLY: 361 case REV_CMD_REF: 362 case REV_CMD_REV: 363 othercount++; 364 continue; 365 } 366 if (!map) 367 map = bitmap_new(); 368 bitmap_set(map, i); 369 } 370 371 /* 372 * Forbid any additional revs for both A...B and A..B. 373 */ 374 if (lpos >= 0 && othercount > 0) 375 usage(builtin_diff_usage); 376 377 if (!is_symdiff) { 378 bitmap_free(map); 379 sym->warn = 0; 380 sym->skip = NULL; 381 return; 382 } 383 384 sym->left = rev->pending.objects[lpos].name; 385 sym->right = rev->pending.objects[rpos].name; 386 if (basecount == 0) 387 die(_("%s...%s: no merge base"), sym->left, sym->right); 388 sym->base = rev->pending.objects[basepos].name; 389 bitmap_unset(map, basepos); /* unmark the base we want */ 390 sym->warn = basecount > 1; 391 sym->skip = map; 392} 393 394static void symdiff_release(struct symdiff *sdiff) 395{ 396 bitmap_free(sdiff->skip); 397} 398 399int cmd_diff(int argc, 400 const char **argv, 401 const char *prefix, 402 struct repository *repo UNUSED) 403{ 404 int i; 405 struct rev_info rev; 406 struct object_array ent = OBJECT_ARRAY_INIT; 407 int first_non_parent = -1; 408 int blobs = 0, paths = 0; 409 struct object_array_entry *blob[2]; 410 int nongit = 0, no_index = 0; 411 int result; 412 struct symdiff sdiff; 413 414 /* 415 * We could get N tree-ish in the rev.pending_objects list. 416 * Also there could be M blobs there, and P pathspecs. --cached may 417 * also be present. 418 * 419 * N=0, M=0: 420 * cache vs files (diff-files) 421 * 422 * N=0, M=0, --cached: 423 * HEAD vs cache (diff-index --cached) 424 * 425 * N=0, M=2: 426 * compare two random blobs. P must be zero. 427 * 428 * N=0, M=1, P=1: 429 * compare a blob with a working tree file. 430 * 431 * N=1, M=0: 432 * tree vs files (diff-index) 433 * 434 * N=1, M=0, --cached: 435 * tree vs cache (diff-index --cached) 436 * 437 * N=2, M=0: 438 * tree vs tree (diff-tree) 439 * 440 * N=0, M=0, P=2: 441 * compare two filesystem entities (aka --no-index). 442 * 443 * Other cases are errors. 444 */ 445 446 /* Were we asked to do --no-index explicitly? */ 447 for (i = 1; i < argc; i++) { 448 if (!strcmp(argv[i], "--")) { 449 i++; 450 break; 451 } 452 if (!strcmp(argv[i], "--no-index")) 453 no_index = DIFF_NO_INDEX_EXPLICIT; 454 if (argv[i][0] != '-') 455 break; 456 } 457 458 prefix = setup_git_directory_gently(&nongit); 459 460 if (!nongit) { 461 prepare_repo_settings(the_repository); 462 the_repository->settings.command_requires_full_index = 0; 463 } 464 465 if (!no_index) { 466 /* 467 * Treat git diff with at least one path outside of the 468 * repo the same as if the command would have been executed 469 * outside of a git repository. In this case it behaves 470 * the same way as "git diff --no-index <a> <b>", which acts 471 * as a colourful "diff" replacement. 472 */ 473 if (nongit || ((argc == i + 2) && 474 (!path_inside_repo(prefix, argv[i]) || 475 !path_inside_repo(prefix, argv[i + 1])))) 476 no_index = DIFF_NO_INDEX_IMPLICIT; 477 } 478 479 /* 480 * When operating outside of a Git repository we need to have a hash 481 * algorithm at hand so that we can generate the blob hashes. We 482 * default to SHA1 here, but may eventually want to change this to be 483 * configurable via a command line option. 484 */ 485 if (nongit) 486 repo_set_hash_algo(the_repository, GIT_HASH_DEFAULT); 487 488 init_diff_ui_defaults(); 489 repo_config(the_repository, git_diff_ui_config, NULL); 490 491 /* 492 * If we are ignoring the fact that our current directory may 493 * be part of a working tree controlled by a Git repository to 494 * pretend to be a "better GNU diff", we should undo the 495 * effect of the setup code that did a chdir() to the top of 496 * the working tree. Where we came from is recorded in the 497 * prefix. 498 */ 499 if (no_index && prefix) { 500 if (chdir(prefix)) 501 die(_("cannot come back to cwd")); 502 prefix = NULL; 503 } 504 505 prefix = precompose_argv_prefix(argc, argv, prefix); 506 507 repo_init_revisions(the_repository, &rev, prefix); 508 509 /* Set up defaults that will apply to both no-index and regular diffs. */ 510 init_diffstat_widths(&rev.diffopt); 511 rev.diffopt.flags.allow_external = 1; 512 rev.diffopt.flags.allow_textconv = 1; 513 514 /* If this is a no-index diff, just run it and exit there. */ 515 if (no_index) 516 exit(diff_no_index(&rev, the_repository->hash_algo, 517 no_index == DIFF_NO_INDEX_IMPLICIT, 518 argc, argv)); 519 520 521 /* 522 * Otherwise, we are doing the usual "git" diff; set up any 523 * further defaults that apply to regular diffs. 524 */ 525 rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index; 526 527 /* 528 * Default to intent-to-add entries invisible in the 529 * index. This makes them show up as new files in diff-files 530 * and not at all in diff-cached. 531 */ 532 rev.diffopt.ita_invisible_in_index = 1; 533 534 if (nongit) 535 die(_("Not a git repository")); 536 argc = setup_revisions(argc, argv, &rev, NULL); 537 if (!rev.diffopt.output_format) { 538 rev.diffopt.output_format = DIFF_FORMAT_PATCH; 539 diff_setup_done(&rev.diffopt); 540 } 541 542 rev.diffopt.flags.recursive = 1; 543 rev.diffopt.rotate_to_strict = 1; 544 545 setup_diff_pager(&rev.diffopt); 546 547 /* 548 * Do we have --cached and not have a pending object, then 549 * default to HEAD by hand. Eek. 550 */ 551 if (!rev.pending.nr) { 552 int i; 553 for (i = 1; i < argc; i++) { 554 const char *arg = argv[i]; 555 if (!strcmp(arg, "--")) 556 break; 557 else if (!strcmp(arg, "--cached") || 558 !strcmp(arg, "--staged")) { 559 add_head_to_pending(&rev); 560 if (!rev.pending.nr) { 561 struct tree *tree; 562 tree = lookup_tree(the_repository, 563 the_repository->hash_algo->empty_tree); 564 add_pending_object(&rev, &tree->object, "HEAD"); 565 } 566 break; 567 } 568 } 569 } 570 571 symdiff_prepare(&rev, &sdiff); 572 for (i = 0; i < rev.pending.nr; i++) { 573 struct object_array_entry *entry = &rev.pending.objects[i]; 574 struct object *obj = entry->item; 575 const char *name = entry->name; 576 int flags = (obj->flags & UNINTERESTING); 577 if (!obj->parsed) 578 obj = parse_object(the_repository, &obj->oid); 579 obj = deref_tag(the_repository, obj, NULL, 0); 580 if (!obj) 581 die(_("invalid object '%s' given."), name); 582 if (obj->type == OBJ_COMMIT) 583 obj = &repo_get_commit_tree(the_repository, 584 ((struct commit *)obj))->object; 585 586 if (obj->type == OBJ_TREE) { 587 if (sdiff.skip && bitmap_get(sdiff.skip, i)) 588 continue; 589 obj->flags |= flags; 590 add_object_array(obj, name, &ent); 591 if (first_non_parent < 0 && 592 (i >= rev.cmdline.nr || /* HEAD by hand. */ 593 rev.cmdline.rev[i].whence != REV_CMD_PARENTS_ONLY)) 594 first_non_parent = ent.nr - 1; 595 } else if (obj->type == OBJ_BLOB) { 596 if (2 <= blobs) 597 die(_("more than two blobs given: '%s'"), name); 598 blob[blobs] = entry; 599 blobs++; 600 601 } else { 602 die(_("unhandled object '%s' given."), name); 603 } 604 } 605 if (rev.prune_data.nr) 606 paths += rev.prune_data.nr; 607 608 /* 609 * Now, do the arguments look reasonable? 610 */ 611 if (!ent.nr) { 612 switch (blobs) { 613 case 0: 614 builtin_diff_files(&rev, argc, argv); 615 break; 616 case 1: 617 if (paths != 1) 618 usage(builtin_diff_usage); 619 builtin_diff_b_f(&rev, argc, argv, blob); 620 break; 621 case 2: 622 if (paths) 623 usage(builtin_diff_usage); 624 builtin_diff_blobs(&rev, argc, argv, blob); 625 break; 626 default: 627 usage(builtin_diff_usage); 628 } 629 } 630 else if (blobs) 631 usage(builtin_diff_usage); 632 else if (ent.nr == 1) 633 builtin_diff_index(&rev, argc, argv); 634 else if (ent.nr == 2) { 635 if (sdiff.warn) 636 warning(_("%s...%s: multiple merge bases, using %s"), 637 sdiff.left, sdiff.right, sdiff.base); 638 builtin_diff_tree(&rev, argc, argv, 639 &ent.objects[0], &ent.objects[1]); 640 } else 641 builtin_diff_combined(&rev, argc, argv, 642 ent.objects, ent.nr, 643 first_non_parent); 644 result = diff_result_code(&rev); 645 if (1 < rev.diffopt.skip_stat_unmatch) 646 refresh_index_quietly(); 647 release_revisions(&rev); 648 object_array_clear(&ent); 649 symdiff_release(&sdiff); 650 return result; 651}