/* * Builtin "git diff" * * Copyright (c) 2006 Junio C Hamano */ #include "cache.h" #include "commit.h" #include "blob.h" #include "tag.h" #include "diff.h" #include "diffcore.h" #include "revision.h" #include "log-tree.h" #include "builtin.h" /* NEEDSWORK: struct object has place for name but we _do_ * know mode when we extracted the blob out of a tree, which * we currently lose. */ struct blobinfo { unsigned char sha1[20]; const char *name; }; static const char builtin_diff_usage[] = "diff {0,2} -- *"; static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv) { int silent = 0; while (1 < argc) { const char *arg = argv[1]; if (!strcmp(arg, "--base")) revs->max_count = 1; else if (!strcmp(arg, "--ours")) revs->max_count = 2; else if (!strcmp(arg, "--theirs")) revs->max_count = 3; else if (!strcmp(arg, "-q")) silent = 1; else if (!strcmp(arg, "--raw")) revs->diffopt.output_format = DIFF_FORMAT_RAW; else usage(builtin_diff_usage); argv++; argc--; } /* * Make sure there are NO revision (i.e. pending object) parameter, * specified rev.max_count is reasonable (0 <= n <= 3), and * there is no other revision filtering parameter. */ if (revs->pending_objects || revs->min_age != -1 || revs->max_age != -1 || 3 < revs->max_count) usage(builtin_diff_usage); if (revs->max_count < 0 && (revs->diffopt.output_format == DIFF_FORMAT_PATCH)) revs->combine_merges = revs->dense_combined_merges = 1; /* * Backward compatibility wart - "diff-files -s" used to * defeat the common diff option "-s" which asked for * DIFF_FORMAT_NO_OUTPUT. */ if (revs->diffopt.output_format == DIFF_FORMAT_NO_OUTPUT) revs->diffopt.output_format = DIFF_FORMAT_RAW; return run_diff_files(revs, silent); } static void stuff_change(struct diff_options *opt, unsigned old_mode, unsigned new_mode, const unsigned char *old_sha1, const unsigned char *new_sha1, const char *old_name, const char *new_name) { struct diff_filespec *one, *two; if (memcmp(null_sha1, old_sha1, 20) && memcmp(null_sha1, new_sha1, 20) && !memcmp(old_sha1, new_sha1, 20)) return; if (opt->reverse_diff) { unsigned tmp; const unsigned char *tmp_u; const char *tmp_c; tmp = old_mode; old_mode = new_mode; new_mode = tmp; tmp_u = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_u; tmp_c = old_name; old_name = new_name; new_name = tmp_c; } one = alloc_filespec(old_name); two = alloc_filespec(new_name); fill_filespec(one, old_sha1, old_mode); fill_filespec(two, new_sha1, new_mode); /* NEEDSWORK: shouldn't this part of diffopt??? */ diff_queue(&diff_queued_diff, one, two); } static int builtin_diff_b_f(struct rev_info *revs, int argc, const char **argv, struct blobinfo *blob, const char *path) { /* Blob vs file in the working tree*/ struct stat st; while (1 < argc) { const char *arg = argv[1]; if (!strcmp(arg, "--raw")) revs->diffopt.output_format = DIFF_FORMAT_RAW; else usage(builtin_diff_usage); argv++; argc--; } if (lstat(path, &st)) die("'%s': %s", path, strerror(errno)); if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) die("'%s': not a regular file or symlink", path); stuff_change(&revs->diffopt, canon_mode(st.st_mode), canon_mode(st.st_mode), blob[0].sha1, null_sha1, path, path); diffcore_std(&revs->diffopt); diff_flush(&revs->diffopt); return 0; } static int builtin_diff_blobs(struct rev_info *revs, int argc, const char **argv, struct blobinfo *blob) { /* Blobs: the arguments are reversed when setup_revisions() * picked them up. */ unsigned mode = canon_mode(S_IFREG | 0644); while (1 < argc) { const char *arg = argv[1]; if (!strcmp(arg, "--raw")) revs->diffopt.output_format = DIFF_FORMAT_RAW; else usage(builtin_diff_usage); argv++; argc--; } stuff_change(&revs->diffopt, mode, mode, blob[1].sha1, blob[0].sha1, blob[0].name, blob[0].name); diffcore_std(&revs->diffopt); diff_flush(&revs->diffopt); return 0; } static int builtin_diff_index(struct rev_info *revs, int argc, const char **argv) { int cached = 0; while (1 < argc) { const char *arg = argv[1]; if (!strcmp(arg, "--cached")) cached = 1; else if (!strcmp(arg, "--raw")) revs->diffopt.output_format = DIFF_FORMAT_RAW; else usage(builtin_diff_usage); argv++; argc--; } /* * Make sure there is one revision (i.e. pending object), * and there is no revision filtering parameters. */ if (!revs->pending_objects || revs->pending_objects->next || revs->max_count != -1 || revs->min_age != -1 || revs->max_age != -1) usage(builtin_diff_usage); return run_diff_index(revs, cached); } static int builtin_diff_tree(struct rev_info *revs, int argc, const char **argv, struct object_list *ent) { const unsigned char *(sha1[2]); int swap = 1; while (1 < argc) { const char *arg = argv[1]; if (!strcmp(arg, "--raw")) revs->diffopt.output_format = DIFF_FORMAT_RAW; else usage(builtin_diff_usage); argv++; argc--; } /* We saw two trees, ent[0] and ent[1]. * unless ent[0] is unintesting, they are swapped */ if (ent[0].item->flags & UNINTERESTING) swap = 0; sha1[swap] = ent[0].item->sha1; sha1[1-swap] = ent[1].item->sha1; diff_tree_sha1(sha1[0], sha1[1], "", &revs->diffopt); log_tree_diff_flush(revs); return 0; } static int builtin_diff_combined(struct rev_info *revs, int argc, const char **argv, struct object_list *ent, int ents) { const unsigned char (*parent)[20]; int i; while (1 < argc) { const char *arg = argv[1]; if (!strcmp(arg, "--raw")) revs->diffopt.output_format = DIFF_FORMAT_RAW; else usage(builtin_diff_usage); argv++; argc--; } if (!revs->dense_combined_merges && !revs->combine_merges) revs->dense_combined_merges = revs->combine_merges = 1; parent = xmalloc(ents * sizeof(*parent)); /* Again, the revs are all reverse */ for (i = 0; i < ents; i++) memcpy(parent + i, ent[ents - 1 - i].item->sha1, 20); diff_tree_combined(parent[0], parent + 1, ents - 1, revs->dense_combined_merges, revs); return 0; } void add_head(struct rev_info *revs) { unsigned char sha1[20]; struct object *obj; if (get_sha1("HEAD", sha1)) return; obj = parse_object(sha1); if (!obj) return; add_object(obj, &revs->pending_objects, NULL, "HEAD"); } int cmd_diff(int argc, const char **argv, char **envp) { struct rev_info rev; struct object_list *list, ent[100]; int ents = 0, blobs = 0, paths = 0; const char *path = NULL; struct blobinfo blob[2]; /* * We could get N tree-ish in the rev.pending_objects list. * Also there could be M blobs there, and P pathspecs. * * N=0, M=0: * cache vs files (diff-files) * N=0, M=2: * compare two random blobs. P must be zero. * N=0, M=1, P=1: * compare a blob with a working tree file. * * N=1, M=0: * tree vs cache (diff-index --cached) * * N=2, M=0: * tree vs tree (diff-tree) * * Other cases are errors. */ git_config(git_diff_config); init_revisions(&rev); rev.diffopt.output_format = DIFF_FORMAT_PATCH; argc = setup_revisions(argc, argv, &rev, NULL); /* Do we have --cached and not have a pending object, then * default to HEAD by hand. Eek. */ if (!rev.pending_objects) { int i; for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (!strcmp(arg, "--")) break; else if (!strcmp(arg, "--cached")) { add_head(&rev); break; } } } for (list = rev.pending_objects; list; list = list->next) { struct object *obj = list->item; const char *name = list->name; int flags = (obj->flags & UNINTERESTING); if (!obj->parsed) obj = parse_object(obj->sha1); obj = deref_tag(obj, NULL, 0); if (!obj) die("invalid object '%s' given.", name); if (!strcmp(obj->type, commit_type)) obj = &((struct commit *)obj)->tree->object; if (!strcmp(obj->type, tree_type)) { if (ARRAY_SIZE(ent) <= ents) die("more than %d trees given: '%s'", (int) ARRAY_SIZE(ent), name); obj->flags |= flags; ent[ents].item = obj; ent[ents].name = name; ents++; continue; } if (!strcmp(obj->type, blob_type)) { if (2 <= blobs) die("more than two blobs given: '%s'", name); memcpy(blob[blobs].sha1, obj->sha1, 20); blob[blobs].name = name; blobs++; continue; } die("unhandled object '%s' given.", name); } if (rev.prune_data) { const char **pathspec = rev.prune_data; while (*pathspec) { if (!path) path = *pathspec; paths++; pathspec++; } } /* * Now, do the arguments look reasonable? */ if (!ents) { switch (blobs) { case 0: return builtin_diff_files(&rev, argc, argv); break; case 1: if (paths != 1) usage(builtin_diff_usage); return builtin_diff_b_f(&rev, argc, argv, blob, path); break; case 2: if (paths) usage(builtin_diff_usage); return builtin_diff_blobs(&rev, argc, argv, blob); break; default: usage(builtin_diff_usage); } } else if (blobs) usage(builtin_diff_usage); else if (ents == 1) return builtin_diff_index(&rev, argc, argv); else if (ents == 2) return builtin_diff_tree(&rev, argc, argv, ent); else return builtin_diff_combined(&rev, argc, argv, ent, ents); usage(builtin_diff_usage); }