Skip to content

Commit e8da000

Browse files
authored
Merge pull request #90 from mhagger/simplify-to-path
New concept: "simplify to path"
2 parents 7334f53 + eeb518a commit e8da000

File tree

1 file changed

+122
-37
lines changed

1 file changed

+122
-37
lines changed

git-imerge

Lines changed: 122 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,6 @@ class Failure(Exception):
139139

140140
return wrapper
141141

142-
pass
143-
144142

145143
class 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+
617632
def 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+
666728
class 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+
18931962
class 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

Comments
 (0)