@@ -332,6 +332,80 @@ static int everybody_uninteresting(struct commit_list *orig)
332332 return 1 ;
333333}
334334
335+ /*
336+ * A definition of "relevant" commit that we can use to simplify limited graphs
337+ * by eliminating side branches.
338+ *
339+ * A "relevant" commit is one that is !UNINTERESTING (ie we are including it
340+ * in our list), or that is a specified BOTTOM commit. Then after computing
341+ * a limited list, during processing we can generally ignore boundary merges
342+ * coming from outside the graph, (ie from irrelevant parents), and treat
343+ * those merges as if they were single-parent. TREESAME is defined to consider
344+ * only relevant parents, if any. If we are TREESAME to our on-graph parents,
345+ * we don't care if we were !TREESAME to non-graph parents.
346+ *
347+ * Treating bottom commits as relevant ensures that a limited graph's
348+ * connection to the actual bottom commit is not viewed as a side branch, but
349+ * treated as part of the graph. For example:
350+ *
351+ * ....Z...A---X---o---o---B
352+ * . /
353+ * W---Y
354+ *
355+ * When computing "A..B", the A-X connection is at least as important as
356+ * Y-X, despite A being flagged UNINTERESTING.
357+ *
358+ * And when computing --ancestry-path "A..B", the A-X connection is more
359+ * important than Y-X, despite both A and Y being flagged UNINTERESTING.
360+ */
361+ static inline int relevant_commit (struct commit * commit )
362+ {
363+ return (commit -> object .flags & (UNINTERESTING | BOTTOM )) != UNINTERESTING ;
364+ }
365+
366+ /*
367+ * Return a single relevant commit from a parent list. If we are a TREESAME
368+ * commit, and this selects one of our parents, then we can safely simplify to
369+ * that parent.
370+ */
371+ static struct commit * one_relevant_parent (const struct rev_info * revs ,
372+ struct commit_list * orig )
373+ {
374+ struct commit_list * list = orig ;
375+ struct commit * relevant = NULL ;
376+
377+ if (!orig )
378+ return NULL ;
379+
380+ /*
381+ * For 1-parent commits, or if first-parent-only, then return that
382+ * first parent (even if not "relevant" by the above definition).
383+ * TREESAME will have been set purely on that parent.
384+ */
385+ if (revs -> first_parent_only || !orig -> next )
386+ return orig -> item ;
387+
388+ /*
389+ * For multi-parent commits, identify a sole relevant parent, if any.
390+ * If we have only one relevant parent, then TREESAME will be set purely
391+ * with regard to that parent, and we can simplify accordingly.
392+ *
393+ * If we have more than one relevant parent, or no relevant parents
394+ * (and multiple irrelevant ones), then we can't select a parent here
395+ * and return NULL.
396+ */
397+ while (list ) {
398+ struct commit * commit = list -> item ;
399+ list = list -> next ;
400+ if (relevant_commit (commit )) {
401+ if (relevant )
402+ return NULL ;
403+ relevant = commit ;
404+ }
405+ }
406+ return relevant ;
407+ }
408+
335409/*
336410 * The goal is to get REV_TREE_NEW as the result only if the
337411 * diff consists of all '+' (and no other changes), REV_TREE_OLD
@@ -502,27 +576,52 @@ static unsigned update_treesame(struct rev_info *revs, struct commit *commit)
502576 if (commit -> parents && commit -> parents -> next ) {
503577 unsigned n ;
504578 struct treesame_state * st ;
579+ struct commit_list * p ;
580+ unsigned relevant_parents ;
581+ unsigned relevant_change , irrelevant_change ;
505582
506583 st = lookup_decoration (& revs -> treesame , & commit -> object );
507584 if (!st )
508585 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- }
586+ relevant_parents = 0 ;
587+ relevant_change = irrelevant_change = 0 ;
588+ for (p = commit -> parents , n = 0 ; p ; n ++ , p = p -> next ) {
589+ if (relevant_commit (p -> item )) {
590+ relevant_change |= !st -> treesame [n ];
591+ relevant_parents ++ ;
592+ } else
593+ irrelevant_change |= !st -> treesame [n ];
515594 }
595+ if (relevant_parents ? relevant_change : irrelevant_change )
596+ commit -> object .flags &= ~TREESAME ;
597+ else
598+ commit -> object .flags |= TREESAME ;
516599 }
517600
518601 return commit -> object .flags & TREESAME ;
519602}
520603
604+ static inline int limiting_can_increase_treesame (const struct rev_info * revs )
605+ {
606+ /*
607+ * TREESAME is irrelevant unless prune && dense;
608+ * if simplify_history is set, we can't have a mixture of TREESAME and
609+ * !TREESAME INTERESTING parents (and we don't have treesame[]
610+ * decoration anyway);
611+ * if first_parent_only is set, then the TREESAME flag is locked
612+ * against the first parent (and again we lack treesame[] decoration).
613+ */
614+ return revs -> prune && revs -> dense &&
615+ !revs -> simplify_history &&
616+ !revs -> first_parent_only ;
617+ }
618+
521619static void try_to_simplify_commit (struct rev_info * revs , struct commit * commit )
522620{
523621 struct commit_list * * pp , * parent ;
524622 struct treesame_state * ts = NULL ;
525- int tree_changed = 0 , nth_parent ;
623+ int relevant_change = 0 , irrelevant_change = 0 ;
624+ int relevant_parents , nth_parent ;
526625
527626 /*
528627 * If we don't do pruning, everything is interesting
@@ -546,10 +645,12 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
546645 if (!revs -> dense && !commit -> parents -> next )
547646 return ;
548647
549- for (pp = & commit -> parents , nth_parent = 0 ;
648+ for (pp = & commit -> parents , nth_parent = 0 , relevant_parents = 0 ;
550649 (parent = * pp ) != NULL ;
551650 pp = & parent -> next , nth_parent ++ ) {
552651 struct commit * p = parent -> item ;
652+ if (relevant_commit (p ))
653+ relevant_parents ++ ;
553654
554655 if (nth_parent == 1 ) {
555656 /*
@@ -573,7 +674,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
573674 !revs -> simplify_history &&
574675 !(commit -> object .flags & UNINTERESTING )) {
575676 ts = initialise_treesame (revs , commit );
576- if (!tree_changed )
677+ if (!( irrelevant_change || relevant_change ) )
577678 ts -> treesame [0 ] = 1 ;
578679 }
579680 }
@@ -619,14 +720,27 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
619720 /* fallthrough */
620721 case REV_TREE_OLD :
621722 case REV_TREE_DIFFERENT :
622- tree_changed = 1 ;
723+ if (relevant_commit (p ))
724+ relevant_change = 1 ;
725+ else
726+ irrelevant_change = 1 ;
623727 continue ;
624728 }
625729 die ("bad tree compare for commit %s" , sha1_to_hex (commit -> object .sha1 ));
626730 }
627- if (tree_changed )
628- return ;
629- commit -> object .flags |= TREESAME ;
731+
732+ /*
733+ * TREESAME is straightforward for single-parent commits. For merge
734+ * commits, it is most useful to define it so that "irrelevant"
735+ * parents cannot make us !TREESAME - if we have any relevant
736+ * parents, then we only consider TREESAMEness with respect to them,
737+ * allowing irrelevant merges from uninteresting branches to be
738+ * simplified away. Only if we have only irrelevant parents do we
739+ * base TREESAME on them. Note that this logic is replicated in
740+ * update_treesame, which should be kept in sync.
741+ */
742+ if (relevant_parents ? !relevant_change : !irrelevant_change )
743+ commit -> object .flags |= TREESAME ;
630744}
631745
632746static void commit_list_insert_by_date_cached (struct commit * p , struct commit_list * * head ,
@@ -998,6 +1112,18 @@ static int limit_list(struct rev_info *revs)
9981112 free_commit_list (bottom );
9991113 }
10001114
1115+ /*
1116+ * Check if any commits have become TREESAME by some of their parents
1117+ * becoming UNINTERESTING.
1118+ */
1119+ if (limiting_can_increase_treesame (revs ))
1120+ for (list = newlist ; list ; list = list -> next ) {
1121+ struct commit * c = list -> item ;
1122+ if (c -> object .flags & (UNINTERESTING | TREESAME ))
1123+ continue ;
1124+ update_treesame (revs , c );
1125+ }
1126+
10011127 revs -> commits = newlist ;
10021128 return 0 ;
10031129}
@@ -2248,6 +2374,7 @@ static int remove_marked_parents(struct rev_info *revs, struct commit *commit)
22482374static struct commit_list * * simplify_one (struct rev_info * revs , struct commit * commit , struct commit_list * * tail )
22492375{
22502376 struct commit_list * p ;
2377+ struct commit * parent ;
22512378 struct merge_simplify_state * st , * pst ;
22522379 int cnt ;
22532380
@@ -2336,19 +2463,20 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
23362463 /*
23372464 * A commit simplifies to itself if it is a root, if it is
23382465 * UNINTERESTING, if it touches the given paths, or if it is a
2339- * merge and its parents simplify to more than one commit
2466+ * merge and its parents don't simplify to one relevant commit
23402467 * (the first two cases are already handled at the beginning of
23412468 * this function).
23422469 *
2343- * Otherwise, it simplifies to what its sole parent simplifies to.
2470+ * Otherwise, it simplifies to what its sole relevant parent
2471+ * simplifies to.
23442472 */
23452473 if (!cnt ||
23462474 (commit -> object .flags & UNINTERESTING ) ||
23472475 !(commit -> object .flags & TREESAME ) ||
2348- (1 < cnt ) )
2476+ (parent = one_relevant_parent ( revs , commit -> parents )) == NULL )
23492477 st -> simplified = commit ;
23502478 else {
2351- pst = locate_simplify_state (revs , commit -> parents -> item );
2479+ pst = locate_simplify_state (revs , parent );
23522480 st -> simplified = pst -> simplified ;
23532481 }
23542482 return tail ;
@@ -2445,7 +2573,8 @@ int prepare_revision_walk(struct rev_info *revs)
24452573 free (list );
24462574
24472575 /* Signal whether we need per-parent treesame decoration */
2448- if (revs -> simplify_merges )
2576+ if (revs -> simplify_merges ||
2577+ (revs -> limited && limiting_can_increase_treesame (revs )))
24492578 revs -> treesame .name = "treesame" ;
24502579
24512580 if (revs -> no_walk != REVISION_WALK_NO_WALK_UNSORTED )
@@ -2479,15 +2608,15 @@ static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp
24792608 if (!revs -> limited )
24802609 if (add_parents_to_list (revs , p , & revs -> commits , & cache ) < 0 )
24812610 return rewrite_one_error ;
2482- if (p -> parents && p -> parents -> next )
2483- return rewrite_one_ok ;
24842611 if (p -> object .flags & UNINTERESTING )
24852612 return rewrite_one_ok ;
24862613 if (!(p -> object .flags & TREESAME ))
24872614 return rewrite_one_ok ;
24882615 if (!p -> parents )
24892616 return rewrite_one_noparents ;
2490- * pp = p -> parents -> item ;
2617+ if ((p = one_relevant_parent (revs , p -> parents )) == NULL )
2618+ return rewrite_one_ok ;
2619+ * pp = p ;
24912620 }
24922621}
24932622
0 commit comments