As was recently shown (c839f1bd "combine-diff: optimize
combine_diff_path sets intersection"), combine-diff runs very slowly. In
that commit we optimized paths sets intersection, but that accounted
only for ~ 25% of the slowness, and as my tracing showed, for linux.git
v3.10..v3.11, for merges 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(A,X1...Xn) calculation scheme
-------------------------------
D(A,X1...Xn) = D(A,X1) ^ ... ^ D(A,Xn) (regarding resulting paths set)
D(A,Xj) - diff between A..Xj
D(A,X1...Xn) - combined diff from A to parents X1,...,Xn
We start from all trees, which are sorted, and compare their entries in
lock-step:
A X1 Xn
- - -
|a| |x1| |xn|
|-| |--| ... |--| i = argmin(x1...xn)
| | | | | |
|-| |--| |--|
|.| |. | |. |
. . .
. . .
at any time there could be 3 cases:
1) a < xi;
2) a > xi;
3) a = xi.
Schematic deduction of what every case means, and what to do, follows:
1) a < xi -> ∀j a ∉ Xj -> "+a" ∈ D(A,Xj) -> D += "+a"; a↓
2) a > xi
2.1) ∃j: xj > xi -> "-xi" ∉ D(A,Xj) -> D += ø; ∀ xk=xi xk↓
2.2) ∀j xj = xi -> xj ∉ A -> "-xj" ∈ D(A,Xj) -> D += "-xi"; ∀j
xj↓
3) a = xi
3.1) ∃j: xj > xi -> "+a" ∈ D(A,Xj) -> only xk=xi remains to
investigate
3.2) xj = xi -> investigate δ(a,xj)
|
|
v
3.1+3.2) looking at δ(a,xk) ∀k: xk=xi - if all != ø ->
⎧δ(a,xk) - if xk=xi
-> D += ⎨
⎩"+a" - if xk>xi
in any case a↓ ∀ xk=xi xk↓
~
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↓
|-| |-|
|.| |.|
. .
. .
For now there is a limitation however - for simple scan to work, we have
to know in advance, a user had not asked for finding rename/copies
detection. At present, if he/she has, we fallback to calling old n
1-parent diffs code. I think that restriction, at least in theory,
could be lifted. I propose we have fast code for at least common case,
and move on incrementally.
Another similar limitation, is that if diffcore transforms which
filter-out paths, are active, we also fallback to old code. The reason
here is pure "technical" - all transforms expect to find paths in
diff_filepair's queue, which applies only to 1-parent case. If needed,
The solution here would be to generalize diff_filepair to
combine_diff_path everywhere, and do not differentiate between plain and
combined diffs (all diffs will be combined with diff(A,B) being
combined diff with only 1 parent), but again, let's move on
incrementally.
I consciously placed trees-scanning code in tree-diff.c, foreseeing diff
and combine-diff merge, and because that code is very similar to
diff_tree - to keep similar codes near.
Timings for `git log --raw --no-abbrev --no-renames` without `-c` ("git log")
and with `-c` ("git log -c") and with `-c --merges` ("git log -c --merges")
before and after the patch are as follows:
linux.git v3.10..v3.11
log log -c log -c --merges
before 1.9s 16.6s 15.5s
after 1.9s 2.4s 1.1s(*)
The result stayed the same.
(*) that 15.5/1.1 = ~14.1 ratio, is the most appropriate estimate for
combine-diff speedup.
Signed-off-by: Kirill Smelkov <[email protected]>
---
combine-diff.c | 85 +++++++++++-
diff.c | 1 +
diff.h | 6 +
tree-diff.c | 411 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 498 insertions(+), 5 deletions(-)
diff --git a/combine-diff.c b/combine-diff.c
index 427a7c1..3e3f328 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -1305,7 +1305,7 @@ static const char *path_path(void *obj)
/* find set of paths that every parent touches */
-static struct combine_diff_path *find_paths(const unsigned char *sha1,
+static struct combine_diff_path *find_paths_generic(const unsigned char *sha1,
const struct sha1_array *parents, struct diff_options *opt)
{
struct combine_diff_path *paths = NULL;
@@ -1318,6 +1318,7 @@ static struct combine_diff_path *find_paths(const
unsigned char *sha1,
/* tell diff_tree to emit paths in sorted (=tree) order */
opt->orderfile = NULL;
+ /* D(A,P1...Pn) = D(A,P1) ^ ... ^ D(A,Pn) (wrt paths) */
for (i = 0; i < num_parent; i++) {
/* show stat against the first parent even
* when doing combined diff.
@@ -1347,6 +1348,35 @@ static struct combine_diff_path *find_paths(const
unsigned char *sha1,
}
+/*
+ * find set of paths that everybody touches, assuming diff is run without
+ * rename/copy detection, etc, comparing all trees simultaneously (= faster).
+ */
+static struct combine_diff_path *find_paths_multitree(
+ const unsigned char *sha1, const struct sha1_array *parents,
+ struct diff_options *opt)
+{
+ int i, nparent = parents->nr;
+ const unsigned char **parents_sha1;
+
+ parents_sha1 = xmalloc(nparent * sizeof(parents_sha1[0]));
+ for (i = 0; i < nparent; i++)
+ parents_sha1[i] = parents->sha1[i];
+
+ /* fake list head, so worker can assume it is non-NULL */
+ struct combine_diff_path paths_head;
+ paths_head.next = NULL;
+
+ struct strbuf base;
+ strbuf_init(&base, PATH_MAX);
+
+ diff_tree_combined_paths(&paths_head, sha1, parents_sha1, nparent,
&base, opt);
+
+ free(parents_sha1);
+ return paths_head.next;
+}
+
+
void diff_tree_combined(const unsigned char *sha1,
const struct sha1_array *parents,
int dense,
@@ -1356,6 +1386,7 @@ void diff_tree_combined(const unsigned char *sha1,
struct diff_options diffopts;
struct combine_diff_path *p, *paths;
int i, num_paths, needsep, show_log_first, num_parent = parents->nr;
+ int need_generic_pathscan;
/* nothing to do, if no parents */
if (!num_parent)
@@ -1378,11 +1409,55 @@ void diff_tree_combined(const unsigned char *sha1,
/* find set of paths that everybody touches
*
- * NOTE find_paths() also handles --stat, as it computes
- * diff(sha1,parent_i) for all i to do the job, specifically
- * for parent0.
+ * NOTE
+ *
+ * Diffcore transformations are bound to diff_filespec and logic
+ * comparing two entries - i.e. they do not apply directly to combine
+ * diff.
+ *
+ * If some of such transformations is requested - we launch generic
+ * path scanning, which works significantly slower compared to
+ * simultaneous all-trees-in-one-go scan in find_paths_multitree().
+ *
+ * TODO some of the filters could be ported to work on
+ * combine_diff_paths - i.e. all functionality that skips paths, so in
+ * theory, we could end up having only multitree path scanning.
+ *
+ * NOTE please keep this semantically in sync with diffcore_std()
*/
- paths = find_paths(sha1, parents, &diffopts);
+ need_generic_pathscan = opt->skip_stat_unmatch ||
+ DIFF_OPT_TST(opt, FOLLOW_RENAMES) ||
+ opt->break_opt != -1 ||
+ opt->detect_rename ||
+ opt->pickaxe ||
+ opt->filter;
+
+
+ if (need_generic_pathscan) {
+ /* NOTE generic case also handles --stat, as it computes
+ * diff(sha1,parent_i) for all i to do the job, specifically
+ * for parent0.
+ */
+ paths = find_paths_generic(sha1, parents, &diffopts);
+ }
+ else {
+ paths = find_paths_multitree(sha1, parents, &diffopts);
+
+ /* show stat against the first parent even
+ * when doing combined diff.
+ */
+ int stat_opt = (opt->output_format &
+ (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT));
+ if (stat_opt) {
+ diffopts.output_format = stat_opt;
+
+ diff_tree_sha1(parents->sha1[0], sha1, "", &diffopts);
+ diffcore_std(&diffopts);
+ if (opt->orderfile)
+ diffcore_order(opt->orderfile);
+ diff_flush(&diffopts);
+ }
+ }
/* find out number of surviving paths */
for (num_paths = 0, p = paths; p; p = p->next)
diff --git a/diff.c b/diff.c
index 49e71f4..29ede0b 100644
--- a/diff.c
+++ b/diff.c
@@ -4755,6 +4755,7 @@ void diffcore_fix_diff_index(struct diff_options *options)
void diffcore_std(struct diff_options *options)
{
+ /* NOTE please keep the following in sync with diff_tree_combined() */
if (options->skip_stat_unmatch)
diffcore_skip_stat_unmatch(options);
if (!options->found_follow) {
diff --git a/diff.h b/diff.h
index a24a767..5496560 100644
--- a/diff.h
+++ b/diff.h
@@ -214,6 +214,12 @@ struct combine_diff_path {
extern void show_combined_diff(struct combine_diff_path *elem, int num_parent,
int dense, struct rev_info *);
+extern
+struct combine_diff_path *diff_tree_combined_paths(
+ struct combine_diff_path *p, const unsigned char *sha1,
+ const unsigned char **parent_sha1, int nparent,
+ struct strbuf *base, struct diff_options *opt);
+
extern void diff_tree_combined(const unsigned char *sha1, const struct
sha1_array *parents, int dense, struct rev_info *rev);
extern void diff_tree_combined_merge(const struct commit *commit, int dense,
struct rev_info *rev);
diff --git a/tree-diff.c b/tree-diff.c
index f7b3ade..7f4f917 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -172,6 +172,417 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
return 0;
}
+
+/*
+ * Compare two tree descriptors, taking into account only path/mode,
+ * but not their sha1's.
+ *
+ * NOTE files and directories *always* compare differently, even when having
+ * the same name - thanks to base_name_compare().
+ *
+ * NOTE empty (=invalid) descriptor(s) take part in comparison as +infty.
+ */
+static int tree_desc_pathcmp(struct tree_desc *t1, struct tree_desc *t2)
+{
+ const unsigned char *t1_sha1, *t2_sha1;
+ const char *path1, *path2;
+ int path1_len, path2_len;
+ unsigned mode1, mode2;
+ int cmp;
+
+ if (!t1->size)
+ return t2->size ? +1 /* +∞ > c */ : 0 /* +∞ = +∞ */;
+ else if (!t2->size)
+ return -1; /* c < +∞ */
+
+
+ t1_sha1 = tree_entry_extract(t1, &path1, &mode1);
+ t2_sha1 = tree_entry_extract(t2, &path2, &mode2);
+
+ path1_len = tree_entry_len(&t1->entry);
+ path2_len = tree_entry_len(&t2->entry);
+
+ cmp = base_name_compare(path1, path1_len, mode1,
+ path2, path2_len, mode2);
+ return cmp;
+}
+
+
+/*
+ * Make a new combine_diff_path from path/mode/sha1
+ * and append it to paths list tail.
+ *
+ * p->parent[] remains uninitialized.
+ */
+static struct combine_diff_path *__path_appendnew(struct combine_diff_path
*last,
+ int nparent, const struct strbuf *base, const char *path, int pathlen,
+ unsigned mode, const unsigned char *sha1)
+{
+ struct combine_diff_path *p;
+ int len = base->len + pathlen;
+
+ p = xmalloc(combine_diff_path_size(nparent, len));
+ p->next = NULL;
+ p->path = (char *)&(p->parent[nparent]);
+ memcpy(p->path, base->buf, base->len);
+ memcpy(p->path + base->len, path, pathlen);
+ p->path[len] = 0;
+ p->mode = mode;
+ hashcpy(p->sha1, sha1);
+
+ last->next = p;
+ return p;
+}
+
+/* new path should be added to combine diff
+ *
+ * 3 cases on how/when it should be called and behaves:
+ *
+ * t, !tp -> path added, all parents lack it
+ * !t, tp -> path removed from all parents
+ * t, tp -> path modified/added
+ * (M for tp[i]=tp[imin], A otherwise)
+ */
+static struct combine_diff_path *path_appendnew(struct combine_diff_path *p,
+ struct strbuf *base, struct diff_options *opt, int nparent,
+ struct tree_desc *t, struct tree_desc *tp,
+ int imin, int *xmin_pathcmpv)
+{
+ int i, isdir, recurse=0, emitthis=1;
+
+ const unsigned char *sha1;
+ const char *path;
+ int pathlen;
+ unsigned mode;
+
+ /* at least something has to be valid */
+ assert(t || tp);
+
+ if (t) {
+ /* path present in resulting tree */
+ sha1 = tree_entry_extract(t, &path, &mode);
+ pathlen = tree_entry_len(&t->entry);
+ isdir = S_ISDIR(mode);
+ }
+ else {
+ /* a path was removed - take path from imin parent. Also take
+ * mode from that parent, to decide on recursion(1).
+ *
+ * 1) all modes for tp[k]=tp[imin] should be the same wrt
+ * S_ISDIR, thanks to base_name_compare().
+ */
+ tree_entry_extract(&tp[imin], &path, &mode);
+ pathlen = tree_entry_len(&tp[imin].entry);
+
+ isdir = S_ISDIR(mode);
+ sha1 = null_sha1;
+ mode = 0;
+ }
+
+
+ if (DIFF_OPT_TST(opt, RECURSIVE) && isdir) {
+ recurse = 1;
+ emitthis = DIFF_OPT_TST(opt, TREE_IN_RECURSIVE);
+ }
+
+ if (emitthis) {
+ p = __path_appendnew(p, nparent, base, path, pathlen, mode,
sha1);
+ for (i = 0; i < nparent; ++i) {
+ /* tp[i] is valid, if present and if tp[i]==tp[imin] -
+ * otherwise, we should ignore it.
+ */
+ int tpi_valid = tp && !xmin_pathcmpv[i];
+
+ const unsigned char *sha1_i;
+ unsigned mode_i;
+
+ p->parent[i].status =
+ !t ? DIFF_STATUS_DELETED :
+ tpi_valid ?
+ DIFF_STATUS_MODIFIED :
+ DIFF_STATUS_ADDED;
+
+ if (tpi_valid) {
+ sha1_i = tp[i].entry.sha1;
+ mode_i = canon_mode(tp[i].entry.mode);
+ }
+ else {
+ sha1_i = null_sha1;
+ mode_i = 0;
+ }
+
+ p->parent[i].mode = mode_i;
+ hashcpy(p->parent[i].sha1, sha1_i);
+ }
+ }
+
+ if (recurse) {
+ const unsigned char **parents_sha1;
+ int old_baselen = base->len;
+
+ parents_sha1 = xmalloc(nparent * sizeof(parents_sha1[0]));
+ for (i = 0; i < nparent; ++i) {
+ /* same rule as in emitthis */
+ int tpi_valid = tp && !xmin_pathcmpv[i];
+
+ parents_sha1[i] = tpi_valid ? tp[i].entry.sha1
+ : null_sha1;
+ }
+
+ /* base += path+'/' */
+ strbuf_add(base, path, pathlen);
+ strbuf_addch(base, '/');
+
+ p = diff_tree_combined_paths(p, sha1, parents_sha1, nparent,
base, opt);
+
+ /* restore base */
+ strbuf_setlen(base, old_baselen);
+
+ free(parents_sha1);
+ }
+
+ return p;
+}
+
+
+/* Like fill_tree_descriptor, but returns empty tree_desc for null_sha1 */
+static void *xfill_tree_descriptor(struct tree_desc *t, const unsigned char
*sha1)
+{
+ if (!is_null_sha1(sha1))
+ return fill_tree_descriptor(t, sha1);
+
+ init_tree_desc(t, NULL, 0);
+ return NULL;
+}
+
+
+/* generate paths for combined diff D(sha1,parents_sha1[])
+ *
+ * The paths are generated scanning new tree and all parents trees
+ * simultaneously, similarly to what diff_tree() does for 2 trees. The theory
+ * behind such scan is as follows:
+ *
+ *
+ * D(A,X1...Xn) calculation scheme
+ * -------------------------------
+ *
+ * D(A,X1...Xn) = D(A,X1) ^ ... ^ D(A,Xn) (regarding resulting paths set)
+ *
+ * D(A,Xj) - diff between A..Xj
+ * D(A,X1...Xn) - combined diff from A to parents X1,...,Xn
+ *
+ *
+ * We start from all trees, which are sorted, and compare their entries in
+ * lock-step:
+ *
+ * A X1 Xn
+ * - - -
+ * |a| |x1| |xn|
+ * |-| |--| ... |--| i = argmin(x1...xn)
+ * | | | | | |
+ * |-| |--| |--|
+ * |.| |. | |. |
+ * . . .
+ * . . .
+ *
+ * at any time there could be 3 cases:
+ *
+ * 1) a < xi;
+ * 2) a > xi;
+ * 3) a = xi.
+ *
+ * Schematic deduction of what every case means, and what to do, follows:
+ *
+ * 1) a < xi -> ∀j a ∉ Xj -> "+a" ∈ D(A,Xj) -> D += "+a"; a↓
+ *
+ * 2) a > xi
+ *
+ * 2.1) ∃j: xj > xi -> "-xi" ∉ D(A,Xj) -> D += ø; ∀ xk=xi xk↓
+ * 2.2) ∀j xj = xi -> xj ∉ A -> "-xj" ∈ D(A,Xj) -> D += "-xi"; ∀j
xj↓
+ *
+ * 3) a = xi
+ *
+ * 3.1) ∃j: xj > xi -> "+a" ∈ D(A,Xj) -> only xk=xi remains to
investigate
+ * 3.2) xj = xi -> investigate δ(a,xj)
+ * |
+ * |
+ * v
+ *
+ * 3.1+3.2) looking at δ(a,xk) ∀k: xk=xi - if all != ø ->
+ *
+ * ⎧δ(a,xk) - if xk=xi
+ * -> D += ⎨
+ * ⎩"+a" - if xk>xi
+ *
+ *
+ * in any case a↓ ∀ xk=xi xk↓
+ */
+struct combine_diff_path *diff_tree_combined_paths(
+ struct combine_diff_path *p, const unsigned char *sha1,
+ const unsigned char **parents_sha1, int nparent,
+ struct strbuf *base, struct diff_options *opt)
+{
+ struct tree_desc t, *tp;
+ void *ttree, **tptree;
+ int *xmin_pathcmpv; /* [i] = pathcmp(tp[i], tp[imin]) */
+ int i;
+
+ tp = xmalloc(nparent * sizeof(tp[0]));
+ tptree = xmalloc(nparent * sizeof(tptree[0]));
+
+ ttree = xfill_tree_descriptor(&t, sha1);
+ for (i = 0; i < nparent; i++)
+ tptree[i] = xfill_tree_descriptor(&tp[i], parents_sha1[i]);
+
+ xmin_pathcmpv = xmalloc(nparent * sizeof(xmin_pathcmpv[0]));
+
+ /* Enable recursion indefinitely */
+ opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE);
+
+ for (;;) {
+ int imin, xmin_eqtotal, cmp, alldifferent;
+ int a_next, tp_next;
+ const unsigned char *entry_sha1;
+ const char *path;
+ unsigned mode;
+
+
+ /* TODO Something similiar to if diff_can_quit_early -> break */
+
+ if (opt->pathspec.nr) {
+ skip_uninteresting(&t, base, opt);
+ for (i = 0; i < nparent; i++)
+ skip_uninteresting(&tp[i], base, opt);
+ }
+
+ /* comparing is finished when all trees are done */
+ if (!t.size) {
+ int done = 1;
+ for (i = 0; i < nparent; ++i)
+ if (tp[i].size) {
+ done = 0;
+ break;
+ }
+ if (done)
+ break;
+ }
+
+
+
+ /* lookup imin = argmin(x1...xn), build X compare vector along
the way */
+ imin = 0; /* argmin(x1...xn) */
+ xmin_eqtotal = 1; /* len([xk: xk=ximin]) */
+ xmin_pathcmpv[0] = 0;
+
+ for (i = 1; i < nparent; ++i) {
+ cmp = tree_desc_pathcmp(&tp[i], &tp[imin]);
+ if (cmp == -1) {
+ imin = i;
+ xmin_eqtotal = 1;
+ xmin_pathcmpv[i] = 0;
+ }
+ else {
+ xmin_pathcmpv[i] = cmp;
+ if (cmp == 0)
+ xmin_eqtotal++;
+ }
+ }
+
+ /* fixup compare vector for entries before imin */
+ for (i = 0; i < imin; ++i)
+ xmin_pathcmpv[i] = +1; /* x[i] > x[imin] */
+
+
+ /* what tree entries will we update, in the end of this step? */
+ a_next = 0; /* a */
+ tp_next = 0; /* tp[k: xk=xmin] */
+
+
+ /* compare a vs x[imin] */
+ cmp = tree_desc_pathcmp(&t, &tp[imin]);
+
+ switch (cmp) {
+ /* a < xi */
+ case -1:
+ /* D += "+a" */
+ p = path_appendnew(p, base, opt, nparent,
+ &t, /*tp=*/NULL, -1, NULL);
+
+ /* a↓ */
+ a_next = 1;
+ break;
+
+
+ /* a > xi */
+ case +1:
+ /* ∀j xj=ximin -> D += "-xi" */
+ if (xmin_eqtotal == nparent)
+ p = path_appendnew(p, base, opt, nparent,
+ /*t=*/NULL, tp, imin, xmin_pathcmpv);
+
+ /* ∀ xk=ximin xk↓ */
+ tp_next = 1;
+ break;
+
+
+ /* a = xi */
+ case 0:
+ /* are either xk > xi or diff(a,xk) != ø ? */
+ alldifferent = 1;
+ entry_sha1 = tree_entry_extract(&t, &path, &mode);
+ for (i = 0; i < nparent; i++) {
+ const unsigned char *sha1_i;
+ const char *path_i;
+ unsigned mode_i;
+
+ /* x[i] > x[imin] */
+ if (xmin_pathcmpv[i])
+ continue;
+
+ /* diff(a,xk) != ø */
+ sha1_i = tree_entry_extract(&tp[i], &path_i,
&mode_i);
+ if (hashcmp(entry_sha1, sha1_i) || (mode !=
mode_i))
+ continue;
+
+ alldifferent = 0;
+ break;
+ }
+
+ /* D += {δ(a,xk) if xk=xi; "+a" if xk > xi} */
+ if (alldifferent)
+ p = path_appendnew(p, base, opt, nparent,
+ &t, tp, imin, xmin_pathcmpv);
+
+ /* a↓, ∀ xk=ximin xk↓ */
+ a_next = 1;
+ tp_next = 1;
+ break;
+ }
+
+ /* perform scheduled tree entries updates: */
+ if (a_next)
+ /* a↓ */
+ update_tree_entry(&t);
+
+ if (tp_next)
+ /* ∀ xk=xi xk↓ */
+ for (i = 0; i < nparent; ++i)
+ if (!xmin_pathcmpv[i])
+ update_tree_entry(&tp[i]);
+ }
+
+ free(xmin_pathcmpv);
+ for (i = nparent-1; i >= 0; i--)
+ free(tptree[i]);
+ free(ttree);
+ free(tptree);
+ free(tp);
+
+ return p;
+}
+
+
+
/*
* Does it look like the resulting diff might be due to a rename?
* - single entry
--
1.9.rc1.181.g641f458
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html