Skip to content
This repository was archived by the owner on Nov 9, 2017. It is now read-only.

Commit d0af663

Browse files
kjbracey2gitster
authored andcommitted
revision.c: Make --full-history consider more merges
History simplification previously always treated merges as TREESAME if they were TREESAME to any parent. While this was consistent with the default behaviour, this could be extremely unhelpful when searching detailed history, and could not be overridden. For example, if a merge had ignored a change, as if by "-s ours", then: git log -m -p --full-history -Schange file would successfully locate "change"'s addition but would not locate the merge that resolved against it. Futher, simplify_merges could drop the actual parent that a commit was TREESAME to, leaving it as a normal commit marked TREESAME that isn't actually TREESAME to its remaining parent. Now redefine a commit's TREESAME flag to be true only if a commit is TREESAME to _all_ of its parents. This doesn't affect either the default simplify_history behaviour (because partially TREESAME merges are turned into normal commits), or full-history with parent rewriting (because all merges are output). But it does affect other modes. The clearest difference is that --full-history will show more merges - sufficient to ensure that -m -p --full-history log searches can really explain every change to the file, including those changes' ultimate fate in merges. Also modify simplify_merges to recalculate TREESAME after removing a parent. This is achieved by storing per-parent TREESAME flags on the initial scan, so the combined flag can be easily recomputed. This fixes some t6111 failures, but creates a couple of new ones - we are now showing some merges that don't need to be shown. Signed-off-by: Kevin Bracey <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent e32db66 commit d0af663

File tree

5 files changed

+229
-47
lines changed

5 files changed

+229
-47
lines changed

Documentation/rev-list-options.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -409,10 +409,10 @@ parent lines.
409409
the example, we get
410410
+
411411
-----------------------------------------------------------------------
412-
I A B N D O
412+
I A B N D O P
413413
-----------------------------------------------------------------------
414414
+
415-
`P` and `M` were excluded because they are TREESAME to a parent. `E`,
415+
`M` was excluded because it is TREESAME to both parents. `E`,
416416
`C` and `B` were all walked, but only `B` was !TREESAME, so the others
417417
do not appear.
418418
+
@@ -440,7 +440,7 @@ themselves. This results in
440440
Compare to '\--full-history' without rewriting above. Note that `E`
441441
was pruned away because it is TREESAME, but the parent list of P was
442442
rewritten to contain `E`'s parent `I`. The same happened for `C` and
443-
`N`. Note also that `P` was included despite being TREESAME.
443+
`N`.
444444

445445
In addition to the above settings, you can change whether TREESAME
446446
affects inclusion:

revision.c

Lines changed: 211 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -429,10 +429,100 @@ static int rev_same_tree_as_empty(struct rev_info *revs, struct commit *commit)
429429
return retval >= 0 && (tree_difference == REV_TREE_SAME);
430430
}
431431

432+
struct treesame_state {
433+
unsigned int nparents;
434+
unsigned char treesame[FLEX_ARRAY];
435+
};
436+
437+
static struct treesame_state *initialise_treesame(struct rev_info *revs, struct commit *commit)
438+
{
439+
unsigned n = commit_list_count(commit->parents);
440+
struct treesame_state *st = xcalloc(1, sizeof(*st) + n);
441+
st->nparents = n;
442+
add_decoration(&revs->treesame, &commit->object, st);
443+
return st;
444+
}
445+
446+
/*
447+
* Must be called immediately after removing the nth_parent from a commit's
448+
* parent list, if we are maintaining the per-parent treesame[] decoration.
449+
* This does not recalculate the master TREESAME flag - update_treesame()
450+
* should be called to update it after a sequence of treesame[] modifications
451+
* that may have affected it.
452+
*/
453+
static int compact_treesame(struct rev_info *revs, struct commit *commit, unsigned nth_parent)
454+
{
455+
struct treesame_state *st;
456+
int old_same;
457+
458+
if (!commit->parents) {
459+
/*
460+
* Have just removed the only parent from a non-merge.
461+
* Different handling, as we lack decoration.
462+
*/
463+
if (nth_parent != 0)
464+
die("compact_treesame %u", nth_parent);
465+
old_same = !!(commit->object.flags & TREESAME);
466+
if (rev_same_tree_as_empty(revs, commit))
467+
commit->object.flags |= TREESAME;
468+
else
469+
commit->object.flags &= ~TREESAME;
470+
return old_same;
471+
}
472+
473+
st = lookup_decoration(&revs->treesame, &commit->object);
474+
if (!st || nth_parent >= st->nparents)
475+
die("compact_treesame %u", nth_parent);
476+
477+
old_same = st->treesame[nth_parent];
478+
memmove(st->treesame + nth_parent,
479+
st->treesame + nth_parent + 1,
480+
st->nparents - nth_parent - 1);
481+
482+
/*
483+
* If we've just become a non-merge commit, update TREESAME
484+
* immediately, and remove the no-longer-needed decoration.
485+
* If still a merge, defer update until update_treesame().
486+
*/
487+
if (--st->nparents == 1) {
488+
if (commit->parents->next)
489+
die("compact_treesame parents mismatch");
490+
if (st->treesame[0] && revs->dense)
491+
commit->object.flags |= TREESAME;
492+
else
493+
commit->object.flags &= ~TREESAME;
494+
free(add_decoration(&revs->treesame, &commit->object, NULL));
495+
}
496+
497+
return old_same;
498+
}
499+
500+
static unsigned update_treesame(struct rev_info *revs, struct commit *commit)
501+
{
502+
if (commit->parents && commit->parents->next) {
503+
unsigned n;
504+
struct treesame_state *st;
505+
506+
st = lookup_decoration(&revs->treesame, &commit->object);
507+
if (!st)
508+
die("update_treesame %s", sha1_to_hex(commit->object.sha1));
509+
commit->object.flags |= TREESAME;
510+
for (n = 0; n < st->nparents; n++) {
511+
if (!st->treesame[n]) {
512+
commit->object.flags &= ~TREESAME;
513+
break;
514+
}
515+
}
516+
}
517+
518+
return commit->object.flags & TREESAME;
519+
}
520+
432521
static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
433522
{
434523
struct commit_list **pp, *parent;
435-
int tree_changed = 0, tree_same = 0, nth_parent = 0;
524+
struct treesame_state *ts = NULL;
525+
int tree_changed = 0, nth_parent;
436526

437527
/*
438528
* If we don't do pruning, everything is interesting
@@ -456,33 +546,52 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
456546
if (!revs->dense && !commit->parents->next)
457547
return;
458548

459-
pp = &commit->parents;
460-
while ((parent = *pp) != NULL) {
549+
for (pp = &commit->parents, nth_parent = 0;
550+
(parent = *pp) != NULL;
551+
pp = &parent->next, nth_parent++) {
461552
struct commit *p = parent->item;
462553

463-
/*
464-
* Do not compare with later parents when we care only about
465-
* the first parent chain, in order to avoid derailing the
466-
* traversal to follow a side branch that brought everything
467-
* in the path we are limited to by the pathspec.
468-
*/
469-
if (revs->first_parent_only && nth_parent++)
470-
break;
554+
if (nth_parent == 1) {
555+
/*
556+
* This our second loop iteration - so we now know
557+
* we're dealing with a merge.
558+
*
559+
* Do not compare with later parents when we care only about
560+
* the first parent chain, in order to avoid derailing the
561+
* traversal to follow a side branch that brought everything
562+
* in the path we are limited to by the pathspec.
563+
*/
564+
if (revs->first_parent_only)
565+
break;
566+
/*
567+
* If this will remain a potentially-simplifiable
568+
* merge, remember per-parent treesame if needed.
569+
* Initialise the array with the comparison from our
570+
* first iteration.
571+
*/
572+
if (revs->treesame.name &&
573+
!revs->simplify_history &&
574+
!(commit->object.flags & UNINTERESTING)) {
575+
ts = initialise_treesame(revs, commit);
576+
if (!tree_changed)
577+
ts->treesame[0] = 1;
578+
}
579+
}
471580
if (parse_commit(p) < 0)
472581
die("cannot simplify commit %s (because of %s)",
473582
sha1_to_hex(commit->object.sha1),
474583
sha1_to_hex(p->object.sha1));
475584
switch (rev_compare_tree(revs, p, commit)) {
476585
case REV_TREE_SAME:
477-
tree_same = 1;
478586
if (!revs->simplify_history || (p->object.flags & UNINTERESTING)) {
479587
/* Even if a merge with an uninteresting
480588
* side branch brought the entire change
481589
* we are interested in, we do not want
482590
* to lose the other branches of this
483591
* merge, so we just keep going.
484592
*/
485-
pp = &parent->next;
593+
if (ts)
594+
ts->treesame[nth_parent] = 1;
486595
continue;
487596
}
488597
parent->next = NULL;
@@ -511,12 +620,11 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
511620
case REV_TREE_OLD:
512621
case REV_TREE_DIFFERENT:
513622
tree_changed = 1;
514-
pp = &parent->next;
515623
continue;
516624
}
517625
die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
518626
}
519-
if (tree_changed && !tree_same)
627+
if (tree_changed)
520628
return;
521629
commit->object.flags |= TREESAME;
522630
}
@@ -1947,28 +2055,32 @@ static void add_child(struct rev_info *revs, struct commit *parent, struct commi
19472055
l->next = add_decoration(&revs->children, &parent->object, l);
19482056
}
19492057

1950-
static int remove_duplicate_parents(struct commit *commit)
2058+
static int remove_duplicate_parents(struct rev_info *revs, struct commit *commit)
19512059
{
2060+
struct treesame_state *ts = lookup_decoration(&revs->treesame, &commit->object);
19522061
struct commit_list **pp, *p;
19532062
int surviving_parents;
19542063

19552064
/* Examine existing parents while marking ones we have seen... */
19562065
pp = &commit->parents;
2066+
surviving_parents = 0;
19572067
while ((p = *pp) != NULL) {
19582068
struct commit *parent = p->item;
19592069
if (parent->object.flags & TMP_MARK) {
19602070
*pp = p->next;
2071+
if (ts)
2072+
compact_treesame(revs, commit, surviving_parents);
19612073
continue;
19622074
}
19632075
parent->object.flags |= TMP_MARK;
2076+
surviving_parents++;
19642077
pp = &p->next;
19652078
}
1966-
/* count them while clearing the temporary mark */
1967-
surviving_parents = 0;
2079+
/* clear the temporary mark */
19682080
for (p = commit->parents; p; p = p->next) {
19692081
p->item->object.flags &= ~TMP_MARK;
1970-
surviving_parents++;
19712082
}
2083+
/* no update_treesame() - removing duplicates can't affect TREESAME */
19722084
return surviving_parents;
19732085
}
19742086

@@ -1988,6 +2100,70 @@ static struct merge_simplify_state *locate_simplify_state(struct rev_info *revs,
19882100
return st;
19892101
}
19902102

2103+
static int mark_redundant_parents(struct rev_info *revs, struct commit *commit)
2104+
{
2105+
struct commit_list *h = reduce_heads(commit->parents);
2106+
int i = 0, marked = 0;
2107+
struct commit_list *po, *pn;
2108+
2109+
/* Want these for sanity-checking only */
2110+
int orig_cnt = commit_list_count(commit->parents);
2111+
int cnt = commit_list_count(h);
2112+
2113+
/*
2114+
* Not ready to remove items yet, just mark them for now, based
2115+
* on the output of reduce_heads(). reduce_heads outputs the reduced
2116+
* set in its original order, so this isn't too hard.
2117+
*/
2118+
po = commit->parents;
2119+
pn = h;
2120+
while (po) {
2121+
if (pn && po->item == pn->item) {
2122+
pn = pn->next;
2123+
i++;
2124+
} else {
2125+
po->item->object.flags |= TMP_MARK;
2126+
marked++;
2127+
}
2128+
po=po->next;
2129+
}
2130+
2131+
if (i != cnt || cnt+marked != orig_cnt)
2132+
die("mark_redundant_parents %d %d %d %d", orig_cnt, cnt, i, marked);
2133+
2134+
free_commit_list(h);
2135+
2136+
return marked;
2137+
}
2138+
2139+
static int remove_marked_parents(struct rev_info *revs, struct commit *commit)
2140+
{
2141+
struct commit_list **pp, *p;
2142+
int nth_parent, removed = 0;
2143+
2144+
pp = &commit->parents;
2145+
nth_parent = 0;
2146+
while ((p = *pp) != NULL) {
2147+
struct commit *parent = p->item;
2148+
if (parent->object.flags & TMP_MARK) {
2149+
parent->object.flags &= ~TMP_MARK;
2150+
*pp = p->next;
2151+
free(p);
2152+
removed++;
2153+
compact_treesame(revs, commit, nth_parent);
2154+
continue;
2155+
}
2156+
pp = &p->next;
2157+
nth_parent++;
2158+
}
2159+
2160+
/* Removing parents can only increase TREESAMEness */
2161+
if (removed && !(commit->object.flags & TREESAME))
2162+
update_treesame(revs, commit);
2163+
2164+
return nth_parent;
2165+
}
2166+
19912167
static struct commit_list **simplify_one(struct rev_info *revs, struct commit *commit, struct commit_list **tail)
19922168
{
19932169
struct commit_list *p;
@@ -2032,7 +2208,9 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
20322208
}
20332209

20342210
/*
2035-
* Rewrite our list of parents.
2211+
* Rewrite our list of parents. Note that this cannot
2212+
* affect our TREESAME flags in any way - a commit is
2213+
* always TREESAME to its simplification.
20362214
*/
20372215
for (p = commit->parents; p; p = p->next) {
20382216
pst = locate_simplify_state(revs, p->item);
@@ -2044,31 +2222,30 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
20442222
if (revs->first_parent_only)
20452223
cnt = 1;
20462224
else
2047-
cnt = remove_duplicate_parents(commit);
2225+
cnt = remove_duplicate_parents(revs, commit);
20482226

20492227
/*
20502228
* It is possible that we are a merge and one side branch
20512229
* does not have any commit that touches the given paths;
2052-
* in such a case, the immediate parents will be rewritten
2053-
* to different commits.
2230+
* in such a case, the immediate parent from that branch
2231+
* will be rewritten to be the merge base.
20542232
*
20552233
* o----X X: the commit we are looking at;
20562234
* / / o: a commit that touches the paths;
20572235
* ---o----'
20582236
*
2059-
* Further reduce the parents by removing redundant parents.
2237+
* Detect and simplify this case.
20602238
*/
20612239
if (1 < cnt) {
2062-
struct commit_list *h = reduce_heads(commit->parents);
2063-
cnt = commit_list_count(h);
2064-
free_commit_list(commit->parents);
2065-
commit->parents = h;
2240+
int marked = mark_redundant_parents(revs, commit);
2241+
if (marked)
2242+
cnt = remove_marked_parents(revs, commit);
20662243
}
20672244

20682245
/*
20692246
* A commit simplifies to itself if it is a root, if it is
20702247
* UNINTERESTING, if it touches the given paths, or if it is a
2071-
* merge and its parents simplifies to more than one commits
2248+
* merge and its parents simplify to more than one commit
20722249
* (the first two cases are already handled at the beginning of
20732250
* this function).
20742251
*
@@ -2176,6 +2353,10 @@ int prepare_revision_walk(struct rev_info *revs)
21762353
if (!revs->leak_pending)
21772354
free(list);
21782355

2356+
/* Signal whether we need per-parent treesame decoration */
2357+
if (revs->simplify_merges)
2358+
revs->treesame.name = "treesame";
2359+
21792360
if (revs->no_walk != REVISION_WALK_NO_WALK_UNSORTED)
21802361
commit_list_sort_by_date(&revs->commits);
21812362
if (revs->no_walk)
@@ -2235,7 +2416,7 @@ static int rewrite_parents(struct rev_info *revs, struct commit *commit)
22352416
}
22362417
pp = &parent->next;
22372418
}
2238-
remove_duplicate_parents(commit);
2419+
remove_duplicate_parents(revs, commit);
22392420
return 0;
22402421
}
22412422

revision.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ struct rev_info {
168168
struct reflog_walk_info *reflog_info;
169169
struct decoration children;
170170
struct decoration merge_simplification;
171+
struct decoration treesame;
171172

172173
/* notes-specific options: which refs to show */
173174
struct display_notes_opt notes_opt;

t/t6019-rev-list-ancestry-path.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ test_expect_success 'rev-list G..M -- G.t' '
100100
test_cmp expect actual
101101
'
102102

103-
test_expect_failure 'rev-list --ancestry-path G..M -- G.t' '
103+
test_expect_success 'rev-list --ancestry-path G..M -- G.t' '
104104
for c in H J L; do echo $c; done >expect &&
105105
git rev-list --ancestry-path --format=%s G..M -- G.t |
106106
sed -e "/^commit /d" |

0 commit comments

Comments
 (0)