/src/git/builtin/diff-tree.c
Line | Count | Source (jump to first uncovered line) |
1 | | #include "builtin.h" |
2 | | #include "config.h" |
3 | | #include "diff.h" |
4 | | #include "commit.h" |
5 | | #include "gettext.h" |
6 | | #include "hex.h" |
7 | | #include "log-tree.h" |
8 | | #include "read-cache-ll.h" |
9 | | #include "repository.h" |
10 | | #include "revision.h" |
11 | | #include "tmp-objdir.h" |
12 | | #include "tree.h" |
13 | | |
14 | | static struct rev_info log_tree_opt; |
15 | | |
16 | | static int diff_tree_commit_oid(const struct object_id *oid) |
17 | 0 | { |
18 | 0 | struct commit *commit = lookup_commit_reference(the_repository, oid); |
19 | 0 | if (!commit) |
20 | 0 | return -1; |
21 | 0 | return log_tree_commit(&log_tree_opt, commit); |
22 | 0 | } |
23 | | |
24 | | /* Diff one or more commits. */ |
25 | | static int stdin_diff_commit(struct commit *commit, const char *p) |
26 | 0 | { |
27 | 0 | struct object_id oid; |
28 | 0 | struct commit_list **pptr = NULL; |
29 | | |
30 | | /* Graft the fake parents locally to the commit */ |
31 | 0 | while (isspace(*p++) && !parse_oid_hex(p, &oid, &p)) { |
32 | 0 | struct commit *parent = lookup_commit(the_repository, &oid); |
33 | 0 | if (!pptr) { |
34 | | /* Free the real parent list */ |
35 | 0 | free_commit_list(commit->parents); |
36 | 0 | commit->parents = NULL; |
37 | 0 | pptr = &(commit->parents); |
38 | 0 | } |
39 | 0 | if (parent) { |
40 | 0 | pptr = &commit_list_insert(parent, pptr)->next; |
41 | 0 | } |
42 | 0 | } |
43 | 0 | return log_tree_commit(&log_tree_opt, commit); |
44 | 0 | } |
45 | | |
46 | | /* Diff two trees. */ |
47 | | static int stdin_diff_trees(struct tree *tree1, const char *p) |
48 | 0 | { |
49 | 0 | struct object_id oid; |
50 | 0 | struct tree *tree2; |
51 | 0 | if (!isspace(*p++) || parse_oid_hex(p, &oid, &p) || *p) |
52 | 0 | return error("Need exactly two trees, separated by a space"); |
53 | 0 | tree2 = lookup_tree(the_repository, &oid); |
54 | 0 | if (!tree2 || parse_tree(tree2)) |
55 | 0 | return -1; |
56 | 0 | printf("%s %s\n", oid_to_hex(&tree1->object.oid), |
57 | 0 | oid_to_hex(&tree2->object.oid)); |
58 | 0 | diff_tree_oid(&tree1->object.oid, &tree2->object.oid, |
59 | 0 | "", &log_tree_opt.diffopt); |
60 | 0 | log_tree_diff_flush(&log_tree_opt); |
61 | 0 | return 0; |
62 | 0 | } |
63 | | |
64 | | static int diff_tree_stdin(char *line) |
65 | 0 | { |
66 | 0 | int len = strlen(line); |
67 | 0 | struct object_id oid; |
68 | 0 | struct object *obj; |
69 | 0 | const char *p; |
70 | |
|
71 | 0 | if (!len || line[len-1] != '\n') |
72 | 0 | return -1; |
73 | 0 | line[len-1] = 0; |
74 | 0 | if (parse_oid_hex(line, &oid, &p)) |
75 | 0 | return -1; |
76 | 0 | obj = parse_object(the_repository, &oid); |
77 | 0 | if (!obj) |
78 | 0 | return -1; |
79 | 0 | if (obj->type == OBJ_COMMIT) |
80 | 0 | return stdin_diff_commit((struct commit *)obj, p); |
81 | 0 | if (obj->type == OBJ_TREE) |
82 | 0 | return stdin_diff_trees((struct tree *)obj, p); |
83 | 0 | error("Object %s is a %s, not a commit or tree", |
84 | 0 | oid_to_hex(&oid), type_name(obj->type)); |
85 | 0 | return -1; |
86 | 0 | } |
87 | | |
88 | | static const char diff_tree_usage[] = |
89 | | "git diff-tree [--stdin] [-m] [-s] [-v] [--no-commit-id] [--pretty]\n" |
90 | | " [-t] [-r] [-c | --cc] [--combined-all-paths] [--root] [--merge-base]\n" |
91 | | " [<common-diff-options>] <tree-ish> [<tree-ish>] [<path>...]\n" |
92 | | "\n" |
93 | | " -r diff recursively\n" |
94 | | " -c show combined diff for merge commits\n" |
95 | | " --cc show combined diff for merge commits removing uninteresting hunks\n" |
96 | | " --combined-all-paths\n" |
97 | | " show name of file in all parents for combined diffs\n" |
98 | | " --root include the initial commit as diff against /dev/null\n" |
99 | | COMMON_DIFF_OPTIONS_HELP; |
100 | | |
101 | | static void diff_tree_tweak_rev(struct rev_info *rev) |
102 | 0 | { |
103 | 0 | if (!rev->diffopt.output_format) { |
104 | 0 | if (rev->dense_combined_merges) |
105 | 0 | rev->diffopt.output_format = DIFF_FORMAT_PATCH; |
106 | 0 | else |
107 | 0 | rev->diffopt.output_format = DIFF_FORMAT_RAW; |
108 | 0 | } |
109 | 0 | } |
110 | | |
111 | | int cmd_diff_tree(int argc, const char **argv, const char *prefix) |
112 | 0 | { |
113 | 0 | char line[1000]; |
114 | 0 | struct object *tree1, *tree2; |
115 | 0 | static struct rev_info *opt = &log_tree_opt; |
116 | 0 | struct setup_revision_opt s_r_opt; |
117 | 0 | struct userformat_want w; |
118 | 0 | int read_stdin = 0; |
119 | 0 | int merge_base = 0; |
120 | |
|
121 | 0 | if (argc == 2 && !strcmp(argv[1], "-h")) |
122 | 0 | usage(diff_tree_usage); |
123 | | |
124 | 0 | git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ |
125 | |
|
126 | 0 | prepare_repo_settings(the_repository); |
127 | 0 | the_repository->settings.command_requires_full_index = 0; |
128 | |
|
129 | 0 | repo_init_revisions(the_repository, opt, prefix); |
130 | 0 | if (repo_read_index(the_repository) < 0) |
131 | 0 | die(_("index file corrupt")); |
132 | 0 | opt->abbrev = 0; |
133 | 0 | opt->diff = 1; |
134 | 0 | opt->disable_stdin = 1; |
135 | 0 | memset(&s_r_opt, 0, sizeof(s_r_opt)); |
136 | 0 | s_r_opt.tweak = diff_tree_tweak_rev; |
137 | |
|
138 | 0 | prefix = precompose_argv_prefix(argc, argv, prefix); |
139 | 0 | argc = setup_revisions(argc, argv, opt, &s_r_opt); |
140 | |
|
141 | 0 | memset(&w, 0, sizeof(w)); |
142 | 0 | userformat_find_requirements(NULL, &w); |
143 | |
|
144 | 0 | if (!opt->show_notes_given && w.notes) |
145 | 0 | opt->show_notes = 1; |
146 | 0 | if (opt->show_notes) |
147 | 0 | load_display_notes(&opt->notes_opt); |
148 | |
|
149 | 0 | while (--argc > 0) { |
150 | 0 | const char *arg = *++argv; |
151 | |
|
152 | 0 | if (!strcmp(arg, "--stdin")) { |
153 | 0 | read_stdin = 1; |
154 | 0 | continue; |
155 | 0 | } |
156 | 0 | if (!strcmp(arg, "--merge-base")) { |
157 | 0 | merge_base = 1; |
158 | 0 | continue; |
159 | 0 | } |
160 | 0 | usage(diff_tree_usage); |
161 | 0 | } |
162 | | |
163 | 0 | if (read_stdin && merge_base) |
164 | 0 | die(_("options '%s' and '%s' cannot be used together"), "--stdin", "--merge-base"); |
165 | 0 | if (merge_base && opt->pending.nr != 2) |
166 | 0 | die(_("--merge-base only works with two commits")); |
167 | | |
168 | 0 | opt->diffopt.rotate_to_strict = 1; |
169 | |
|
170 | 0 | if (opt->remerge_diff) { |
171 | 0 | opt->remerge_objdir = tmp_objdir_create("remerge-diff"); |
172 | 0 | if (!opt->remerge_objdir) |
173 | 0 | die(_("unable to create temporary object directory")); |
174 | 0 | tmp_objdir_replace_primary_odb(opt->remerge_objdir, 1); |
175 | 0 | } |
176 | | |
177 | | /* |
178 | | * NOTE! We expect "a..b" to expand to "^a b" but it is |
179 | | * perfectly valid for revision range parser to yield "b ^a", |
180 | | * which means the same thing. If we get the latter, i.e. the |
181 | | * second one is marked UNINTERESTING, we recover the original |
182 | | * order the user gave, i.e. "a..b", by swapping the trees. |
183 | | */ |
184 | 0 | switch (opt->pending.nr) { |
185 | 0 | case 0: |
186 | 0 | if (!read_stdin) |
187 | 0 | usage(diff_tree_usage); |
188 | 0 | break; |
189 | 0 | case 1: |
190 | 0 | tree1 = opt->pending.objects[0].item; |
191 | 0 | diff_tree_commit_oid(&tree1->oid); |
192 | 0 | break; |
193 | 0 | case 2: |
194 | 0 | tree1 = opt->pending.objects[0].item; |
195 | 0 | tree2 = opt->pending.objects[1].item; |
196 | 0 | if (merge_base) { |
197 | 0 | struct object_id oid; |
198 | |
|
199 | 0 | diff_get_merge_base(opt, &oid); |
200 | 0 | tree1 = lookup_object(the_repository, &oid); |
201 | 0 | } else if (tree2->flags & UNINTERESTING) { |
202 | 0 | SWAP(tree2, tree1); |
203 | 0 | } |
204 | 0 | diff_tree_oid(&tree1->oid, &tree2->oid, "", &opt->diffopt); |
205 | 0 | log_tree_diff_flush(opt); |
206 | 0 | break; |
207 | 0 | } |
208 | | |
209 | 0 | if (read_stdin) { |
210 | 0 | int saved_nrl = 0; |
211 | 0 | int saved_dcctc = 0; |
212 | |
|
213 | 0 | opt->diffopt.rotate_to_strict = 0; |
214 | 0 | opt->diffopt.no_free = 1; |
215 | 0 | if (opt->diffopt.detect_rename) { |
216 | 0 | if (the_repository->index->cache) |
217 | 0 | repo_read_index(the_repository); |
218 | 0 | opt->diffopt.setup |= DIFF_SETUP_USE_SIZE_CACHE; |
219 | 0 | } |
220 | 0 | while (fgets(line, sizeof(line), stdin)) { |
221 | 0 | struct object_id oid; |
222 | |
|
223 | 0 | if (get_oid_hex(line, &oid)) { |
224 | 0 | fputs(line, stdout); |
225 | 0 | fflush(stdout); |
226 | 0 | } |
227 | 0 | else { |
228 | 0 | diff_tree_stdin(line); |
229 | 0 | if (saved_nrl < opt->diffopt.needed_rename_limit) |
230 | 0 | saved_nrl = opt->diffopt.needed_rename_limit; |
231 | 0 | if (opt->diffopt.degraded_cc_to_c) |
232 | 0 | saved_dcctc = 1; |
233 | 0 | } |
234 | 0 | } |
235 | 0 | opt->diffopt.degraded_cc_to_c = saved_dcctc; |
236 | 0 | opt->diffopt.needed_rename_limit = saved_nrl; |
237 | 0 | opt->diffopt.no_free = 0; |
238 | 0 | diff_free(&opt->diffopt); |
239 | 0 | } |
240 | |
|
241 | 0 | if (opt->remerge_diff) { |
242 | 0 | tmp_objdir_destroy(opt->remerge_objdir); |
243 | 0 | opt->remerge_objdir = NULL; |
244 | 0 | } |
245 | |
|
246 | 0 | return diff_result_code(&opt->diffopt); |
247 | 0 | } |