tree-diff: rework diff_tree() to generate diffs for multiparent cases as well

Previously diff_tree(), which is now named ll_diff_tree_sha1(), was generating diff_filepair(s) for two trees t1 and t2, and that was usually used for a commit as t1=HEAD~, and t2=HEAD - i.e. to see changes a commit introduces. In Git, however, we have fundamentally built flexibility in that a commit can have many parents - 1 for a plain commit, 2 for a simple merge, but also more than 2 for merging several heads at once. For merges there is a so called combine-diff, which shows diff, a merge introduces by itself, omitting changes done by any parent. That works through first finding paths, that are different to all parents, and then showing generalized diff, with separate columns for +/- for each parent. The code lives in combine-diff.c . There is an impedance mismatch, however, in that a commit could generally have any number of parents, and that while diffing trees, we divide cases for 2-tree diffs and more-than-2-tree diffs. I mean there is no special casing for multiple parents commits in e.g. revision-walker . That impedance mismatch *hurts* *performance* *badly* for generating combined diffs - in "combine-diff: optimize combine_diff_path sets intersection" I've already removed some slowness from it, but from the timings provided there, it could be seen, that combined diffs still cost more than an order of magnitude more cpu time, compared to diff for usual commits, and that would only be an optimistic estimate, if we take into account that for e.g. linux.git there is only one merge for several dozens of plain commits. That slowness comes from the fact that currently, while generating combined diff, a lot of time is spent computing diff(commit,commit^2) just to only then intersect that huge diff to almost small set of files from diff(commit,commit^1). That's because at present, to compute combine-diff, for first finding paths, that "every parent touches", we use the following combine-diff property/definition: D(A,P1...Pn) = D(A,P1) ^ ... ^ D(A,Pn) (w.r.t. paths) where D(A,P1...Pn) is combined diff between commit A, and parents Pi and D(A,Pi) is usual two-tree diff Pi..A So if any of that D(A,Pi) is huge, tracting 1 n-parent combine-diff as n 1-parent diffs and intersecting results will be slow. And usually, for linux.git and other topic-based workflows, that D(A,P2) is huge, because, if merge-base of A and P2, is several dozens of merges (from A, via first parent) below, that D(A,P2) will be diffing sum of merges from several subsystems to 1 subsystem. The solution is to avoid computing n 1-parent diffs, and to find changed-to-all-parents paths via scanning A's and all Pi's trees simultaneously, at each step comparing their entries, and based on that comparison, populate paths result, and deduce we could *skip* *recursing* into subdirectories, if at least for 1 parent, sha1 of that dir tree is the same as in A. That would save us from doing significant amount of needless work. Such approach is very similar to what diff_tree() does, only there we deal with scanning only 2 trees simultaneously, and for n+1 tree, the logic is a bit more complex: D(T,P1...Pn) calculation scheme ------------------------------- D(T,P1...Pn) = D(T,P1) ^ ... ^ D(T,Pn) (regarding resulting paths set) D(T,Pj) - diff between T..Pj D(T,P1...Pn) - combined diff from T to parents P1,...,Pn We start from all trees, which are sorted, and compare their entries in lock-step: T P1 Pn - - - |t| |p1| |pn| |-| |--| ... |--| imin = argmin(p1...pn) | | | | | | |-| |--| |--| |.| |. | |. | . . . . . . at any time there could be 3 cases: 1) t < p[imin]; 2) t > p[imin]; 3) t = p[imin]. Schematic deduction of what every case means, and what to do, follows: 1) t < p[imin] -> ∀j t ∉ Pj -> "+t" ∈ D(T,Pj) -> D += "+t"; t↓ 2) t > p[imin] 2.1) ∃j: pj > p[imin] -> "-p[imin]" ∉ D(T,Pj) -> D += ø; ∀ pi=p[imin] pi↓ 2.2) ∀i pi = p[imin] -> pi ∉ T -> "-pi" ∈ D(T,Pi) -> D += "-p[imin]"; ∀i pi↓ 3) t = p[imin] 3.1) ∃j: pj > p[imin] -> "+t" ∈ D(T,Pj) -> only pi=p[imin] remains to investigate 3.2) pi = p[imin] -> investigate δ(t,pi) | | v 3.1+3.2) looking at δ(t,pi) ∀i: pi=p[imin] - if all != ø -> ⎧δ(t,pi) - if pi=p[imin] -> D += ⎨ ⎩"+t" - if pi>p[imin] in any case t↓ ∀ pi=p[imin] pi↓ ~ For comparison, here is how diff_tree() works: D(A,B) calculation scheme ------------------------- A B - - |a| |b| a < b -> a ∉ B -> D(A,B) += +a a↓ |-| |-| a > b -> b ∉ A -> D(A,B) += -b b↓ | | | | a = b -> investigate δ(a,b) a↓ b↓ |-| |-| |.| |.| . . . . ~~~~~~~~ This patch generalizes diff tree-walker to work with arbitrary number of parents as described above - i.e. now there is a resulting tree t, and some parents trees tp[i] i=[0..nparent). The generalization builds on the fact that usual diff D(A,B) is by definition the same as combined diff D(A,[B]), so if we could rework the code for common case and make it be not slower for nparent=1 case, usual diff(t1,t2) generation will not be slower, and multiparent diff tree-walker would greatly benefit generating combine-diff. What we do is as follows: 1) diff tree-walker ll_diff_tree_sha1() is internally reworked to be a paths generator (new name diff_tree_paths()), with each generated path being `struct combine_diff_path` with info for path, new sha1,mode and for every parent which sha1,mode it was in it. 2) From that info, we can still generate usual diff queue with struct diff_filepairs, via "exporting" generated combine_diff_path, if we know we run for nparent=1 case. (see emit_diff() which is now named emit_diff_first_parent_only()) 3) In order for diff_can_quit_early(), which checks DIFF_OPT_TST(opt, HAS_CHANGES)) to work, that exporting have to be happening not in bulk, but incrementally, one diff path at a time. For such consumers, there is a new callback in diff_options introduced: ->pathchange(opt, struct combine_diff_path *) which, if set to !NULL, is called for every generated path. (see new compat ll_diff_tree_sha1() wrapper around new paths generator for setup) 4) The paths generation itself, is reworked from previous ll_diff_tree_sha1() code according to "D(A,P1...Pn) calculation scheme" provided above: On the start we allocate [nparent] arrays in place what was earlier just for one parent tree. then we just generalize loops, and comparison according to the algorithm. Some notes(*): 1) alloca(), for small arrays, is used for "runs not slower for nparent=1 case than before" goal - if we change it to xmalloc()/free() the timings get ~1% worse. For alloca() we use just-introduced xalloca/xalloca_free compatibility wrappers, so it should not be a portability problem. 2) For every parent tree, we need to keep a tag, whether entry from that parent equals to entry from minimal parent. For performance reasons I'm keeping that tag in entry's mode field in unused bit - see S_IFXMIN_NEQ. Not doing so, we'd need to alloca another [nparent] array, which hurts performance. 3) For emitted paths, memory could be reused, if we know the path was processed via callback and will not be needed later. We use efficient hand-made realloc-style path_appendnew(), that saves us from ~1-1.5% of potential additional slowdown. 4) goto(s) are used in several places, as the code executes a little bit faster with lowered register pressure. Also - we should now check for FIND_COPIES_HARDER not only when two entries names are the same, and their hashes are equal, but also for a case, when a path was removed from some of all parents having it. The reason is, if we don't, that path won't be emitted at all (see "a > xi" case), and we'll just skip it, and FIND_COPIES_HARDER wants all paths - with diff or without - to be emitted, to be later analyzed for being copies sources. The new check is only necessary for nparent >1, as for nparent=1 case xmin_eqtotal always =1 =nparent, and a path is always added to diff as removal. ~~~~~~~~ Timings for # without -c, i.e. testing only nparent=1 case `git log --raw --no-abbrev --no-renames` before and after the patch are as follows: navy.git linux.git v3.10..v3.11 before 0.611s 1.889s after 0.619s 1.907s slowdown 1.3% 0.9% This timings show we did no harm to usual diff(tree1,tree2) generation. From the table we can see that we actually did ~1% slowdown, but I think I've "earned" that 1% in the previous patch ("tree-diff: reuse base str(buf) memory on sub-tree recursion", HEAD~~) so for nparent=1 case, net timings stays approximately the same. The output also stayed the same. (*) If we revert 1)-4) to more usual techniques, for nparent=1 case, we'll get ~2-2.5% of additional slowdown, which I've tried to avoid, as "do no harm for nparent=1 case" rule. For linux.git, combined diff will run an order of magnitude faster and appropriate timings will be provided in the next commit, as we'll be taking advantage of the new diff tree-walker for combined-diff generation there. P.S. and combined diff is not some exotic/for-play-only stuff - for example for a program I write to represent Git archives as readonly filesystem, there is initial scan with `git log --reverse --raw --no-abbrev --no-renames -c` to extract log of what was created/changed when, as a result building a map {} sha1 -> in which commit (and date) a content was added that `-c` means also show combined diff for merges, and without them, if a merge is non-trivial (merges changes from two parents with both having separate changes to a file), or an evil one, the map will not be full, i.e. some valid sha1 would be absent from it. That case was my initial motivation for combined diffs speedup. Signed-off-by: Kirill Smelkov <kirr@mns.spb.ru> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Kirill Smelkov committed Apr 7, 2014 at 01:46 UTC 72441af7c4e3bde33cdf7edafcf09c227d5d5296
4 files changed +465 -64
cache.h
+15
@@ -74,6 +74,21 @@ unsigned long git_deflate_bound(git_zstream *, unsigned long);
74 #define S_IFGITLINK 0160000
75 #define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK)
76
77 +/*
78 + * Some mode bits are also used internally for computations.
79 + *
80 + * They *must* not overlap with any valid modes, and they *must* not be emitted
81 + * to outside world - i.e. appear on disk or network. In other words, it's just
82 + * temporary fields, which we internally use, but they have to stay in-house.
83 + *
84 + * ( such approach is valid, as standard S_IF* fits into 16 bits, and in Git
85 + * codebase mode is `unsigned int` which is assumed to be at least 32 bits )
86 + */
87 +
88 +/* used internally in tree-diff */
89 +#define S_DIFFTREE_IFXMIN_NEQ 0x80000000
90 +
91 +
92 /*
93 * Intensive research over the course of many years has shown that
94 * port 9418 is totally unused by anything else. Or
diff.c
+1
@@ -3216,6 +3216,7 @@ void diff_setup(struct diff_options *options)
3216 options->context = diff_context_default;
3217 DIFF_OPT_SET(options, RENAME_EMPTY);
3218
3219 + /* pathchange left =NULL by default */
3220 options->change = diff_change;
3221 options->add_remove = diff_addremove;
3222 options->use_color = diff_use_color_default;
diff.h
+9
@@ -15,6 +15,10 @@ struct diff_filespec;
15 struct userdiff_driver;
16 struct sha1_array;
17 struct commit;
18 +struct combine_diff_path;
19 +
20 +typedef int (*pathchange_fn_t)(struct diff_options *options,
21 + struct combine_diff_path *path);
22
23 typedef void (*change_fn_t)(struct diff_options *options,
24 unsigned old_mode, unsigned new_mode,
@@ -157,6 +161,7 @@ struct diff_options {
161 int close_file;
162
163 struct pathspec pathspec;
164 + pathchange_fn_t pathchange;
165 change_fn_t change;
166 add_remove_fn_t add_remove;
167 diff_format_fn_t format_callback;
@@ -189,6 +194,10 @@ const char *diff_line_prefix(struct diff_options *);
194
195 extern const char mime_boundary_leader[];
196
197 +extern struct combine_diff_path *diff_tree_paths(
198 + struct combine_diff_path *p, const unsigned char *sha1,
199 + const unsigned char **parent_sha1, int nparent,
200 + struct strbuf *base, struct diff_options *opt);
201 extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new,
202 const char *base, struct diff_options *opt);
203 extern int diff_root_tree_sha1(const unsigned char *new, const char *base,
tree-diff.c
+440 -64
@@ -6,7 +6,19 @@
6 #include "diffcore.h"
7 #include "tree.h"
8
9 +/*
10 + * internal mode marker, saying a tree entry != entry of tp[imin]
11 + * (see ll_diff_tree_paths for what it means there)
12 + *
13 + * we will update/use/emit entry for diff only with it unset.
14 + */
15 +#define S_IFXMIN_NEQ S_DIFFTREE_IFXMIN_NEQ
16 +
17
18 +static struct combine_diff_path *ll_diff_tree_paths(
19 + struct combine_diff_path *p, const unsigned char *sha1,
20 + const unsigned char **parents_sha1, int nparent,
21 + struct strbuf *base, struct diff_options *opt);
22 static int ll_diff_tree_sha1(const unsigned char *old, const unsigned char *new,
23 struct strbuf *base, struct diff_options *opt);
24
@@ -42,71 +54,151 @@ static int tree_entry_pathcmp(struct tree_desc *t1, struct tree_desc *t2)
54 }
55
56
45 -/* convert path, t1/t2 -> opt->diff_*() callbacks */
46 -static void emit_diff(struct diff_options *opt, struct strbuf *path,
47 - struct tree_desc *t1, struct tree_desc *t2)
57 +/*
58 + * convert path -> opt->diff_*() callbacks
59 + *
60 + * emits diff to first parent only, and tells diff tree-walker that we are done
61 + * with p and it can be freed.
62 + */
63 +static int emit_diff_first_parent_only(struct diff_options *opt, struct combine_diff_path *p)
64 {
49 - unsigned int mode1 = t1 ? t1->entry.mode : 0;
50 - unsigned int mode2 = t2 ? t2->entry.mode : 0;
51 -
52 - if (mode1 && mode2) {
53 - opt->change(opt, mode1, mode2, t1->entry.sha1, t2->entry.sha1,
54 - 1, 1, path->buf, 0, 0);
65 + struct combine_diff_parent *p0 = &p->parent[0];
66 + if (p->mode && p0->mode) {
67 + opt->change(opt, p0->mode, p->mode, p0->sha1, p->sha1,
68 + 1, 1, p->path, 0, 0);
69 }
70 else {
71 const unsigned char *sha1;
72 unsigned int mode;
73 int addremove;
74
61 - if (mode2) {
75 + if (p->mode) {
76 addremove = '+';
63 - sha1 = t2->entry.sha1;
64 - mode = mode2;
77 + sha1 = p->sha1;
78 + mode = p->mode;
79 } else {
80 addremove = '-';
67 - sha1 = t1->entry.sha1;
68 - mode = mode1;
81 + sha1 = p0->sha1;
82 + mode = p0->mode;
83 }
84
71 - opt->add_remove(opt, addremove, mode, sha1, 1, path->buf, 0);
85 + opt->add_remove(opt, addremove, mode, sha1, 1, p->path, 0);
86 }
87 +
88 + return 0; /* we are done with p */
89 }
90
91
76 -/* new path should be added to diff
92 +/*
93 + * Make a new combine_diff_path from path/mode/sha1
94 + * and append it to paths list tail.
95 + *
96 + * Memory for created elements could be reused:
97 + *
98 + * - if last->next == NULL, the memory is allocated;
99 + *
100 + * - if last->next != NULL, it is assumed that p=last->next was returned
101 + * earlier by this function, and p->next was *not* modified.
102 + * The memory is then reused from p.
103 + *
104 + * so for clients,
105 + *
106 + * - if you do need to keep the element
107 + *
108 + * p = path_appendnew(p, ...);
109 + * process(p);
110 + * p->next = NULL;
111 + *
112 + * - if you don't need to keep the element after processing
113 + *
114 + * pprev = p;
115 + * p = path_appendnew(p, ...);
116 + * process(p);
117 + * p = pprev;
118 + * ; don't forget to free tail->next in the end
119 + *
120 + * p->parent[] remains uninitialized.
121 + */
122 +static struct combine_diff_path *path_appendnew(struct combine_diff_path *last,
123 + int nparent, const struct strbuf *base, const char *path, int pathlen,
124 + unsigned mode, const unsigned char *sha1)
125 +{
126 + struct combine_diff_path *p;
127 + int len = base->len + pathlen;
128 + int alloclen = combine_diff_path_size(nparent, len);
129 +
130 + /* if last->next is !NULL - it is a pre-allocated memory, we can reuse */
131 + p = last->next;
132 + if (p && (alloclen > (intptr_t)p->next)) {
133 + free(p);
134 + p = NULL;
135 + }
136 +
137 + if (!p) {
138 + p = xmalloc(alloclen);
139 +
140 + /*
141 + * until we go to it next round, .next holds how many bytes we
142 + * allocated (for faster realloc - we don't need copying old data).
143 + */
144 + p->next = (struct combine_diff_path *)(intptr_t)alloclen;
145 + }
146 +
147 + last->next = p;
148 +
149 + p->path = (char *)&(p->parent[nparent]);
150 + memcpy(p->path, base->buf, base->len);
151 + memcpy(p->path + base->len, path, pathlen);
152 + p->path[len] = 0;
153 + p->mode = mode;
154 + hashcpy(p->sha1, sha1 ? sha1 : null_sha1);
155 +
156 + return p;
157 +}
158 +
159 +/*
160 + * new path should be added to combine diff
161 *
162 * 3 cases on how/when it should be called and behaves:
163 *
80 - * !t1, t2 -> path added, parent lacks it
81 - * t1, !t2 -> path removed from parent
82 - * t1, t2 -> path modified
164 + * t, !tp -> path added, all parents lack it
165 + * !t, tp -> path removed from all parents
166 + * t, tp -> path modified/added
167 + * (M for tp[i]=tp[imin], A otherwise)
168 */
84 -static void show_path(struct strbuf *base, struct diff_options *opt,
85 - struct tree_desc *t1, struct tree_desc *t2)
169 +static struct combine_diff_path *emit_path(struct combine_diff_path *p,
170 + struct strbuf *base, struct diff_options *opt, int nparent,
171 + struct tree_desc *t, struct tree_desc *tp,
172 + int imin)
173 {
174 unsigned mode;
175 const char *path;
176 + const unsigned char *sha1;
177 int pathlen;
178 int old_baselen = base->len;
91 - int isdir, recurse = 0, emitthis = 1;
179 + int i, isdir, recurse = 0, emitthis = 1;
180
181 /* at least something has to be valid */
94 - assert(t1 || t2);
182 + assert(t || tp);
183
96 - if (t2) {
184 + if (t) {
185 /* path present in resulting tree */
98 - tree_entry_extract(t2, &path, &mode);
99 - pathlen = tree_entry_len(&t2->entry);
186 + sha1 = tree_entry_extract(t, &path, &mode);
187 + pathlen = tree_entry_len(&t->entry);
188 isdir = S_ISDIR(mode);
189 } else {
190 /*
103 - * a path was removed - take path from parent. Also take
104 - * mode from parent, to decide on recursion.
191 + * a path was removed - take path from imin parent. Also take
192 + * mode from that parent, to decide on recursion(1).
193 + *
194 + * 1) all modes for tp[i]=tp[imin] should be the same wrt
195 + * S_ISDIR, thanks to base_name_compare().
196 */
106 - tree_entry_extract(t1, &path, &mode);
107 - pathlen = tree_entry_len(&t1->entry);
197 + tree_entry_extract(&tp[imin], &path, &mode);
198 + pathlen = tree_entry_len(&tp[imin].entry);
199
200 isdir = S_ISDIR(mode);
201 + sha1 = NULL;
202 mode = 0;
203 }
204
@@ -115,18 +207,81 @@ static void show_path(struct strbuf *base, struct diff_options *opt,
207 emitthis = DIFF_OPT_TST(opt, TREE_IN_RECURSIVE);
208 }
209
118 - strbuf_add(base, path, pathlen);
210 + if (emitthis) {
211 + int keep;
212 + struct combine_diff_path *pprev = p;
213 + p = path_appendnew(p, nparent, base, path, pathlen, mode, sha1);
214 +
215 + for (i = 0; i < nparent; ++i) {
216 + /*
217 + * tp[i] is valid, if present and if tp[i]==tp[imin] -
218 + * otherwise, we should ignore it.
219 + */
220 + int tpi_valid = tp && !(tp[i].entry.mode & S_IFXMIN_NEQ);
221 +
222 + const unsigned char *sha1_i;
223 + unsigned mode_i;
224 +
225 + p->parent[i].status =
226 + !t ? DIFF_STATUS_DELETED :
227 + tpi_valid ?
228 + DIFF_STATUS_MODIFIED :
229 + DIFF_STATUS_ADDED;
230 +
231 + if (tpi_valid) {
232 + sha1_i = tp[i].entry.sha1;
233 + mode_i = tp[i].entry.mode;
234 + }
235 + else {
236 + sha1_i = NULL;
237 + mode_i = 0;
238 + }
239 +
240 + p->parent[i].mode = mode_i;
241 + hashcpy(p->parent[i].sha1, sha1_i ? sha1_i : null_sha1);
242 + }
243
120 - if (emitthis)
121 - emit_diff(opt, base, t1, t2);
244 + keep = 1;
245 + if (opt->pathchange)
246 + keep = opt->pathchange(opt, p);
247 +
248 + /*
249 + * If a path was filtered or consumed - we don't need to add it
250 + * to the list and can reuse its memory, leaving it as
251 + * pre-allocated element on the tail.
252 + *
253 + * On the other hand, if path needs to be kept, we need to
254 + * correct its .next to NULL, as it was pre-initialized to how
255 + * much memory was allocated.
256 + *
257 + * see path_appendnew() for details.
258 + */
259 + if (!keep)
260 + p = pprev;
261 + else
262 + p->next = NULL;
263 + }
264
265 if (recurse) {
266 + const unsigned char **parents_sha1;
267 +
268 + parents_sha1 = xalloca(nparent * sizeof(parents_sha1[0]));
269 + for (i = 0; i < nparent; ++i) {
270 + /* same rule as in emitthis */
271 + int tpi_valid = tp && !(tp[i].entry.mode & S_IFXMIN_NEQ);
272 +
273 + parents_sha1[i] = tpi_valid ? tp[i].entry.sha1
274 + : NULL;
275 + }
276 +
277 + strbuf_add(base, path, pathlen);
278 strbuf_addch(base, '/');
125 - ll_diff_tree_sha1(t1 ? t1->entry.sha1 : NULL,
126 - t2 ? t2->entry.sha1 : NULL, base, opt);
279 + p = ll_diff_tree_paths(p, sha1, parents_sha1, nparent, base, opt);
280 + xalloca_free(parents_sha1);
281 }
282
283 strbuf_setlen(base, old_baselen);
284 + return p;
285 }
286
287 static void skip_uninteresting(struct tree_desc *t, struct strbuf *base,
@@ -145,59 +300,260 @@ static void skip_uninteresting(struct tree_desc *t, struct strbuf *base,
300 }
301 }
302
148 -static int ll_diff_tree_sha1(const unsigned char *old, const unsigned char *new,
149 - struct strbuf *base, struct diff_options *opt)
303 +
304 +/*
305 + * generate paths for combined diff D(sha1,parents_sha1[])
306 + *
307 + * Resulting paths are appended to combine_diff_path linked list, and also, are
308 + * emitted on the go via opt->pathchange() callback, so it is possible to
309 + * process the result as batch or incrementally.
310 + *
311 + * The paths are generated scanning new tree and all parents trees
312 + * simultaneously, similarly to what diff_tree() was doing for 2 trees.
313 + * The theory behind such scan is as follows:
314 + *
315 + *
316 + * D(T,P1...Pn) calculation scheme
317 + * -------------------------------
318 + *
319 + * D(T,P1...Pn) = D(T,P1) ^ ... ^ D(T,Pn) (regarding resulting paths set)
320 + *
321 + * D(T,Pj) - diff between T..Pj
322 + * D(T,P1...Pn) - combined diff from T to parents P1,...,Pn
323 + *
324 + *
325 + * We start from all trees, which are sorted, and compare their entries in
326 + * lock-step:
327 + *
328 + * T P1 Pn
329 + * - - -
330 + * |t| |p1| |pn|
331 + * |-| |--| ... |--| imin = argmin(p1...pn)
332 + * | | | | | |
333 + * |-| |--| |--|
334 + * |.| |. | |. |
335 + * . . .
336 + * . . .
337 + *
338 + * at any time there could be 3 cases:
339 + *
340 + * 1) t < p[imin];
341 + * 2) t > p[imin];
342 + * 3) t = p[imin].
343 + *
344 + * Schematic deduction of what every case means, and what to do, follows:
345 + *
346 + * 1) t < p[imin] -> ∀j t ∉ Pj -> "+t" ∈ D(T,Pj) -> D += "+t"; t↓
347 + *
348 + * 2) t > p[imin]
349 + *
350 + * 2.1) ∃j: pj > p[imin] -> "-p[imin]" ∉ D(T,Pj) -> D += ø; ∀ pi=p[imin] pi↓
351 + * 2.2) ∀i pi = p[imin] -> pi ∉ T -> "-pi" ∈ D(T,Pi) -> D += "-p[imin]"; ∀i pi↓
352 + *
353 + * 3) t = p[imin]
354 + *
355 + * 3.1) ∃j: pj > p[imin] -> "+t" ∈ D(T,Pj) -> only pi=p[imin] remains to investigate
356 + * 3.2) pi = p[imin] -> investigate δ(t,pi)
357 + * |
358 + * |
359 + * v
360 + *
361 + * 3.1+3.2) looking at δ(t,pi) ∀i: pi=p[imin] - if all != ø ->
362 + *
363 + * ⎧δ(t,pi) - if pi=p[imin]
364 + * -> D += ⎨
365 + * ⎩"+t" - if pi>p[imin]
366 + *
367 + *
368 + * in any case t↓ ∀ pi=p[imin] pi↓
369 + *
370 + *
371 + * ~~~~~~~~
372 + *
373 + * NOTE
374 + *
375 + * Usual diff D(A,B) is by definition the same as combined diff D(A,[B]),
376 + * so this diff paths generator can, and is used, for plain diffs
377 + * generation too.
378 + *
379 + * Please keep attention to the common D(A,[B]) case when working on the
380 + * code, in order not to slow it down.
381 + *
382 + * NOTE
383 + * nparent must be > 0.
384 + */
385 +
386 +
387 +/* ∀ pi=p[imin] pi↓ */
388 +static inline void update_tp_entries(struct tree_desc *tp, int nparent)
389 {
151 - struct tree_desc t1, t2;
152 - void *t1tree, *t2tree;
390 + int i;
391 + for (i = 0; i < nparent; ++i)
392 + if (!(tp[i].entry.mode & S_IFXMIN_NEQ))
393 + update_tree_entry(&tp[i]);
394 +}
395
154 - t1tree = fill_tree_descriptor(&t1, old);
155 - t2tree = fill_tree_descriptor(&t2, new);
396 +static struct combine_diff_path *ll_diff_tree_paths(
397 + struct combine_diff_path *p, const unsigned char *sha1,
398 + const unsigned char **parents_sha1, int nparent,
399 + struct strbuf *base, struct diff_options *opt)
400 +{
401 + struct tree_desc t, *tp;
402 + void *ttree, **tptree;
403 + int i;
404 +
405 + tp = xalloca(nparent * sizeof(tp[0]));
406 + tptree = xalloca(nparent * sizeof(tptree[0]));
407 +
408 + /*
409 + * load parents first, as they are probably already cached.
410 + *
411 + * ( log_tree_diff() parses commit->parent before calling here via
412 + * diff_tree_sha1(parent, commit) )
413 + */
414 + for (i = 0; i < nparent; ++i)
415 + tptree[i] = fill_tree_descriptor(&tp[i], parents_sha1[i]);
416 + ttree = fill_tree_descriptor(&t, sha1);
417
418 /* Enable recursion indefinitely */
419 opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE);
420
421 for (;;) {
161 - int cmp;
422 + int imin, cmp;
423
424 if (diff_can_quit_early(opt))
425 break;
426 +
427 if (opt->pathspec.nr) {
166 - skip_uninteresting(&t1, base, opt);
167 - skip_uninteresting(&t2, base, opt);
428 + skip_uninteresting(&t, base, opt);
429 + for (i = 0; i < nparent; i++)
430 + skip_uninteresting(&tp[i], base, opt);
431 + }
432 +
433 + /* comparing is finished when all trees are done */
434 + if (!t.size) {
435 + int done = 1;
436 + for (i = 0; i < nparent; ++i)
437 + if (tp[i].size) {
438 + done = 0;
439 + break;
440 + }
441 + if (done)
442 + break;
443 + }
444 +
445 + /*
446 + * lookup imin = argmin(p1...pn),
447 + * mark entries whether they =p[imin] along the way
448 + */
449 + imin = 0;
450 + tp[0].entry.mode &= ~S_IFXMIN_NEQ;
451 +
452 + for (i = 1; i < nparent; ++i) {
453 + cmp = tree_entry_pathcmp(&tp[i], &tp[imin]);
454 + if (cmp < 0) {
455 + imin = i;
456 + tp[i].entry.mode &= ~S_IFXMIN_NEQ;
457 + }
458 + else if (cmp == 0) {
459 + tp[i].entry.mode &= ~S_IFXMIN_NEQ;
460 + }
461 + else {
462 + tp[i].entry.mode |= S_IFXMIN_NEQ;
463 + }
464 }
169 - if (!t1.size && !t2.size)
170 - break;
465
172 - cmp = tree_entry_pathcmp(&t1, &t2);
466 + /* fixup markings for entries before imin */
467 + for (i = 0; i < imin; ++i)
468 + tp[i].entry.mode |= S_IFXMIN_NEQ; /* pi > p[imin] */
469
174 - /* t1 = t2 */
175 - if (cmp == 0) {
176 - if (DIFF_OPT_TST(opt, FIND_COPIES_HARDER) ||
177 - hashcmp(t1.entry.sha1, t2.entry.sha1) ||
178 - (t1.entry.mode != t2.entry.mode))
179 - show_path(base, opt, &t1, &t2);
470
181 - update_tree_entry(&t1);
182 - update_tree_entry(&t2);
471 +
472 + /* compare t vs p[imin] */
473 + cmp = tree_entry_pathcmp(&t, &tp[imin]);
474 +
475 + /* t = p[imin] */
476 + if (cmp == 0) {
477 + /* are either pi > p[imin] or diff(t,pi) != ø ? */
478 + if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER)) {
479 + for (i = 0; i < nparent; ++i) {
480 + /* p[i] > p[imin] */
481 + if (tp[i].entry.mode & S_IFXMIN_NEQ)
482 + continue;
483 +
484 + /* diff(t,pi) != ø */
485 + if (hashcmp(t.entry.sha1, tp[i].entry.sha1) ||
486 + (t.entry.mode != tp[i].entry.mode))
487 + continue;
488 +
489 + goto skip_emit_t_tp;
490 + }
491 + }
492 +
493 + /* D += {δ(t,pi) if pi=p[imin]; "+a" if pi > p[imin]} */
494 + p = emit_path(p, base, opt, nparent,
495 + &t, tp, imin);
496 +
497 + skip_emit_t_tp:
498 + /* t↓, ∀ pi=p[imin] pi↓ */
499 + update_tree_entry(&t);
500 + update_tp_entries(tp, nparent);
501 }
502
185 - /* t1 < t2 */
503 + /* t < p[imin] */
504 else if (cmp < 0) {
187 - show_path(base, opt, &t1, /*t2=*/NULL);
188 - update_tree_entry(&t1);
505 + /* D += "+t" */
506 + p = emit_path(p, base, opt, nparent,
507 + &t, /*tp=*/NULL, -1);
508 +
509 + /* t↓ */
510 + update_tree_entry(&t);
511 }
512
191 - /* t1 > t2 */
513 + /* t > p[imin] */
514 else {
193 - show_path(base, opt, /*t1=*/NULL, &t2);
194 - update_tree_entry(&t2);
515 + /* ∀i pi=p[imin] -> D += "-p[imin]" */
516 + if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER)) {
517 + for (i = 0; i < nparent; ++i)
518 + if (tp[i].entry.mode & S_IFXMIN_NEQ)
519 + goto skip_emit_tp;
520 + }
521 +
522 + p = emit_path(p, base, opt, nparent,
523 + /*t=*/NULL, tp, imin);
524 +
525 + skip_emit_tp:
526 + /* ∀ pi=p[imin] pi↓ */
527 + update_tp_entries(tp, nparent);
528 }
529 }
530
198 - free(t2tree);
199 - free(t1tree);
200 - return 0;
531 + free(ttree);
532 + for (i = nparent-1; i >= 0; i--)
533 + free(tptree[i]);
534 + xalloca_free(tptree);
535 + xalloca_free(tp);
536 +
537 + return p;
538 +}
539 +
540 +struct combine_diff_path *diff_tree_paths(
541 + struct combine_diff_path *p, const unsigned char *sha1,
542 + const unsigned char **parents_sha1, int nparent,
543 + struct strbuf *base, struct diff_options *opt)
544 +{
545 + p = ll_diff_tree_paths(p, sha1, parents_sha1, nparent, base, opt);
546 +
547 + /*
548 + * free pre-allocated last element, if any
549 + * (see path_appendnew() for details about why)
550 + */
551 + if (p->next) {
552 + free(p->next);
553 + p->next = NULL;
554 + }
555 +
556 + return p;
557 }
558
559 /*
@@ -308,6 +664,26 @@ static void try_to_follow_renames(const unsigned char *old, const unsigned char
664 q->nr = 1;
665 }
666
667 +static int ll_diff_tree_sha1(const unsigned char *old, const unsigned char *new,
668 + struct strbuf *base, struct diff_options *opt)
669 +{
670 + struct combine_diff_path phead, *p;
671 + pathchange_fn_t pathchange_old = opt->pathchange;
672 +
673 + phead.next = NULL;
674 + opt->pathchange = emit_diff_first_parent_only;
675 + diff_tree_paths(&phead, new, &old, 1, base, opt);
676 +
677 + for (p = phead.next; p;) {
678 + struct combine_diff_path *pprev = p;
679 + p = p->next;
680 + free(pprev);
681 + }
682 +
683 + opt->pathchange = pathchange_old;
684 + return 0;
685 +}
686 +
687 int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base_str, struct diff_options *opt)
688 {
689 struct strbuf base;