@@ -139,8 +139,6 @@ class Failure(Exception):
139139
140140 return wrapper
141141
142- pass
143-
144142
145143class AnsiColor :
146144 BLACK = '\033 [0;30m'
@@ -614,6 +612,23 @@ class TemporaryHead(object):
614612 return False
615613
616614
615+ def is_ff (refname , commit ):
616+ """Would updating refname to commit be a fast-forward update?
617+
618+ Return True iff refname is not currently set or it points to an
619+ ancestor of commit.
620+
621+ """
622+
623+ try :
624+ ref_oldval = get_commit_sha1 (refname )
625+ except ValueError :
626+ # refname doesn't already exist; no problem.
627+ return True
628+ else :
629+ return MergeState ._is_ancestor (ref_oldval , commit )
630+
631+
617632def reparent (commit , parent_sha1s , msg = None ):
618633 """Create a new commit object like commit, but with the specified parents.
619634
@@ -663,6 +678,53 @@ def reparent(commit, parent_sha1s, msg=None):
663678 return out .strip ()
664679
665680
681+ def create_commit_chain (base , path ):
682+ """Point refname at the chain of commits indicated by path.
683+
684+ path is a list [(commit, metadata), ...]. Create a series of
685+ commits corresponding to the entries in path. Each commit's tree
686+ is taken from the corresponding old commit, and each commit's
687+ metadata is taken from the corresponding metadata commit. Use base
688+ as the parent of the first commit, or make the first commit a root
689+ commit if base is None. Reuse existing commits from the list
690+ whenever possible.
691+
692+ Return a commit object corresponding to the last commit in the
693+ chain.
694+
695+ """
696+
697+ reusing = True
698+ if base is None :
699+ if not path :
700+ raise ValueError ('neither base nor path specified' )
701+ parents = []
702+ else :
703+ parents = [base ]
704+
705+ for (commit , metadata ) in path :
706+ if reusing :
707+ if commit == metadata and get_commit_parents (commit ) == parents :
708+ # We can reuse this commit, too.
709+ parents = [commit ]
710+ continue
711+ else :
712+ reusing = False
713+
714+ # Create a commit, copying the old log message and author info
715+ # from the metadata commit:
716+ tree = get_tree (commit )
717+ new_commit = commit_tree (
718+ tree , parents ,
719+ msg = get_log_message (metadata ),
720+ metadata = get_author_info (metadata ),
721+ )
722+ parents = [new_commit ]
723+
724+ [commit ] = parents
725+ return commit
726+
727+
666728class AutomaticMergeFailed (Exception ):
667729 def __init__ (self , commit1 , commit2 ):
668730 Exception .__init__ (
@@ -1890,6 +1952,13 @@ class SubBlock(Block):
18901952 )
18911953
18921954
1955+ class MissingMergeFailure (Failure ):
1956+ def __init__ (self , i1 , i2 ):
1957+ Failure .__init__ (self , 'Merge %d-%d is not yet done' % (i1 , i2 ))
1958+ self .i1 = i1
1959+ self .i2 = i2
1960+
1961+
18931962class MergeState (Block ):
18941963 SOURCE_TABLE = {
18951964 'auto' : MergeRecord .SAVED_AUTO ,
@@ -2447,46 +2516,62 @@ class MergeState(Block):
24472516
24482517 self ._set_refname (refname , commit , force = force )
24492518
2450- def simplify_to_rebase (self , refname , force = False ):
2451- i1 = self .len1 - 1
2452- for i2 in range (1 , self .len2 ):
2453- if not (i1 , i2 ) in self :
2454- raise Failure (
2455- 'Cannot simplify to rebase because merge %d-%d is not yet done'
2456- % (i1 , i2 )
2457- )
2519+ def _simplify_to_path (self , refname , base , path , force = False ):
2520+ """Simplify based on path and set refname to the result.
24582521
2459- if not force :
2460- # A rebase simplification is allowed to discard history,
2461- # as long as the *pre-simplification* apex commit is a
2462- # descendant of the branch to be moved.
2463- try :
2464- ref_oldval = get_commit_sha1 (refname )
2465- except ValueError :
2466- # refname doesn't already exist; no problem.
2467- pass
2468- else :
2469- commit = self [- 1 , - 1 ].sha1
2470- if not MergeState ._is_ancestor (ref_oldval , commit ):
2471- raise Failure (
2472- '%s is not an ancestor of %s; use --force if you are sure'
2473- % (commit , refname ,)
2474- )
2522+ The base and path arguments are defined similarly to
2523+ create_commit_chain(), except that instead of SHA-1s they
2524+ represent commits via (i1, i2) tuples.
24752525
2476- commit = self [i1 , 0 ].sha1
2477- for i2 in range (1 , self .len2 ):
2478- orig = self [0 , i2 ].sha1
2479- tree = get_tree (self [i1 , i2 ].sha1 )
2480- authordata = get_author_info (orig )
2526+ """
2527+
2528+ base_sha1 = self [base ].sha1
2529+ path_sha1 = []
2530+ for (commit , metadata ) in path :
2531+ commit_record = self [commit ]
2532+ if not commit_record .is_known ():
2533+ raise MissingMergeFailure (* commit )
2534+ metadata_record = self [metadata ]
2535+ if not metadata_record .is_known ():
2536+ raise MissingMergeFailure (* metadata_record )
2537+ path_sha1 .append ((commit_record .sha1 , metadata_record .sha1 ))
2538+
2539+ # A path simplification is allowed to discard history, as long
2540+ # as the *pre-simplification* apex commit is a descendant of
2541+ # the branch to be moved.
2542+ if path :
2543+ apex = path_sha1 [- 1 ][0 ]
2544+ else :
2545+ apex = base_sha1
24812546
2482- # Create a commit, copying the old log message and author info:
2483- commit = commit_tree (
2484- tree , [commit ], msg = get_log_message (orig ), metadata = authordata ,
2547+ if not force and not is_ff (refname , apex ):
2548+ raise Failure (
2549+ '%s cannot be updated to %s without discarding history.\n '
2550+ 'Use --force if you are sure, or choose a different reference'
2551+ % (refname , apex ,)
24852552 )
24862553
2487- # We checked above that the update is OK, so here we can set
2488- # force=True:
2489- self ._set_refname (refname , commit , force = True )
2554+ # The update is OK, so here we can set force=True:
2555+ self ._set_refname (
2556+ refname ,
2557+ create_commit_chain (base_sha1 , path_sha1 ),
2558+ force = True ,
2559+ )
2560+
2561+ def simplify_to_rebase (self , refname , force = False ):
2562+ i1 = self .len1 - 1
2563+ path = [
2564+ ((i1 , i2 ), (0 , i2 ))
2565+ for i2 in range (1 , self .len2 )
2566+ ]
2567+
2568+ try :
2569+ self ._simplify_to_path (refname , (i1 , 0 ), path , force = force )
2570+ except MissingMergeFailure as e :
2571+ raise Failure (
2572+ 'Cannot simplify to rebase because merge %d-%d is not yet done'
2573+ % (e .i1 , e .i2 )
2574+ )
24902575
24912576 def simplify_to_merge (self , refname , force = False ):
24922577 if not (- 1 , - 1 ) in self :
0 commit comments