3 # Copyright 2012-2013 Michael Haggerty <mhagger@alum.mit.edu>
5 # This file is part of git-imerge.
7 # git-imerge is free software: you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License as
9 # published by the Free Software Foundation, either version 2 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 # General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see
19 # <http://www.gnu.org/licenses/>.
21 r
"""Git incremental merge
23 Perform the merge between two branches incrementally. If conflicts
24 are encountered, figure out exactly which pairs of commits conflict,
25 and present the user with one pairwise conflict at a time for
28 Multiple incremental merges can be in progress at the same time. Each
29 incremental merge has a name, and its progress is recorded in the Git
30 repository as references under 'refs/imerge/NAME'.
32 An incremental merge can be interrupted and resumed arbitrarily, or
33 even pushed to a server to allow somebody else to work on it.
38 To start an incremental merge or rebase, use one of the following
41 git-imerge merge BRANCH # analogous to "git merge BRANCH"
42 git-imerge rebase BRANCH # analogous to "git rebase BRANCH"
43 git-imerge start --name=NAME --goal=GOAL --first-parent BRANCH
45 Then the tool will present conflicts to you one at a time, similar to
46 "git rebase --incremental". Resolve each conflict, and then
51 You can view your progress at any time with
55 When you have resolved all of the conflicts, simplify and record the
60 To get more help about any git-imerge subcommand, type
62 git-imerge SUBCOMMAND --help
66 from __future__
import absolute_import
67 from __future__
import division
68 from __future__
import print_function
69 from __future__
import unicode_literals
75 from subprocess
import CalledProcessError
76 from subprocess
import check_call
80 from io
import StringIO
85 PREFERRED_ENCODING
= locale
.getpreferredencoding()
88 # Define check_output() for ourselves, including decoding of the
89 # output into PREFERRED_ENCODING:
90 def check_output(*popenargs
, **kwargs
):
91 if 'stdout' in kwargs
:
92 raise ValueError('stdout argument not allowed, it will be overridden.')
93 process
= subprocess
.Popen(stdout
=subprocess
.PIPE
, *popenargs
, **kwargs
)
94 output
= communicate(process
)[0]
95 retcode
= process
.poll()
97 cmd
= kwargs
.get("args")
100 # We don't store output in the CalledProcessError because
101 # the "output" keyword parameter was not supported in
103 raise CalledProcessError(retcode
, cmd
)
107 STATE_VERSION
= (1, 3, 0)
113 'rebase-with-history',
117 DEFAULT_GOAL
= 'merge'
120 class Failure(Exception):
121 """An exception that indicates a normal failure of the script.
123 Failures are reported at top level via sys.exit(str(e)) rather
124 than via a Python stack dump."""
128 """Wrap a function inside a try...except that catches this error.
130 If the exception is thrown, call sys.exit(). This function
131 can be used as a decorator."""
134 def wrapper(*args
, **kw
):
136 return f(*args
, **kw
)
149 YELLOW
= '\033[0;33m'
151 MAGENTA
= '\033[0;35m'
153 B_GRAY
= '\033[0;37m'
154 D_GRAY
= '\033[1;30m'
156 B_GREEN
= '\033[1;32m'
157 B_YELLOW
= '\033[1;33m'
158 B_BLUE
= '\033[1;34m'
159 B_MAGENTA
= '\033[1;35m'
160 B_CYAN
= '\033[1;36m'
185 def iter_neighbors(iterable
):
186 """For an iterable (x0, x1, x2, ...) generate [(x0,x1), (x1,x2), ...]."""
192 except StopIteration:
200 def find_first_false(f
, lo
, hi
):
201 """Return the smallest i in lo <= i < hi for which f(i) returns False using bisection.
203 If there is no such i, return hi.
207 # Loop invariant: f(i) returns True for i < lo; f(i) returns False
220 def call_silently(cmd
):
222 NULL
= open(os
.devnull
, 'w')
223 except (IOError, AttributeError):
224 NULL
= subprocess
.PIPE
226 p
= subprocess
.Popen(cmd
, stdout
=NULL
, stderr
=NULL
)
230 raise CalledProcessError(retcode
, cmd
)
233 def communicate(process
, input=None):
234 """Return decoded output from process."""
235 if input is not None:
236 input = input.encode(PREFERRED_ENCODING
)
238 output
, error
= process
.communicate(input)
240 output
= None if output
is None else output
.decode(PREFERRED_ENCODING
)
241 error
= None if error
is None else error
.decode(PREFERRED_ENCODING
)
243 return (output
, error
)
246 if sys
.hexversion
< 0x03000000:
247 # In Python 2.x, os.environ keys and values must be byte
250 """Encode unicode keys or values for use in os.environ."""
252 return s
.encode(PREFERRED_ENCODING
)
255 # In Python 3.x, os.environ keys and values must be unicode
258 """Use unicode keys or values unchanged in os.environ."""
263 def get_default_edit():
264 """Should '--edit' be used when committing intermediate user merges?
266 When 'git imerge continue' or 'git imerge record' finds a user
267 merge that can be committed, should it (by default) ask the user
268 to edit the commit message? This behavior can be configured via
269 'imerge.editmergemessages'. If it is not configured, return False.
271 Please note that this function is only used to choose the default
272 value. It can be overridden on the command line using '--edit' or
278 return {'true' : True, 'false' : False}[
280 ['git', 'config', '--bool', 'imerge.editmergemessages']
283 except CalledProcessError
:
287 class UncleanWorkTreeError(Failure
):
291 def require_clean_work_tree(action
):
292 """Verify that the current tree is clean.
294 The code is a Python translation of the git-sh-setup(1) function
297 process
= subprocess
.Popen(
298 ['git', 'rev-parse', '--verify', 'HEAD'],
299 stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
301 err
= communicate(process
)[1]
302 retcode
= process
.poll()
304 raise UncleanWorkTreeError(err
.rstrip())
306 process
= subprocess
.Popen(
307 ['git', 'update-index', '-q', '--ignore-submodules', '--refresh'],
308 stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
310 out
, err
= communicate(process
)
311 retcode
= process
.poll()
313 raise UncleanWorkTreeError(err
.rstrip() or out
.rstrip())
317 check_call(['git', 'diff-files', '--quiet', '--ignore-submodules'])
318 except CalledProcessError
:
319 error
.append('Cannot %s: You have unstaged changes.' % (action
,))
323 'git', 'diff-index', '--cached', '--quiet',
324 '--ignore-submodules', 'HEAD', '--',
326 except CalledProcessError
:
328 error
.append('Cannot %s: Your index contains uncommitted changes.' % (action
,))
330 error
.append('Additionally, your index contains uncommitted changes.')
333 raise UncleanWorkTreeError('\n'.join(error
))
336 class InvalidBranchNameError(Failure
):
340 def check_branch_name_format(name
):
341 """Check that name is a valid branch name."""
345 ['git', 'check-ref-format', 'refs/heads/%s' % (name
,)]
347 except CalledProcessError
:
348 raise InvalidBranchNameError('Name %r is not a valid branch name!' % (name
,))
352 return check_output(['git', 'rev-parse', '--verify', '--quiet', arg
]).strip()
358 for l
in check_output(['git', 'rev-list'] + list(args
),).splitlines()
363 """Return the type of a git object ('commit', 'tree', 'blob', or 'tag')."""
365 return check_output(['git', 'cat-file', '-t', arg
]).strip()
369 return rev_parse('%s^{tree}' % (arg
,))
372 BRANCH_PREFIX
= 'refs/heads/'
375 def checkout(refname
, quiet
=False):
376 cmd
= ['git', 'checkout']
379 if refname
.startswith(BRANCH_PREFIX
):
380 target
= refname
[len(BRANCH_PREFIX
):]
382 target
= '%s^0' % (refname
,)
387 def get_commit_sha1(arg
):
388 """Convert arg into a SHA1 and verify that it refers to a commit.
390 If not, raise ValueError."""
393 return rev_parse('%s^{commit}' % (arg
,))
394 except CalledProcessError
:
395 raise ValueError('%r does not refer to a valid git commit' % (arg
,))
398 def get_commit_parents(commit
):
399 """Return a list containing the parents of commit."""
402 ['git', '--no-pager', 'log', '--no-walk', '--pretty=format:%P', commit
]
406 def get_log_message(commit
):
407 contents
= check_output([
408 'git', 'cat-file', 'commit', commit
,
410 contents
= contents
[contents
.index('\n') + 1:]
411 if contents
and contents
[-1][-1:] != '\n':
412 contents
.append('\n')
413 return ''.join(contents
)
416 def get_author_info(commit
):
417 """Return environment settings to set author metadata.
419 Return a map {str : str}."""
421 # We use newlines as separators here because msysgit has problems
422 # with NUL characters; see
424 # https://github.com/mhagger/git-imerge/pull/71
426 'git', '--no-pager', 'log', '-n1',
427 '--format=%an%n%ae%n%ai', commit
428 ]).strip().splitlines()
431 'GIT_AUTHOR_NAME': env_encode(a
[0]),
432 'GIT_AUTHOR_EMAIL': env_encode(a
[1]),
433 'GIT_AUTHOR_DATE': env_encode(a
[2]),
437 def commit_tree(tree
, parents
, msg
, metadata
=None):
438 """Create a commit containing the specified tree.
440 metadata can be author or committer information to be added to the
441 environment, as str objects; e.g., {'GIT_AUTHOR_NAME' : 'me'}.
443 Return the SHA-1 of the new commit object."""
445 cmd
= ['git', 'commit-tree', tree
]
446 for parent
in parents
:
447 cmd
+= ['-p', parent
]
449 if metadata
is not None:
450 env
= os
.environ
.copy()
455 process
= subprocess
.Popen(
456 cmd
, env
=env
, stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
,
458 out
= communicate(process
, input=msg
)[0]
459 retcode
= process
.poll()
462 # We don't store the output in the CalledProcessError because
463 # the "output" keyword parameter was not supported in Python
465 raise CalledProcessError(retcode
, cmd
)
470 class NothingToDoError(Failure
):
471 def __init__(self
, src_tip
, dst_tip
):
474 'There are no commits on "%s" that are not already in "%s"'
475 % (src_tip
, dst_tip
),
479 def linear_ancestry(commit1
, commit2
):
480 """Compute the linear ancestry between commit1 and commit2.
482 Compute what *should* be the output of
484 git --first-parent --ancestry-path commit1..commit2
486 if it worked correctly; i.e., the list of commits in the
487 first-parent chain going backwards from commit2 that are also
488 descendents of commit1. Return a list of SHA-1s in 'chronological'
491 ancestry_path
= set(rev_list('--ancestry-path', '%s..%s' % (commit1
, commit2
)))
494 for sha1
in rev_list('--first-parent', '%s..%s' % (commit1
, commit2
))
495 if sha1
in ancestry_path
501 def compute_best_merge_base(tip1
, tip2
):
503 merge_bases
= check_output(['git', 'merge-base', '--all', tip1
, tip2
]).splitlines()
504 except CalledProcessError
:
505 raise Failure('Cannot compute merge base for %r and %r' % (tip1
, tip2
))
507 raise Failure('%r and %r do not have a common merge base' % (tip1
, tip2
))
508 if len(merge_bases
) == 1:
509 return merge_bases
[0]
511 # There are multiple merge bases. The "best" one is the one that
512 # is the "closest" to the tips, which we define to be the one with
513 # the fewest non-merge commits in "merge_base..tip". (It can be
514 # shown that the result is independent of which tip is used in the
516 best_base
= best_count
= None
517 for merge_base
in merge_bases
:
518 cmd
= ['git', 'rev-list', '--no-merges', '--count', '%s..%s' % (merge_base
, tip1
)]
519 count
= int(check_output(cmd
).strip())
520 if best_base
is None or count
< best_count
:
521 best_base
= merge_base
527 def get_boundaries(tip1
, tip2
):
528 """Get the boundaries of an incremental merge.
530 Given the tips of two branches that should be merged, return
531 (merge_base, commits1, commits2) describing the edges of the
532 imerge. Raise Failure if there are any problems."""
534 merge_base
= compute_best_merge_base(tip1
, tip2
)
536 commits1
= linear_ancestry(merge_base
, tip1
)
538 raise NothingToDoError(tip1
, tip2
)
540 commits2
= linear_ancestry(merge_base
, tip2
)
542 raise NothingToDoError(tip2
, tip1
)
544 return (merge_base
, commits1
, commits2
)
547 class TemporaryHead(object):
548 """A context manager that records the current HEAD state then restores it.
550 The message is used for the reflog."""
552 def __enter__(self
, message
='imerge: restoring'):
553 self
.message
= message
555 self
.head_name
= check_output(['git', 'symbolic-ref', '--quiet', 'HEAD']).strip()
556 except CalledProcessError
:
557 self
.head_name
= None
558 self
.orig_head
= get_commit_sha1('HEAD')
561 def __exit__(self
, exc_type
, exc_val
, exc_tb
):
565 'git', 'symbolic-ref',
566 '-m', self
.message
, 'HEAD',
569 except Exception as e
:
571 'Could not restore HEAD to %r!: %s\n'
572 % (self
.head_name
, e
.message
,)
576 check_call(['git', 'reset', '--hard', self
.orig_head
])
577 except Exception as e
:
579 'Could not restore HEAD to %r!: %s\n'
580 % (self
.orig_head
, e
.message
,)
585 def reparent(commit
, parent_sha1s
, msg
=None):
586 """Create a new commit object like commit, but with the specified parents.
588 commit is the SHA1 of an existing commit and parent_sha1s is a
589 list of SHA1s. Create a new commit exactly like that one, except
590 that it has the specified parent commits. Return the SHA1 of the
591 resulting commit object, which is already stored in the object
592 database but is not yet referenced by anything.
594 If msg is set, then use it as the commit message for the new
597 old_commit
= check_output(['git', 'cat-file', 'commit', commit
])
598 separator
= old_commit
.index('\n\n')
599 headers
= old_commit
[:separator
+ 1].splitlines(True)
600 rest
= old_commit
[separator
+ 2:]
602 new_commit
= StringIO()
603 for i
in range(len(headers
)):
605 if line
.startswith('tree '):
606 new_commit
.write(line
)
607 for parent_sha1
in parent_sha1s
:
608 new_commit
.write('parent %s\n' % (parent_sha1
,))
609 elif line
.startswith('parent '):
610 # Discard old parents:
613 new_commit
.write(line
)
615 new_commit
.write('\n')
617 new_commit
.write(rest
)
619 new_commit
.write(msg
)
620 if not msg
.endswith('\n'):
621 new_commit
.write('\n')
623 process
= subprocess
.Popen(
624 ['git', 'hash-object', '-t', 'commit', '-w', '--stdin'],
625 stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
,
627 out
= communicate(process
, input=new_commit
.getvalue())[0]
628 retcode
= process
.poll()
630 raise Failure('Could not reparent commit %s' % (commit
,))
634 class AutomaticMergeFailed(Exception):
635 def __init__(self
, commit1
, commit2
):
637 self
, 'Automatic merge of %s and %s failed' % (commit1
, commit2
,)
639 self
.commit1
, self
.commit2
= commit1
, commit2
642 def automerge(commit1
, commit2
, msg
=None):
643 """Attempt an automatic merge of commit1 and commit2.
645 Return the SHA1 of the resulting commit, or raise
646 AutomaticMergeFailed on error. This must be called with a clean
649 call_silently(['git', 'checkout', '-f', commit1
])
650 cmd
= ['git', '-c', 'rerere.enabled=false', 'merge']
656 except CalledProcessError
:
657 # We don't use "git merge --abort" here because it was only
658 # added in git version 1.7.4.
659 call_silently(['git', 'reset', '--merge'])
660 raise AutomaticMergeFailed(commit1
, commit2
)
662 return get_commit_sha1('HEAD')
665 class MergeRecord(object):
666 # Bits for the flags field:
668 # There is a saved successful auto merge:
671 # An auto merge (which may have been unsuccessful) has been done:
674 # There is a saved successful manual merge:
677 # A manual merge (which may have been unsuccessful) has been done:
680 # A merge that is currently blocking the merge frontier:
683 # Some useful bit combinations:
684 SAVED
= SAVED_AUTO | SAVED_MANUAL
685 NEW
= NEW_AUTO | NEW_MANUAL
687 AUTO
= SAVED_AUTO | NEW_AUTO
688 MANUAL
= SAVED_MANUAL | NEW_MANUAL
690 ALLOWED_INITIAL_FLAGS
= [
697 def __init__(self
, sha1
=None, flags
=0):
698 # The currently believed correct merge, or None if it is
699 # unknown or the best attempt was unsuccessful.
702 if self
.sha1
is None:
704 raise ValueError('Initial flags (%s) for sha1=None should be 0' % (flags
,))
705 elif flags
not in self
.ALLOWED_INITIAL_FLAGS
:
706 raise ValueError('Initial flags (%s) is invalid' % (flags
,))
711 def record_merge(self
, sha1
, source
):
712 """Record a merge at this position.
714 source must be SAVED_AUTO, SAVED_MANUAL, NEW_AUTO, or NEW_MANUAL."""
716 if source
== self
.SAVED_AUTO
:
717 # SAVED_AUTO is recorded in any case, but only used if it
718 # is the only info available.
719 if self
.flags
& (self
.MANUAL | self
.NEW
) == 0:
722 elif source
== self
.NEW_AUTO
:
723 # NEW_AUTO is silently ignored if any MANUAL value is
725 if self
.flags
& self
.MANUAL
== 0:
728 elif source
== self
.SAVED_MANUAL
:
729 # SAVED_MANUAL is recorded in any case, but only used if
730 # no NEW_MANUAL is available.
731 if self
.flags
& self
.NEW_MANUAL
== 0:
734 elif source
== self
.NEW_MANUAL
:
735 # NEW_MANUAL is always used, and also causes NEW_AUTO to
736 # be forgotten if present.
738 self
.flags
= (self
.flags | source
) & ~self
.NEW_AUTO
740 raise ValueError('Undefined source: %s' % (source
,))
742 def record_blocked(self
, blocked
):
744 self
.flags |
= self
.BLOCKED
746 self
.flags
&= ~self
.BLOCKED
749 return self
.sha1
is not None
751 def is_blocked(self
):
752 return self
.flags
& self
.BLOCKED
!= 0
755 return self
.flags
& self
.MANUAL
!= 0
757 def save(self
, name
, i1
, i2
):
758 """If this record has changed, save it."""
763 '-m', 'imerge %r: Record %s merge' % (name
, source
,),
764 'refs/imerge/%s/%s/%d-%d' % (name
, source
, i1
, i2
),
768 def clear_ref(source
):
771 '-m', 'imerge %r: Remove obsolete %s merge' % (name
, source
,),
772 '-d', 'refs/imerge/%s/%s/%d-%d' % (name
, source
, i1
, i2
),
775 if self
.flags
& self
.MANUAL
:
776 if self
.flags
& self
.AUTO
:
777 # Any MANUAL obsoletes any AUTO:
778 if self
.flags
& self
.SAVED_AUTO
:
781 self
.flags
&= ~self
.AUTO
783 if self
.flags
& self
.NEW_MANUAL
:
784 # Convert NEW_MANUAL to SAVED_MANUAL.
787 self
.flags |
= self
.SAVED_MANUAL
788 elif self
.flags
& self
.SAVED_MANUAL
:
789 # Delete any existing SAVED_MANUAL:
791 self
.flags
&= ~self
.SAVED_MANUAL
792 self
.flags
&= ~self
.NEW_MANUAL
794 elif self
.flags
& self
.NEW_AUTO
:
795 # Convert NEW_AUTO to SAVED_AUTO.
798 self
.flags |
= self
.SAVED_AUTO
799 elif self
.flags
& self
.SAVED_AUTO
:
800 # Delete any existing SAVED_AUTO:
802 self
.flags
&= ~self
.SAVED_AUTO
803 self
.flags
&= ~self
.NEW_AUTO
806 class UnexpectedMergeFailure(Exception):
807 def __init__(self
, msg
, i1
, i2
):
808 Exception.__init
__(self
, msg
)
809 self
.i1
, self
.i2
= i1
, i2
812 class BlockCompleteError(Exception):
816 class FrontierBlockedError(Exception):
817 def __init__(self
, msg
, i1
, i2
):
818 Exception.__init
__(self
, msg
)
823 class NotABlockingCommitError(Exception):
827 class MergeFrontier(object):
828 """Represents the merge frontier within a Block.
830 A MergeFrontier is represented by a list of SubBlocks, each of
831 which is thought to be completely mergeable. The list is kept in
834 * Only non-empty blocks are retained
836 * Blocks are sorted from bottom left to upper right
838 * No redundant blocks
843 def map_known_frontier(block
):
844 """Return the MergeFrontier describing existing successful merges in block.
846 The return value only includes the part that is fully outlined
847 and whose outline consists of rectangles reaching back to
850 A blocked commit is *not* considered to be within the
851 frontier, even if a merge is registered for it. Such merges
852 must be explicitly unblocked."""
854 # FIXME: This algorithm can take combinatorial time, for
855 # example if there is a big block of merges that is a dead
864 # The problem is that the algorithm will explore all of the
865 # ways of getting to commit *, and the number of paths grows
866 # like a binomial coefficient. The solution would be to
867 # remember dead-ends and reject any curves that visit a point
868 # to the right of a dead-end.
870 # For now we don't intend to allow a situation like this to be
871 # created, so we ignore the problem.
873 # A list (i1, i2, down) of points in the path so far. down is
874 # True iff the attempted step following this one was
878 def create_frontier(path
):
880 for ((i1old
, i2old
, downold
), (i1new
, i2new
, downnew
)) in iter_neighbors(path
):
881 if downold
is True and downnew
is False:
882 blocks
.append(block
[:i1new
+ 1, :i2new
+ 1])
883 return MergeFrontier(block
, blocks
)
887 # * path is a valid path
889 # * (i1, i2) is in block but it not yet added to path
891 # * down is True if a step downwards from (i1, i2) has not yet
893 (i1
, i2
) = (block
.len1
- 1, 0)
897 if i2
== block
.len2
- 1:
898 # Hit edge of block; can't move down:
900 elif (i1
, i2
+ 1) in block
and not block
.is_blocked(i1
, i2
+ 1):
902 path
.append((i1
, i2
, True))
910 path
.append((i1
, i2
, False))
911 return create_frontier(path
)
912 elif (i1
- 1, i2
) in block
and not block
.is_blocked(i1
- 1, i2
):
914 path
.append((i1
, i2
, False))
918 # There's no way to go forward; backtrack until we
919 # find a place where we can still try going left:
922 (i1
, i2
, down
) = path
.pop()
924 # This shouldn't happen because, in the
925 # worst case, there is a valid path across
926 # the top edge of the merge diagram.
927 raise RuntimeError('Block is improperly formed!')
933 def compute_by_bisection(block
):
934 """Return a MergeFrontier instance for block.
936 We make the following assumptions (using Python subscript
939 0. All of the merges in block[1:,0] and block[0,1:] are
940 already known. (This is an invariant of the Block class.)
942 1. If a direct merge can be done between block[i1-1,0] and
943 block[0,i2-1], then all of the pairwise merges in
944 block[1:i1, 1:i2] can also be done.
946 2. If a direct merge fails between block[i1-1,0] and
947 block[0,i2-1], then all of the pairwise merges in
948 block[i1-1:,i2-1:] would also fail.
950 Under these assumptions, the merge frontier is a stepstair
951 pattern going from the bottom-left to the top-right, and
952 bisection can be used to find the transition between mergeable
953 and conflicting in any row or column.
955 Of course these assumptions are not rigorously true, so the
956 MergeFrontier returned by this function is only an
957 approximation of the real merge diagram. We check for and
958 correct such inconsistencies later."""
960 # Given that these diagrams typically have few blocks, check
961 # the end of a range first to see if the whole range can be
962 # determined, and fall back to bisection otherwise. We
963 # determine the frontier block by block, starting in the lower
966 if block
.len1
<= 1 or block
.len2
<= 1 or block
.is_blocked(1, 1):
967 return MergeFrontier(block
, [])
969 if block
.is_mergeable(block
.len1
- 1, block
.len2
- 1):
970 # The whole block is mergable!
971 return MergeFrontier(block
, [block
])
973 if not block
.is_mergeable(1, 1):
974 # There are no mergeable blocks in block; therefore,
975 # block[1,1] must itself be unmergeable. Record that
977 block
[1, 1].record_blocked(True)
978 return MergeFrontier(block
, [])
982 # At this point, we know that there is at least one mergeable
983 # commit in the first column. Find the height of the success
986 i2
= find_first_false(
987 lambda i
: block
.is_mergeable(i1
, i
),
991 # Now we know that (1,i2-1) is mergeable but (1,i2) is not;
992 # e.g., (i1, i2) is like 'A' (or maybe 'B') in the following
993 # diagram (where '*' means mergeable, 'x' means not mergeable,
994 # and '?' means indeterminate) and that the merge under 'A' is
1012 # At this point in the loop, we know that any blocks to
1013 # the left of 'A' have already been recorded, (i1, i2-1)
1014 # is mergeable but (i1, i2) is not; e.g., we are at a
1015 # place like 'A' in the following diagram:
1027 # This implies that (i1, i2) is the first unmergeable
1028 # commit in a blocker block (though blocker blocks are not
1029 # recorded explicitly). It also implies that a mergeable
1030 # block has its last mergeable commit somewhere in row
1031 # i2-1; find its width.
1033 i1
== block
.len1
- 1
1034 or block
.is_mergeable(block
.len1
- 1, i2
- 1)
1036 blocks
.append(block
[:block
.len1
, :i2
])
1039 i1
= find_first_false(
1040 lambda i
: block
.is_mergeable(i
, i2
- 1),
1041 i1
+ 1, block
.len1
- 1,
1043 blocks
.append(block
[:i1
, :i2
])
1045 # At this point in the loop, (i1-1, i2-1) is mergeable but
1046 # (i1, i2-1) is not; e.g., 'A' in the following diagram:
1058 # The block ending at (i1-1,i2-1) has just been recorded.
1059 # Now find the height of the conflict rectangle at column
1060 # i1 and fill it in:
1061 if i2
- 1 == 1 or not block
.is_mergeable(i1
, 1):
1064 i2
= find_first_false(
1065 lambda i
: block
.is_mergeable(i1
, i
),
1069 return MergeFrontier(block
, blocks
)
1071 def __init__(self
, block
, blocks
=None):
1073 self
.blocks
= self
._normalized
_blocks
(blocks
or [])
1076 """Iterate over blocks from bottom left to upper right."""
1078 return iter(self
.blocks
)
1081 """Return True iff this frontier has no completed parts."""
1083 return bool(self
.blocks
)
1085 def __nonzero__(self
):
1086 """Return True iff this frontier has no completed parts."""
1088 return bool(self
.blocks
)
1090 def is_complete(self
):
1091 """Return True iff the frontier covers the whole block."""
1094 len(self
.blocks
) == 1
1095 and self
.blocks
[0].len1
== self
.block
.len1
1096 and self
.blocks
[0].len2
== self
.block
.len2
1099 # Additional codes used in the 2D array returned from create_diagram()
1100 FRONTIER_WITHIN
= 0x10
1101 FRONTIER_RIGHT_EDGE
= 0x20
1102 FRONTIER_BOTTOM_EDGE
= 0x40
1103 FRONTIER_MASK
= 0x70
1106 def default_formatter(cls
, node
, legend
=None):
1107 def color(node
, within
):
1109 return AnsiColor
.B_GREEN
+ node
+ AnsiColor
.END
1111 return AnsiColor
.B_RED
+ node
+ AnsiColor
.END
1114 legend
= ['?', '*', '.', '#', '@', '-', '|', '+']
1115 merge
= node
& Block
.MERGE_MASK
1116 within
= merge
== Block
.MERGE_MANUAL
or (node
& cls
.FRONTIER_WITHIN
)
1117 skip
= [Block
.MERGE_MANUAL
, Block
.MERGE_BLOCKED
, Block
.MERGE_UNBLOCKED
]
1118 if merge
not in skip
:
1119 vertex
= (cls
.FRONTIER_BOTTOM_EDGE | cls
.FRONTIER_RIGHT_EDGE
)
1120 edge_status
= node
& vertex
1121 if edge_status
== vertex
:
1122 return color(legend
[-1], within
)
1123 elif edge_status
== cls
.FRONTIER_RIGHT_EDGE
:
1124 return color(legend
[-2], within
)
1125 elif edge_status
== cls
.FRONTIER_BOTTOM_EDGE
:
1126 return color(legend
[-3], within
)
1127 return color(legend
[merge
], within
)
1129 def create_diagram(self
):
1130 """Generate a diagram of this frontier.
1132 The returned diagram is a nested list of integers forming a 2D array,
1133 representing the merge frontier embedded in the diagram of commits
1134 returned from Block.create_diagram().
1136 At each node in the returned diagram is an integer whose value is a
1137 bitwise-or of existing MERGE_* constant from Block.create_diagram()
1138 and zero or more of the FRONTIER_* constants defined in this class."""
1140 diagram
= self
.block
.create_diagram()
1143 next_block
= self
.blocks
[0]
1147 diagram
[0][-1] |
= self
.FRONTIER_BOTTOM_EDGE
1148 for i2
in range(1, self
.block
.len2
):
1149 if next_block
is None or i2
>= next_block
.len2
:
1150 diagram
[0][i2
] |
= self
.FRONTIER_RIGHT_EDGE
1153 for n
in range(len(self
.blocks
)):
1154 block
= self
.blocks
[n
]
1156 next_block
= self
.blocks
[n
+ 1]
1160 for i1
in range(block
.len1
):
1161 for i2
in range(block
.len2
):
1162 v
= self
.FRONTIER_WITHIN
1163 if i1
== block
.len1
- 1 and (
1164 next_block
is None or i2
>= next_block
.len2
1166 v |
= self
.FRONTIER_RIGHT_EDGE
1167 if i2
== block
.len2
- 1 and (
1168 prev_block
is None or i1
>= prev_block
.len1
1170 v |
= self
.FRONTIER_BOTTOM_EDGE
1171 diagram
[i1
][i2
] |
= v
1175 prev_block
= self
.blocks
[-1]
1179 for i1
in range(1, self
.block
.len1
):
1180 if prev_block
is None or i1
>= prev_block
.len1
:
1181 diagram
[i1
][0] |
= self
.FRONTIER_BOTTOM_EDGE
1182 diagram
[-1][0] |
= self
.FRONTIER_RIGHT_EDGE
1186 def format_diagram(self
, formatter
=None, diagram
=None):
1187 if formatter
is None:
1188 formatter
= self
.default_formatter
1190 diagram
= self
.create_diagram()
1192 [formatter(diagram
[i1
][i2
]) for i2
in range(self
.block
.len2
)]
1193 for i1
in range(self
.block
.len1
)]
1196 """Write this frontier to file-like object f."""
1197 diagram
= self
.format_diagram()
1198 for i2
in range(self
.block
.len2
):
1199 for i1
in range(self
.block
.len1
):
1200 f
.write(diagram
[i1
][i2
])
1203 def write_html(self
, f
, name
, cssfile
='imerge.css', abbrev_sha1
=7):
1205 Block
.MERGE_UNKNOWN
: 'merge_unknown',
1206 Block
.MERGE_MANUAL
: 'merge_manual',
1207 Block
.MERGE_AUTOMATIC
: 'merge_automatic',
1208 Block
.MERGE_BLOCKED
: 'merge_blocked',
1209 Block
.MERGE_UNBLOCKED
: 'merge_unblocked',
1210 self
.FRONTIER_WITHIN
: 'frontier_within',
1211 self
.FRONTIER_RIGHT_EDGE
: 'frontier_right_edge',
1212 self
.FRONTIER_BOTTOM_EDGE
: 'frontier_bottom_edge',
1215 def map_to_classes(i1
, i2
, node
):
1216 merge
= node
& Block
.MERGE_MASK
1217 ret
= [class_map
[merge
]]
1218 for bit
in [self
.FRONTIER_WITHIN
, self
.FRONTIER_RIGHT_EDGE
,
1219 self
.FRONTIER_BOTTOM_EDGE
]:
1221 ret
.append(class_map
[bit
])
1222 if not (node
& self
.FRONTIER_WITHIN
):
1223 ret
.append('frontier_without')
1224 elif (node
& Block
.MERGE_MASK
) == Block
.MERGE_UNKNOWN
:
1225 ret
.append('merge_skipped')
1226 if i1
== 0 or i2
== 0:
1227 ret
.append('merge_initial')
1229 ret
.append('col_left')
1230 if i1
== self
.block
.len1
- 1:
1231 ret
.append('col_right')
1233 ret
.append('row_top')
1234 if i2
== self
.block
.len2
- 1:
1235 ret
.append('row_bottom')
1241 <title>git-imerge: %s</title>
1242 <link rel="stylesheet" href="%s" type="text/css" />
1246 """ % (name
, cssfile
))
1248 diagram
= self
.create_diagram()
1251 f
.write(' <th class="indexes"> </td>\n')
1252 for i1
in range(self
.block
.len1
):
1253 f
.write(' <th class="indexes">%d-*</td>\n' % (i1
,))
1256 for i2
in range(self
.block
.len2
):
1258 f
.write(' <th class="indexes">*-%d</td>\n' % (i2
,))
1259 for i1
in range(self
.block
.len1
):
1260 classes
= map_to_classes(i1
, i2
, diagram
[i1
][i2
])
1261 record
= self
.block
.get_value(i1
, i2
)
1262 sha1
= record
.sha1
or ''
1263 td_id
= record
.sha1
and ' id="%s"' % (record
.sha1
) or ''
1264 td_class
= classes
and ' class="' + ' '.join(classes
) + '"' or ''
1265 f
.write(' <td%s%s>%.*s</td>\n' % (
1266 td_id
, td_class
, abbrev_sha1
, sha1
))
1268 f
.write('</table>\n</body>\n</html>\n')
1271 def _normalized_blocks(blocks
):
1272 """Return a normalized list of blocks from the argument.
1274 * Remove empty blocks.
1276 * Remove redundant blocks.
1278 * Sort the blocks according to their len1 members.
1282 def contains(block1
, block2
):
1283 """Return true if block1 contains block2."""
1285 return block1
.len1
>= block2
.len1
and block1
.len2
>= block2
.len2
1287 blocks
= sorted(blocks
, key
=lambda block
: block
.len1
)
1290 for block
in blocks
:
1291 if block
.len1
== 0 or block
.len2
== 0:
1299 if contains(last
, block
):
1301 elif contains(block
, last
):
1309 def remove_failure(self
, i1
, i2
):
1310 """Refine the merge frontier given that the specified merge fails."""
1313 shrunk_block
= False
1315 for block
in self
.blocks
:
1316 if i1
< block
.len1
and i2
< block
.len2
:
1318 newblocks
.append(block
[:i1
, :])
1320 newblocks
.append(block
[:, :i2
])
1323 newblocks
.append(block
)
1326 self
.blocks
= self
._normalized
_blocks
(newblocks
)
1328 def partition(self
, block
):
1329 """Return two MergeFrontier instances partitioned by block.
1331 Return (frontier1, frontier2), where each frontier is limited
1332 to each side of the argument.
1334 block must be contained in this MergeFrontier and already be
1337 # Remember that the new blocks have to include the outlined
1338 # edge of the partitioning block to satisfy the invariant that
1339 # the left and upper edge of a block has to be known.
1343 for b
in self
.blocks
:
1344 if b
.len1
== block
.len1
and b
.len2
== block
.len2
:
1345 # That's the block we're partitioning on; just skip it.
1347 elif b
.len1
< block
.len1
and b
.len2
> block
.len2
:
1348 left
.append(b
[:, block
.len2
- 1:])
1349 elif b
.len1
> block
.len1
and b
.len2
< block
.len2
:
1350 right
.append(b
[block
.len1
- 1:, :])
1353 'MergeFrontier partitioned with inappropriate block'
1356 MergeFrontier(self
.block
[:block
.len1
, block
.len2
- 1:], left
),
1357 MergeFrontier(self
.block
[block
.len1
- 1:, :block
.len2
], right
),
1360 def iter_blocker_blocks(self
):
1361 """Iterate over the blocks on the far side of this frontier.
1363 This must only be called for an outlined frontier."""
1370 if self
.blocks
[0].len2
< self
.block
.len2
:
1371 blockruns
.append([self
.block
[0, :]])
1372 blockruns
.append(self
)
1373 if self
.blocks
[-1].len1
< self
.block
.len1
:
1374 blockruns
.append([self
.block
[:, 0]])
1376 for block1
, block2
in iter_neighbors(itertools
.chain(*blockruns
)):
1377 yield self
.block
[block1
.len1
- 1:block2
.len1
, block2
.len2
- 1: block1
.len2
]
1379 def get_affected_blocker_block(self
, i1
, i2
):
1380 """Return the blocker block that a successful merge (i1,i2) would unblock.
1382 If there is no such block, raise NotABlockingCommitError."""
1384 for block
in self
.iter_blocker_blocks():
1386 (block_i1
, block_i2
) = block
.convert_original_indexes(i1
, i2
)
1390 if (block_i1
, block_i2
) == (1, 1):
1391 # That's the one we need to improve this block:
1394 # An index pair can only be in a single blocker
1395 # block, which we've already found:
1396 raise NotABlockingCommitError(
1397 'Commit %d-%d was not blocking the frontier.'
1398 % self
.block
.get_original_indexes(i1
, i2
)
1401 raise NotABlockingCommitError(
1402 'Commit %d-%d was not on the frontier.'
1403 % self
.block
.get_original_indexes(i1
, i2
)
1406 def auto_expand(self
):
1407 """Try pushing out one of the blocks on this frontier.
1409 Raise BlockCompleteError if the whole block has already been
1410 solved. Raise FrontierBlockedError if the frontier is blocked
1411 everywhere. This method does *not* update self; if it returns
1412 successfully you should recompute the frontier from
1415 blocks
= list(self
.iter_blocker_blocks())
1417 raise BlockCompleteError('The block is already complete')
1419 # Try blocks from left to right:
1420 blocks
.sort(key
=lambda block
: block
.get_original_indexes(0, 0))
1422 for block
in blocks
:
1423 if block
.auto_expand_frontier():
1426 # None of the blocks could be expanded. Suggest that the
1427 # caller do a manual merge of the commit that is blocking
1428 # the leftmost blocker block.
1429 i1
, i2
= blocks
[0].get_original_indexes(1, 1)
1430 raise FrontierBlockedError(
1431 'Conflict; suggest manual merge of %d-%d' % (i1
, i2
),
1436 class NoManualMergeError(Exception):
1440 class ManualMergeUnusableError(Exception):
1441 def __init__(self
, msg
, commit
):
1442 Exception.__init
__(self
, 'Commit %s is not usable; %s' % (commit
, msg
))
1443 self
.commit
= commit
1446 class CommitNotFoundError(Exception):
1447 def __init__(self
, commit
):
1450 'Commit %s was not found among the known merge commits' % (commit
,),
1452 self
.commit
= commit
1455 class Block(object):
1456 """A rectangular range of commits, indexed by (i1,i2).
1458 The commits block[0,1:] and block[1:,0] are always all known.
1459 block[0,0] may or may not be known; it is usually unneeded (except
1464 name -- the name of the imerge of which this block is part.
1466 len1, len2 -- the dimensions of the block.
1470 def __init__(self
, name
, len1
, len2
):
1475 def get_merge_state(self
):
1476 """Return the MergeState instance containing this Block."""
1478 raise NotImplementedError()
1481 """Return the area of this block, ignoring the known edges."""
1483 return (self
.len1
- 1) * (self
.len2
- 1)
1485 def _check_indexes(self
, i1
, i2
):
1486 if not (0 <= i1
< self
.len1
):
1487 raise IndexError('first index (%s) is out of range 0:%d' % (i1
, self
.len1
,))
1488 if not (0 <= i2
< self
.len2
):
1489 raise IndexError('second index (%s) is out of range 0:%d' % (i2
, self
.len2
,))
1491 def _normalize_indexes(self
, index
):
1492 """Return a pair of non-negative integers (i1, i2)."""
1497 raise IndexError('Block indexing requires exactly two indexes')
1504 self
._check
_indexes
(i1
, i2
)
1507 def get_original_indexes(self
, i1
, i2
):
1508 """Return the original indexes corresponding to (i1,i2) in this block.
1510 This function supports negative indexes."""
1512 return self
._normalize
_indexes
((i1
, i2
))
1514 def convert_original_indexes(self
, i1
, i2
):
1515 """Return the indexes in this block corresponding to original indexes (i1,i2).
1517 raise IndexError if they are not within this block. This
1518 method does not support negative indices."""
1522 def _set_value(self
, i1
, i2
, value
):
1523 """Set the MergeRecord for integer indexes (i1, i2).
1525 i1 and i2 must be non-negative."""
1527 raise NotImplementedError()
1529 def get_value(self
, i1
, i2
):
1530 """Return the MergeRecord for integer indexes (i1, i2).
1532 i1 and i2 must be non-negative."""
1534 raise NotImplementedError()
1536 def __getitem__(self
, index
):
1537 """Return the MergeRecord at (i1, i2) (requires two indexes).
1539 If i1 and i2 are integers but the merge is unknown, return
1540 None. If either index is a slice, return a SubBlock."""
1545 raise IndexError('Block indexing requires exactly two indexes')
1546 if isinstance(i1
, slice) or isinstance(i2
, slice):
1547 return SubBlock(self
, i1
, i2
)
1549 return self
.get_value(*self
._normalize
_indexes
((i1
, i2
)))
1551 def __contains__(self
, index
):
1552 return self
[index
].is_known()
1554 def is_blocked(self
, i1
, i2
):
1555 """Return True iff the specified commit is blocked."""
1557 (i1
, i2
) = self
._normalize
_indexes
((i1
, i2
))
1558 return self
[i1
, i2
].is_blocked()
1560 def is_mergeable(self
, i1
, i2
):
1561 """Determine whether (i1,i2) can be merged automatically.
1563 If we already have a merge record for (i1,i2), return True.
1564 Otherwise, attempt a merge (discarding the result)."""
1566 (i1
, i2
) = self
._normalize
_indexes
((i1
, i2
))
1567 if (i1
, i2
) in self
:
1571 'Attempting automerge of %d-%d...' % self
.get_original_indexes(i1
, i2
)
1574 automerge(self
[i1
, 0].sha1
, self
[0, i2
].sha1
)
1575 sys
.stderr
.write('success.\n')
1577 except AutomaticMergeFailed
:
1578 sys
.stderr
.write('failure.\n')
1581 def auto_outline(self
):
1582 """Complete the outline of this Block.
1584 raise UnexpectedMergeFailure if automerging fails."""
1586 # Check that all of the merges go through before recording any
1587 # of them permanently.
1590 def do_merge(i1
, commit1
, i2
, commit2
, msg
='Autofilling %d-%d...', record
=True):
1591 if (i1
, i2
) in self
:
1592 return self
[i1
, i2
].sha1
1593 (i1orig
, i2orig
) = self
.get_original_indexes(i1
, i2
)
1594 sys
.stderr
.write(msg
% (i1orig
, i2orig
))
1595 logmsg
= 'imerge \'%s\': automatic merge %d-%d' % (self
.name
, i1orig
, i2orig
)
1597 merge
= automerge(commit1
, commit2
, msg
=logmsg
)
1598 sys
.stderr
.write('success.\n')
1599 except AutomaticMergeFailed
as e
:
1600 sys
.stderr
.write('unexpected conflict. Backtracking...\n')
1601 raise UnexpectedMergeFailure(str(e
), i1
, i2
)
1603 merges
.append((i1
, i2
, merge
))
1607 left
= self
[0, i2
].sha1
1608 for i1
in range(1, self
.len1
- 1):
1609 left
= do_merge(i1
, self
[i1
, 0].sha1
, i2
, left
)
1612 above
= self
[i1
, 0].sha1
1613 for i2
in range(1, self
.len2
- 1):
1614 above
= do_merge(i1
, above
, i2
, self
[0, i2
].sha1
)
1616 i1
, i2
= self
.len1
- 1, self
.len2
- 1
1617 if i1
> 1 and i2
> 1:
1618 # We will compare two ways of doing the final "vertex" merge:
1619 # as a continuation of the bottom edge, or as a continuation
1620 # of the right edge. We only accept it if both approaches
1621 # succeed and give identical trees.
1622 vertex_v1
= do_merge(
1623 i1
, self
[i1
, 0].sha1
, i2
, left
,
1624 msg
='Autofilling %d-%d (first way)...',
1627 vertex_v2
= do_merge(
1628 i1
, above
, i2
, self
[0, i2
].sha1
,
1629 msg
='Autofilling %d-%d (second way)...',
1632 if get_tree(vertex_v1
) == get_tree(vertex_v2
):
1634 'The two ways of autofilling %d-%d agree.\n'
1635 % self
.get_original_indexes(i1
, i2
)
1637 # Everything is OK. Now reparent the actual vertex merge to
1638 # have above and left as its parents:
1639 merges
.append((i1
, i2
, reparent(vertex_v1
, [above
, left
])))
1642 'The two ways of autofilling %d-%d do not agree. Backtracking...\n'
1643 % self
.get_original_indexes(i1
, i2
)
1645 raise UnexpectedMergeFailure('Inconsistent vertex merges', i1
, i2
)
1648 i1
, above
, i2
, left
,
1649 msg
='Autofilling %d-%d...',
1652 # Done! Now we can record the results:
1653 sys
.stderr
.write('Recording autofilled block %s.\n' % (self
,))
1654 for (i1
, i2
, merge
) in merges
:
1655 self
[i1
, i2
].record_merge(merge
, MergeRecord
.NEW_AUTO
)
1657 def auto_fill_micromerge(self
):
1658 """Try to fill the very first micromerge in this block.
1660 Return True iff the attempt was successful."""
1662 assert (1, 1) not in self
1663 if self
.len1
<= 1 or self
.len2
<= 1 or self
.is_blocked(1, 1):
1667 (i1orig
, i2orig
) = self
.get_original_indexes(i1
, i2
)
1668 sys
.stderr
.write('Attempting to merge %d-%d...' % (i1orig
, i2orig
))
1669 logmsg
= 'imerge \'%s\': automatic merge %d-%d' % (self
.name
, i1orig
, i2orig
)
1672 self
[i1
, i2
- 1].sha1
,
1673 self
[i1
- 1, i2
].sha1
,
1676 sys
.stderr
.write('success.\n')
1677 except AutomaticMergeFailed
:
1678 sys
.stderr
.write('conflict.\n')
1679 self
[i1
, i2
].record_blocked(True)
1682 self
[i1
, i2
].record_merge(merge
, MergeRecord
.NEW_AUTO
)
1685 def auto_outline_frontier(self
, merge_frontier
=None):
1686 """Try to outline the merge frontier of this block.
1688 Return True iff some progress was made."""
1690 if merge_frontier
is None:
1691 merge_frontier
= MergeFrontier
.compute_by_bisection(self
)
1693 if not merge_frontier
:
1697 best_block
= max(merge_frontier
, key
=lambda block
: block
.get_original_indexes(0, 0))
1700 best_block
.auto_outline()
1701 except UnexpectedMergeFailure
as e
:
1702 # One of the merges that we expected to succeed in
1704 merge_frontier
.remove_failure(e
.i1
, e
.i2
)
1705 return self
.auto_outline_frontier(merge_frontier
)
1707 f1
, f2
= merge_frontier
.partition(best_block
)
1709 f1
.block
.auto_outline_frontier(f1
)
1711 f2
.block
.auto_outline_frontier(f2
)
1714 def auto_expand_frontier(self
):
1715 merge_state
= self
.get_merge_state()
1716 if merge_state
.manual
:
1718 elif merge_state
.goal
== 'full':
1719 return self
.auto_fill_micromerge()
1721 return self
.auto_outline_frontier()
1723 # The codes in the 2D array returned from create_diagram()
1731 # A map {(is_known(), manual, is_blocked()) : integer constant}
1733 (False, False, False): MERGE_UNKNOWN
,
1734 (False, False, True): MERGE_BLOCKED
,
1735 (True, False, True): MERGE_UNBLOCKED
,
1736 (True, True, True): MERGE_UNBLOCKED
,
1737 (True, False, False): MERGE_AUTOMATIC
,
1738 (True, True, False): MERGE_MANUAL
,
1741 def create_diagram(self
):
1742 """Generate a diagram of this Block.
1744 The returned diagram, is a nested list of integers forming a 2D array,
1745 where the integer at diagram[i1][i2] is one of MERGE_UNKNOWN,
1746 MERGE_MANUAL, MERGE_AUTOMATIC, MERGE_BLOCKED, or MERGE_UNBLOCKED,
1747 representing the state of the commit at (i1, i2)."""
1749 diagram
= [[None for i2
in range(self
.len2
)] for i1
in range(self
.len1
)]
1751 for i2
in range(self
.len2
):
1752 for i1
in range(self
.len1
):
1753 rec
= self
.get_value(i1
, i2
)
1754 c
= self
.MergeState
[
1755 rec
.is_known(), rec
.is_manual(), rec
.is_blocked()]
1760 def format_diagram(self
, legend
=None, diagram
=None):
1763 AnsiColor
.D_GRAY
+ '?' + AnsiColor
.END
,
1764 AnsiColor
.B_GREEN
+ '*' + AnsiColor
.END
,
1765 AnsiColor
.B_GREEN
+ '.' + AnsiColor
.END
,
1766 AnsiColor
.B_RED
+ '#' + AnsiColor
.END
,
1767 AnsiColor
.B_YELLOW
+ '@' + AnsiColor
.END
,
1770 diagram
= self
.create_diagram()
1772 [legend
[diagram
[i1
][i2
]] for i2
in range(self
.len2
)]
1773 for i1
in range(self
.len1
)]
1775 def write(self
, f
, legend
=None, sep
='', linesep
='\n'):
1776 diagram
= self
.format_diagram(legend
)
1777 for i2
in range(self
.len2
):
1778 f
.write(sep
.join(diagram
[i1
][i2
] for i1
in range(self
.len1
)) + linesep
)
1780 def writeppm(self
, f
):
1782 f
.write('%d %d 255\n' % (self
.len1
, self
.len2
,))
1783 legend
= ['127 127 0', '0 255 0', '0 127 0', '255 0 0', '127 0 0']
1784 self
.write(f
, legend
, sep
=' ')
1787 class SubBlock(Block
):
1789 def _convert_to_slice(i
, len):
1790 """Return (start, len) for the specified index.
1792 i may be an integer or a slice with step equal to 1."""
1794 if isinstance(i
, int):
1798 elif isinstance(i
, slice):
1799 if i
.step
is not None and i
.step
!= 1:
1800 raise ValueError('Index has a non-zero step size')
1802 raise ValueError('Index cannot be converted to a slice')
1804 (start
, stop
, step
) = i
.indices(len)
1805 return (start
, stop
- start
)
1807 def __init__(self
, block
, slice1
, slice2
):
1808 (start1
, len1
) = self
._convert
_to
_slice
(slice1
, block
.len1
)
1809 (start2
, len2
) = self
._convert
_to
_slice
(slice2
, block
.len2
)
1810 Block
.__init
__(self
, block
.name
, len1
, len2
)
1811 if isinstance(block
, SubBlock
):
1812 # Peel away one level of indirection:
1813 self
._merge
_state
= block
._merge
_state
1814 self
._start
1 = start1
+ block
._start
1
1815 self
._start
2 = start2
+ block
._start
2
1817 assert(isinstance(block
, MergeState
))
1818 self
._merge
_state
= block
1819 self
._start
1 = start1
1820 self
._start
2 = start2
1822 def get_merge_state(self
):
1823 return self
._merge
_state
1825 def get_original_indexes(self
, i1
, i2
):
1826 i1
, i2
= self
._normalize
_indexes
((i1
, i2
))
1827 return self
._merge
_state
.get_original_indexes(
1832 def convert_original_indexes(self
, i1
, i2
):
1833 (i1
, i2
) = self
._merge
_state
.convert_original_indexes(i1
, i2
)
1835 self
._start
1 <= i1
< self
._start
1 + self
.len1
1836 and self
._start
2 <= i2
< self
._start
2 + self
.len2
1838 raise IndexError('Indexes are not within block')
1839 return (i1
- self
._start
1, i2
- self
._start
2)
1841 def _set_value(self
, i1
, i2
, sha1
, flags
):
1842 self
._check
_indexes
(i1
, i2
)
1843 self
._merge
_state
._set
_value
(
1849 def get_value(self
, i1
, i2
):
1850 self
._check
_indexes
(i1
, i2
)
1851 return self
._merge
_state
.get_value(i1
+ self
._start
1, i2
+ self
._start
2)
1854 return '%s[%d:%d,%d:%d]' % (
1856 self
._start
1, self
._start
1 + self
.len1
,
1857 self
._start
2, self
._start
2 + self
.len2
,
1861 class MergeState(Block
):
1863 'auto': MergeRecord
.SAVED_AUTO
,
1864 'manual': MergeRecord
.SAVED_MANUAL
,
1867 MERGE_STATE_RE
= re
.compile(
1879 def iter_existing_names():
1880 """Iterate over the names of existing MergeStates in this repo."""
1882 for line
in check_output(['git', 'for-each-ref', 'refs/imerge']).splitlines():
1883 (sha1
, type, refname
) = line
.split()
1885 m
= MergeState
.MERGE_STATE_RE
.match(refname
)
1887 yield m
.group('name')
1890 def get_scratch_refname(name
):
1891 return 'refs/heads/imerge/%s' % (name
,)
1894 def _read_state(name
, sha1
):
1895 state_string
= check_output(['git', 'cat-file', 'blob', sha1
])
1896 state
= json
.loads(state_string
)
1898 version
= tuple(int(i
) for i
in state
['version'].split('.'))
1899 if version
[0] != STATE_VERSION
[0] or version
[1] > STATE_VERSION
[1]:
1901 'The format of imerge %s (%s) is not compatible with this script version.'
1902 % (name
, state
['version'],)
1904 state
['version'] = version
1908 def check_exists(name
):
1909 """Verify that a MergeState with the given name exists.
1911 Just check for the existence, readability, and compatible
1912 version of the 'state' reference. If the reference doesn't
1913 exist, just return False. If it exists but is unusable for
1914 some other reason, raise an exception."""
1918 ['git', 'check-ref-format', 'refs/imerge/%s' % (name
,)]
1920 except CalledProcessError
:
1921 raise Failure('Name %r is not a valid refname component!' % (name
,))
1923 state_refname
= 'refs/imerge/%s/state' % (name
,)
1924 for line
in check_output(['git', 'for-each-ref', state_refname
]).splitlines():
1925 (sha1
, type, refname
) = line
.split()
1926 if refname
== state_refname
and type == 'blob':
1927 MergeState
._read
_state
(name
, sha1
)
1928 # If that didn't throw an exception:
1934 def set_default_name(name
):
1935 """Set the default merge to the specified one.
1937 name can be None to cause the default to be cleared."""
1941 check_call(['git', 'config', '--unset', 'imerge.default'])
1942 except CalledProcessError
as e
:
1943 if e
.returncode
== 5:
1949 check_call(['git', 'config', 'imerge.default', name
])
1952 def get_default_name():
1953 """Get the name of the default merge, or None if none is currently set."""
1956 return check_output(['git', 'config', 'imerge.default']).rstrip()
1957 except CalledProcessError
:
1961 def _check_no_merges(commits
):
1962 multiparent_commits
= [
1964 for commit
in commits
1965 if len(get_commit_parents(commit
)) > 1
1967 if multiparent_commits
:
1969 'The following commits on the to-be-merged branch are merge commits:\n'
1971 '--goal=\'rebase\' is not yet supported for branches that include merges.\n'
1972 % ('\n '.join(multiparent_commits
),)
1976 def check_name_format(name
):
1977 """Check that name is a valid imerge name."""
1981 ['git', 'check-ref-format', 'refs/imerge/%s' % (name
,)]
1983 except CalledProcessError
:
1984 raise Failure('Name %r is not a valid refname component!' % (name
,))
1991 goal
=DEFAULT_GOAL
, manual
=False, branch
=None,
1993 """Create and return a new MergeState object."""
1995 MergeState
.check_name_format(name
)
1997 check_branch_name_format(branch
)
2001 if check_output(['git', 'for-each-ref', 'refs/imerge/%s' % (name
,)]):
2002 raise Failure('Name %r is already in use!' % (name
,))
2004 if goal
== 'rebase':
2005 MergeState
._check
_no
_merges
(commits2
)
2011 MergeRecord
.NEW_MANUAL
,
2019 merge_ref_re
= re
.compile(
2023 """ + re
.escape(name
) + r
"""
2024 \/(?P<source>auto|manual)\/
2025 (?P<i1>0|[1-9][0-9]*)
2027 (?P<i2>0|[1-9][0-9]*)
2033 state_ref_re
= re
.compile(
2037 """ + re
.escape(name
) + r
"""
2046 # A map {(i1, i2) : (sha1, source)}:
2049 # refnames that were found but not understood:
2052 for line
in check_output([
2053 'git', 'for-each-ref', 'refs/imerge/%s' % (name
,)
2055 (sha1
, type, refname
) = line
.split()
2056 m
= merge_ref_re
.match(refname
)
2058 if type != 'commit':
2059 raise Failure('Reference %r is not a commit!' % (refname
,))
2060 i1
, i2
= int(m
.group('i1')), int(m
.group('i2'))
2061 source
= MergeState
.SOURCE_TABLE
[m
.group('source')]
2062 merges
[i1
, i2
] = (sha1
, source
)
2065 m
= state_ref_re
.match(refname
)
2068 raise Failure('Reference %r is not a blob!' % (refname
,))
2069 state
= MergeState
._read
_state
(name
, sha1
)
2072 unexpected
.append(refname
)
2076 'No state found; it should have been a blob reference at '
2077 '"refs/imerge/%s/state"' % (name
,)
2080 blockers
= state
.get('blockers', [])
2084 'Unexpected reference(s) found in "refs/imerge/%s" namespace:\n %s\n'
2085 % (name
, '\n '.join(unexpected
),)
2088 # Find merge_base, commits1, and commits2:
2089 (merge_base
, source
) = merges
.pop((0, 0))
2090 if source
!= MergeRecord
.SAVED_MANUAL
:
2091 raise Failure('Merge base should be manual!')
2093 for i1
in itertools
.count(1):
2095 (sha1
, source
) = merges
.pop((i1
, 0))
2096 if source
!= MergeRecord
.SAVED_MANUAL
:
2097 raise Failure('Merge %d-0 should be manual!' % (i1
,))
2098 commits1
.append(sha1
)
2103 for i2
in itertools
.count(1):
2105 (sha1
, source
) = merges
.pop((0, i2
))
2106 if source
!= MergeRecord
.SAVED_MANUAL
:
2107 raise Failure('Merge (0,%d) should be manual!' % (i2
,))
2108 commits2
.append(sha1
)
2112 tip1
= state
.get('tip1', commits1
[-1])
2113 tip2
= state
.get('tip2', commits2
[-1])
2115 goal
= state
['goal']
2116 if goal
not in ALLOWED_GOALS
:
2117 raise Failure('Goal %r, read from state, is not recognized.' % (goal
,))
2119 manual
= state
['manual']
2120 branch
= state
.get('branch', name
)
2126 MergeRecord
.SAVED_MANUAL
,
2132 # Now write the rest of the merges to state:
2133 for ((i1
, i2
), (sha1
, source
)) in merges
.items():
2134 if i1
== 0 and i2
>= state
.len2
:
2135 raise Failure('Merge 0-%d is missing!' % (state
.len2
,))
2136 if i1
>= state
.len1
and i2
== 0:
2137 raise Failure('Merge %d-0 is missing!' % (state
.len1
,))
2138 if i1
>= state
.len1
or i2
>= state
.len2
:
2140 'Merge %d-%d is out of range [0:%d,0:%d]'
2141 % (i1
, i2
, state
.len1
, state
.len2
)
2143 state
[i1
, i2
].record_merge(sha1
, source
)
2145 # Record any blockers:
2146 for (i1
, i2
) in blockers
:
2147 state
[i1
, i2
].record_blocked(True)
2153 # If HEAD is the scratch refname, abort any in-progress
2154 # commits and detach HEAD:
2155 scratch_refname
= MergeState
.get_scratch_refname(name
)
2157 head_refname
= check_output(['git', 'symbolic-ref', '--quiet', 'HEAD']).strip()
2158 except CalledProcessError
:
2160 if head_refname
== scratch_refname
:
2162 # We don't use "git merge --abort" here because it was
2163 # only added in git version 1.7.4.
2164 check_call(['git', 'reset', '--merge'])
2165 except CalledProcessError
:
2167 # Detach head so that we can delete scratch_refname:
2169 'git', 'update-ref', '--no-deref',
2170 '-m', 'Detach HEAD from %s' % (scratch_refname
,),
2171 'HEAD', get_commit_sha1('HEAD'),
2174 # Delete the scratch refname:
2176 'git', 'update-ref',
2177 '-m', 'imerge %s: remove scratch reference' % (name
,),
2178 '-d', scratch_refname
,
2181 # Remove any references referring to intermediate merges:
2182 for line
in check_output([
2183 'git', 'for-each-ref', 'refs/imerge/%s' % (name
,)
2185 (sha1
, type, refname
) = line
.split()
2188 'git', 'update-ref',
2189 '-m', 'imerge: remove merge %r' % (name
,),
2192 except CalledProcessError
as e
:
2194 'Warning: error removing reference %r: %s' % (refname
, e
)
2197 # If this merge was the default, unset the default:
2198 if MergeState
.get_default_name() == name
:
2199 MergeState
.set_default_name(None)
2202 self
, name
, merge_base
,
2210 Block
.__init
__(self
, name
, len(commits1
) + 1, len(commits2
) + 1)
2214 self
.manual
= bool(manual
)
2215 self
.branch
= branch
or name
2217 # A simulated 2D array. Values are None or MergeRecord instances.
2218 self
._data
= [[None] * self
.len2
for i1
in range(self
.len1
)]
2220 self
.get_value(0, 0).record_merge(merge_base
, source
)
2221 for (i1
, commit
) in enumerate(commits1
, 1):
2222 self
.get_value(i1
, 0).record_merge(commit
, source
)
2223 for (i2
, commit
) in enumerate(commits2
, 1):
2224 self
.get_value(0, i2
).record_merge(commit
, source
)
2226 def get_merge_state(self
):
2229 def set_goal(self
, goal
):
2230 if goal
not in ALLOWED_GOALS
:
2231 raise ValueError('%r is not an allowed goal' % (goal
,))
2233 if goal
== 'rebase':
2234 self
._check
_no
_merges
(
2235 [self
[0, i2
].sha1
for i2
in range(1, self
.len2
)]
2240 def _set_value(self
, i1
, i2
, value
):
2241 self
._data
[i1
][i2
] = value
2243 def get_value(self
, i1
, i2
):
2244 value
= self
._data
[i1
][i2
]
2245 # Missing values spring to life on first access:
2247 value
= MergeRecord()
2248 self
._data
[i1
][i2
] = value
2251 def __contains__(self
, index
):
2252 # Avoid creating new MergeRecord objects here.
2253 (i1
, i2
) = self
._normalize
_indexes
(index
)
2254 value
= self
._data
[i1
][i2
]
2255 return (value
is not None) and value
.is_known()
2257 def auto_complete_frontier(self
):
2258 """Complete the frontier using automerges.
2260 If progress is blocked before the frontier is complete, raise
2261 a FrontierBlockedError. Save the state as progress is
2264 progress_made
= False
2267 frontier
= MergeFrontier
.map_known_frontier(self
)
2268 frontier
.auto_expand()
2270 progress_made
= True
2271 except BlockCompleteError
:
2273 except FrontierBlockedError
as e
:
2275 if not progress_made
:
2276 # Adjust the error message:
2277 raise FrontierBlockedError(
2278 'No progress was possible; suggest manual merge of %d-%d'
2285 def find_index(self
, commit
):
2286 """Return (i1,i2) for the specified commit.
2288 Raise CommitNotFoundError if it is not known."""
2290 for i2
in range(0, self
.len2
):
2291 for i1
in range(0, self
.len1
):
2292 if (i1
, i2
) in self
:
2293 record
= self
[i1
, i2
]
2294 if record
.sha1
== commit
:
2296 raise CommitNotFoundError(commit
)
2298 def incorporate_manual_merge(self
, commit
):
2299 """Record commit as a manual merge of its parents.
2301 Return the indexes (i1,i2) where it was recorded. If the
2302 commit is not usable for some reason, raise
2303 ManualMergeUnusableError."""
2305 parents
= get_commit_parents(commit
)
2306 if len(parents
) < 2:
2307 raise ManualMergeUnusableError('it is not a merge', commit
)
2308 if len(parents
) > 2:
2309 raise ManualMergeUnusableError('it is an octopus merge', commit
)
2310 # Find the parents among our contents...
2312 (i1first
, i2first
) = self
.find_index(parents
[0])
2313 (i1second
, i2second
) = self
.find_index(parents
[1])
2314 except CommitNotFoundError
:
2315 raise ManualMergeUnusableError(
2316 'its parents are not known merge commits', commit
,
2319 if i1first
< i1second
:
2320 # Swap parents to make the parent from above the first parent:
2321 (i1first
, i2first
, i1second
, i2second
) = (i1second
, i2second
, i1first
, i2first
)
2323 if i1first
!= i1second
+ 1 or i2first
!= i2second
- 1:
2324 raise ManualMergeUnusableError(
2325 'it is not a pairwise merge of adjacent parents', commit
,
2328 # Create a new merge with the parents in the conventional order:
2329 commit
= reparent(commit
, [parents
[1], parents
[0]])
2331 i1
, i2
= i1first
, i2second
2332 self
[i1
, i2
].record_merge(commit
, MergeRecord
.NEW_MANUAL
)
2336 def _is_ancestor(commit1
, commit2
):
2337 """Return True iff commit1 is an ancestor (or equal to) commit2."""
2339 if commit1
== commit2
:
2344 'git', 'rev-list', '--count', '--ancestry-path',
2345 '%s..%s' % (commit1
, commit2
,),
2350 def _set_refname(refname
, commit
, force
=False):
2352 ref_oldval
= get_commit_sha1(refname
)
2354 # refname doesn't already exist; simply point it at commit:
2355 check_call(['git', 'update-ref', refname
, commit
])
2356 checkout(refname
, quiet
=True)
2358 # refname already exists. This has two ramifications:
2359 # 1. HEAD might point at it
2360 # 2. We may only fast-forward it (unless force is set)
2362 head_refname
= check_output(['git', 'symbolic-ref', '--quiet', 'HEAD']).strip()
2363 except CalledProcessError
:
2367 if not MergeState
._is
_ancestor
(ref_oldval
, commit
):
2369 '%s cannot be fast-forwarded to %s!' % (refname
, commit
)
2372 if head_refname
== refname
:
2373 check_call(['git', 'reset', '--hard', commit
])
2376 'git', 'update-ref',
2377 '-m', 'imerge: recording final merge',
2380 checkout(refname
, quiet
=True)
2382 def simplify_to_full(self
, refname
, force
=False):
2383 for i1
in range(1, self
.len1
):
2384 for i2
in range(1, self
.len2
):
2385 if not (i1
, i2
) in self
:
2387 'Cannot simplify to "full" because '
2388 'merge %d-%d is not yet done'
2392 self
._set
_refname
(refname
, self
[-1, -1].sha1
, force
=force
)
2394 def simplify_to_rebase_with_history(self
, refname
, force
=False):
2396 for i2
in range(1, self
.len2
):
2397 if not (i1
, i2
) in self
:
2399 'Cannot simplify to rebase-with-history because '
2400 'merge %d-%d is not yet done'
2404 commit
= self
[i1
, 0].sha1
2405 for i2
in range(1, self
.len2
):
2406 orig
= self
[0, i2
].sha1
2407 tree
= get_tree(self
[i1
, i2
].sha1
)
2409 # Create a commit, copying the old log message:
2411 get_log_message(orig
).rstrip('\n')
2412 + '\n\n(rebased-with-history from commit %s)\n' % orig
2414 commit
= commit_tree(tree
, [commit
, orig
], msg
=msg
)
2416 self
._set
_refname
(refname
, commit
, force
=force
)
2418 def simplify_to_rebase(self
, refname
, force
=False):
2420 for i2
in range(1, self
.len2
):
2421 if not (i1
, i2
) in self
:
2423 'Cannot simplify to rebase because merge %d-%d is not yet done'
2428 # A rebase simplification is allowed to discard history,
2429 # as long as the *pre-simplification* apex commit is a
2430 # descendant of the branch to be moved.
2432 ref_oldval
= get_commit_sha1(refname
)
2434 # refname doesn't already exist; no problem.
2437 commit
= self
[-1, -1].sha1
2438 if not MergeState
._is
_ancestor
(ref_oldval
, commit
):
2440 '%s is not an ancestor of %s; use --force if you are sure'
2441 % (commit
, refname
,)
2444 commit
= self
[i1
, 0].sha1
2445 for i2
in range(1, self
.len2
):
2446 orig
= self
[0, i2
].sha1
2447 tree
= get_tree(self
[i1
, i2
].sha1
)
2448 authordata
= get_author_info(orig
)
2450 # Create a commit, copying the old log message and author info:
2451 commit
= commit_tree(
2452 tree
, [commit
], msg
=get_log_message(orig
), metadata
=authordata
,
2455 # We checked above that the update is OK, so here we can set
2457 self
._set
_refname
(refname
, commit
, force
=True)
2459 def simplify_to_merge(self
, refname
, force
=False):
2460 if not (-1, -1) in self
:
2462 'Cannot simplify to merge because merge %d-%d is not yet done'
2463 % (self
.len1
- 1, self
.len2
- 1)
2465 tree
= get_tree(self
[-1, -1].sha1
)
2466 parents
= [self
[-1, 0].sha1
, self
[0, -1].sha1
]
2468 # Create a preliminary commit with a generic commit message:
2471 msg
='Merge %s into %s (using imerge)' % (self
.tip2
, self
.tip1
),
2474 self
._set
_refname
(refname
, sha1
, force
=force
)
2476 # Now let the user edit the commit log message:
2477 check_call(['git', 'commit', '--amend'])
2479 def simplify(self
, refname
, force
=False):
2480 """Simplify this MergeState and save the result to refname.
2482 The merge must be complete before calling this method."""
2484 if self
.goal
== 'full':
2485 self
.simplify_to_full(refname
, force
=force
)
2486 elif self
.goal
== 'rebase-with-history':
2487 self
.simplify_to_rebase_with_history(refname
, force
=force
)
2488 elif self
.goal
== 'rebase':
2489 self
.simplify_to_rebase(refname
, force
=force
)
2490 elif self
.goal
== 'merge':
2491 self
.simplify_to_merge(refname
, force
=force
)
2493 raise ValueError('Invalid value for goal (%r)' % (self
.goal
,))
2496 """Write the current MergeState to the repository."""
2499 for i2
in range(0, self
.len2
):
2500 for i1
in range(0, self
.len1
):
2501 record
= self
[i1
, i2
]
2502 if record
.is_known():
2503 record
.save(self
.name
, i1
, i2
)
2504 if record
.is_blocked():
2505 blockers
.append((i1
, i2
))
2508 version
='.'.join(str(i
) for i
in STATE_VERSION
),
2510 tip1
=self
.tip1
, tip2
=self
.tip2
,
2515 state_string
= json
.dumps(state
, sort_keys
=True) + '\n'
2517 cmd
= ['git', 'hash-object', '-t', 'blob', '-w', '--stdin']
2518 p
= subprocess
.Popen(cmd
, stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
)
2519 out
= communicate(p
, input=state_string
)[0]
2522 raise CalledProcessError(retcode
, cmd
)
2525 'git', 'update-ref',
2526 '-m', 'imerge %r: Record state' % (self
.name
,),
2527 'refs/imerge/%s/state' % (self
.name
,),
2532 return 'MergeState(\'%s\', tip1=\'%s\', tip2=\'%s\', goal=\'%s\')' % (
2533 self
.name
, self
.tip1
, self
.tip2
, self
.goal
,
2537 def request_user_merge(merge_state
, i1
, i2
):
2538 """Prepare the working tree for the user to do a manual merge.
2540 It is assumed that the merges above and to the left of (i1, i2)
2541 are already done."""
2543 above
= merge_state
[i1
, i2
- 1]
2544 left
= merge_state
[i1
- 1, i2
]
2545 if not above
.is_known() or not left
.is_known():
2546 raise RuntimeError('The parents of merge %d-%d are not ready' % (i1
, i2
))
2547 refname
= MergeState
.get_scratch_refname(merge_state
.name
)
2549 'git', 'update-ref',
2550 '-m', 'imerge %r: Prepare merge %d-%d' % (merge_state
.name
, i1
, i2
,),
2551 refname
, above
.sha1
,
2554 logmsg
= 'imerge \'%s\': manual merge %d-%d' % (merge_state
.name
, i1
, i2
)
2557 'git', 'merge', '--no-commit',
2561 except CalledProcessError
:
2562 # We expect an error (otherwise we would have automerged!)
2566 'Original first commit:\n'
2568 check_call(['git', '--no-pager', 'log', '--no-walk', merge_state
[i1
, 0].sha1
])
2571 'Original second commit:\n'
2573 check_call(['git', '--no-pager', 'log', '--no-walk', merge_state
[0, i2
].sha1
])
2576 'There was a conflict merging commit %d-%d, shown above.\n'
2577 'Please resolve the conflict, commit the result, then type\n'
2579 ' git-imerge continue\n'
2584 def incorporate_user_merge(merge_state
, edit_log_msg
=None):
2585 """If the user has done a merge for us, incorporate the results.
2587 If the scratch reference refs/heads/imerge/NAME exists and is
2588 checked out, first check if there are staged changes that can be
2589 committed. Then try to incorporate the current commit into
2590 merge_state, delete the reference, and return (i1,i2)
2591 corresponding to the merge. If the scratch reference does not
2592 exist, raise NoManualMergeError(). If the scratch reference
2593 exists but cannot be used, raise a ManualMergeUnusableError. If
2594 there are unstaged changes in the working tree, emit an error
2595 message and raise UncleanWorkTreeError."""
2597 refname
= MergeState
.get_scratch_refname(merge_state
.name
)
2600 commit
= get_commit_sha1(refname
)
2602 raise NoManualMergeError('Reference %s does not exist.' % (refname
,))
2605 head_name
= check_output(['git', 'symbolic-ref', '--quiet', 'HEAD']).strip()
2606 except CalledProcessError
:
2607 raise NoManualMergeError('HEAD is currently detached.')
2609 if head_name
!= refname
:
2610 # This should not usually happen. The scratch reference
2611 # exists, but it is not current. Perhaps the user gave up on
2612 # an attempted merge then switched to another branch. We want
2613 # to delete refname, but only if it doesn't contain any
2614 # content that we don't already know.
2616 merge_state
.find_index(commit
)
2617 except CommitNotFoundError
:
2618 # It points to a commit that we don't have in our records.
2620 'The scratch reference, %(refname)s, already exists but is not\n'
2621 'checked out. If it points to a merge commit that you would like\n'
2622 'to use, please check it out using\n'
2624 ' git checkout %(refname)s\n'
2626 'and then try to continue again. If it points to a commit that is\n'
2627 'unneeded, then please delete the reference using\n'
2629 ' git update-ref -d %(refname)s\n'
2631 'and then continue.'
2632 % dict(refname
=refname
)
2635 # It points to a commit that is already recorded. We can
2636 # delete it without losing any information.
2638 'git', 'update-ref',
2640 'imerge %r: Remove obsolete scratch reference'
2641 % (merge_state
.name
,),
2645 '%s did not point to a new merge; it has been deleted.\n'
2648 raise NoManualMergeError(
2649 'Reference %s was not checked out.' % (refname
,)
2652 # If we reach this point, then the scratch reference exists and is
2653 # checked out. Now check whether there is staged content that
2656 merge_frontier
= MergeFrontier
.map_known_frontier(merge_state
)
2659 check_call(['git', 'diff-index', '--cached', '--quiet', 'HEAD', '--'])
2660 except CalledProcessError
:
2661 # There are staged changes; commit them if possible.
2662 cmd
= ['git', 'commit', '--no-verify']
2664 if edit_log_msg
is None:
2665 edit_log_msg
= get_default_edit()
2670 cmd
+= ['--no-edit']
2674 except CalledProcessError
:
2675 raise Failure('Could not commit staged changes.')
2676 commit
= get_commit_sha1('HEAD')
2678 require_clean_work_tree('proceed')
2680 # This might throw ManualMergeUnusableError:
2681 (i1
, i2
) = merge_state
.incorporate_manual_merge(commit
)
2683 # Now detach head so that we can delete refname.
2685 'git', 'update-ref', '--no-deref',
2686 '-m', 'Detach HEAD from %s' % (refname
,),
2691 'git', 'update-ref',
2692 '-m', 'imerge %s: remove scratch reference' % (merge_state
.name
,),
2697 # This might throw NotABlockingCommitError:
2698 unblocked_block
= merge_frontier
.get_affected_blocker_block(i1
, i2
)
2699 unblocked_block
[1, 1].record_blocked(False)
2701 'Merge has been recorded for merge %d-%d.\n'
2702 % unblocked_block
.get_original_indexes(1, 1)
2704 except NotABlockingCommitError
:
2710 def choose_merge_name(name
, default_to_unique
=True):
2711 # If a name was specified, try to use it and fail if not possible:
2712 if name
is not None:
2713 if not MergeState
.check_exists(name
):
2714 raise Failure('There is no incremental merge called \'%s\'!' % (name
,))
2715 MergeState
.set_default_name(name
)
2718 # A name was not specified. Try to use the default name:
2719 default_name
= MergeState
.get_default_name()
2721 if MergeState
.check_exists(default_name
):
2724 # There's no reason to keep the invalid default around:
2725 MergeState
.set_default_name(None)
2727 'Warning: The default incremental merge \'%s\' has disappeared.\n'
2728 '(The setting imerge.default has been cleared.)\n'
2729 'Please select an incremental merge using --name'
2733 if default_to_unique
:
2734 # If there is exactly one imerge, set it to be the default and use it.
2735 names
= list(MergeState
.iter_existing_names())
2736 if len(names
) == 1 and MergeState
.check_exists(names
[0]):
2737 MergeState
.set_default_name(names
[0])
2740 raise Failure('Please select an incremental merge using --name')
2743 def read_merge_state(name
=None, default_to_unique
=True):
2744 return MergeState
.read(choose_merge_name(name
, default_to_unique
=default_to_unique
))
2749 NAME_INIT_HELP
= 'name to use for this incremental merge'
2751 def add_name_argument(subparser
, help=None):
2753 subcommand
= subparser
.prog
.split()[1]
2754 help = 'name of incremental merge to {0}'.format(subcommand
)
2756 subparser
.add_argument(
2757 '--name', action
='store', default
=None, help=help,
2760 def add_goal_argument(subparser
, default
=DEFAULT_GOAL
):
2761 help = 'the goal of the incremental merge'
2764 'the type of simplification to be made '
2765 '(default is the value provided to "init" or "start")'
2767 subparser
.add_argument(
2769 action
='store', default
=default
,
2770 choices
=ALLOWED_GOALS
,
2774 def add_branch_argument(subparser
):
2775 subcommand
= subparser
.prog
.split()[1]
2776 help = 'the name of the branch to which the result will be stored'
2777 if subcommand
in ['simplify', 'finish']:
2779 'the name of the branch to which to store the result '
2780 '(default is the value provided to "init" or "start" if any; '
2781 'otherwise the name of the merge). '
2782 'If BRANCH already exists then it must be able to be '
2783 'fast-forwarded to the result unless the --force option is '
2786 subparser
.add_argument(
2788 action
='store', default
=None,
2792 def add_manual_argument(subparser
):
2793 subparser
.add_argument(
2795 action
='store_true', default
=False,
2797 'ask the user to complete all merges manually, even when they '
2798 'appear conflict-free. This option disables the usual bisection '
2799 'algorithm and causes the full incremental merge diagram to be '
2804 def add_first_parent_argument(subparser
, default
=None):
2805 subcommand
= subparser
.prog
.split()[1]
2807 'handle only the first parent commits '
2808 '(this option is currently required)'
2810 if subcommand
in ['merge', 'rebase']:
2811 help = argparse
.SUPPRESS
2812 subparser
.add_argument(
2813 '--first-parent', action
='store_true', default
=default
, help=help,
2816 def add_tip2_argument(subparser
):
2817 subparser
.add_argument(
2818 'tip2', action
='store', metavar
='branch',
2819 help='the tip of the branch to be merged into HEAD',
2822 parser
= argparse
.ArgumentParser(
2823 description
=__doc__
,
2824 formatter_class
=argparse
.RawDescriptionHelpFormatter
,
2826 subparsers
= parser
.add_subparsers(dest
='subcommand', help='sub-command')
2828 subparser
= subparsers
.add_parser(
2831 'start a new incremental merge '
2832 '(equivalent to "init" followed by "continue")'
2835 add_name_argument(subparser
, help=NAME_INIT_HELP
)
2836 add_goal_argument(subparser
)
2837 add_branch_argument(subparser
)
2838 add_manual_argument(subparser
)
2839 add_first_parent_argument(subparser
)
2840 add_tip2_argument(subparser
)
2842 subparser
= subparsers
.add_parser(
2844 help='start a simple merge via incremental merge',
2846 add_name_argument(subparser
, help=NAME_INIT_HELP
)
2847 add_goal_argument(subparser
, default
='merge')
2848 add_branch_argument(subparser
)
2849 add_manual_argument(subparser
)
2850 add_first_parent_argument(subparser
, default
=True)
2851 add_tip2_argument(subparser
)
2853 subparser
= subparsers
.add_parser(
2855 help='start a simple rebase via incremental merge',
2857 add_name_argument(subparser
, help=NAME_INIT_HELP
)
2858 add_goal_argument(subparser
, default
='rebase')
2859 add_branch_argument(subparser
)
2860 add_manual_argument(subparser
)
2861 add_first_parent_argument(subparser
, default
=True)
2862 subparser
.add_argument(
2863 'tip1', action
='store', metavar
='branch',
2865 'the tip of the branch onto which the current branch should '
2870 subparser
= subparsers
.add_parser(
2873 'record the merge at branch imerge/NAME '
2874 'and start the next step of the merge '
2875 '(equivalent to "record" followed by "autofill" '
2876 'and then sets up the working copy with the next '
2877 'conflict that has to be resolved manually)'
2880 add_name_argument(subparser
)
2881 subparser
.set_defaults(edit
=None)
2882 subparser
.add_argument(
2883 '--edit', '-e', dest
='edit', action
='store_true',
2884 help='commit staged changes with the --edit option',
2886 subparser
.add_argument(
2887 '--no-edit', dest
='edit', action
='store_false',
2888 help='commit staged changes with the --no-edit option',
2891 subparser
= subparsers
.add_parser(
2894 'simplify then remove a completed incremental merge '
2895 '(equivalent to "simplify" followed by "remove")'
2898 add_name_argument(subparser
)
2899 add_goal_argument(subparser
, default
=None)
2900 add_branch_argument(subparser
)
2901 subparser
.add_argument(
2903 action
='store_true', default
=False,
2904 help='allow the target branch to be updated in a non-fast-forward manner',
2907 subparser
= subparsers
.add_parser(
2909 help='display a diagram of the current state of a merge',
2911 add_name_argument(subparser
)
2912 subparser
.add_argument(
2913 '--commits', action
='store_true', default
=False,
2914 help='show the merges that have been made so far',
2916 subparser
.add_argument(
2917 '--frontier', action
='store_true', default
=False,
2918 help='show the current merge frontier',
2920 subparser
.add_argument(
2921 '--html', action
='store', default
=None,
2922 help='generate HTML diagram showing the current merge frontier',
2924 subparser
.add_argument(
2925 '--color', dest
='color', action
='store_true', default
=None,
2926 help='draw diagram with colors',
2928 subparser
.add_argument(
2929 '--no-color', dest
='color', action
='store_false',
2930 help='draw diagram without colors',
2933 subparser
= subparsers
.add_parser(
2936 'list the names of incremental merges that are currently in progress. '
2937 'The active merge is shown with an asterisk next to it.'
2941 subparser
= subparsers
.add_parser(
2943 help='initialize a new incremental merge',
2945 add_name_argument(subparser
, help=NAME_INIT_HELP
)
2946 add_goal_argument(subparser
)
2947 add_branch_argument(subparser
)
2948 add_manual_argument(subparser
)
2949 add_first_parent_argument(subparser
)
2950 add_tip2_argument(subparser
)
2952 subparser
= subparsers
.add_parser(
2954 help='record the merge at branch imerge/NAME',
2959 help='name of merge to which the merge should be added',
2961 subparser
.set_defaults(edit
=None)
2962 subparser
.add_argument(
2963 '--edit', '-e', dest
='edit', action
='store_true',
2964 help='commit staged changes with the --edit option',
2966 subparser
.add_argument(
2967 '--no-edit', dest
='edit', action
='store_false',
2968 help='commit staged changes with the --no-edit option',
2971 subparser
= subparsers
.add_parser(
2973 help='autofill non-conflicting merges',
2975 add_name_argument(subparser
)
2977 subparser
= subparsers
.add_parser(
2980 'simplify a completed incremental merge by discarding unneeded '
2981 'intermediate merges and cleaning up the ancestry of the commits '
2985 add_name_argument(subparser
)
2986 add_goal_argument(subparser
, default
=None)
2987 add_branch_argument(subparser
)
2988 subparser
.add_argument(
2990 action
='store_true', default
=False,
2991 help='allow the target branch to be updated in a non-fast-forward manner',
2994 subparser
= subparsers
.add_parser(
2996 help='irrevocably remove an incremental merge',
2998 add_name_argument(subparser
)
3000 subparser
= subparsers
.add_parser(
3002 help='change the parents of the HEAD commit',
3004 subparser
.add_argument(
3005 'parents', nargs
='*', help='[PARENT...]',
3008 options
= parser
.parse_args(args
)
3010 # Set an environment variable GIT_IMERGE=1 while we are running.
3011 # This makes it possible for hook scripts etc. to know that they
3012 # are being run within git-imerge, and should perhaps behave
3013 # differently. In the future we might make the value more
3014 # informative, like GIT_IMERGE=[automerge|autofill|...].
3015 os
.environ
[str('GIT_IMERGE')] = str('1')
3017 if options
.subcommand
== 'list':
3018 default_merge
= MergeState
.get_default_name()
3019 for name
in MergeState
.iter_existing_names():
3020 if name
== default_merge
:
3021 sys
.stdout
.write('* %s\n' % (name
,))
3023 sys
.stdout
.write(' %s\n' % (name
,))
3024 elif options
.subcommand
== 'init':
3025 require_clean_work_tree('proceed')
3027 if not options
.first_parent
:
3029 'The --first-parent option is currently required for the "init" command'
3031 if not options
.name
:
3033 'Please specify the --name to be used for this incremental merge'
3036 tip1
= check_output(
3037 ['git', 'symbolic-ref', '--quiet', '--short', 'HEAD'],
3039 except CalledProcessError
:
3042 (merge_base
, commits1
, commits2
) = get_boundaries(tip1
, tip2
)
3043 merge_state
= MergeState
.initialize(
3044 options
.name
, merge_base
,
3047 goal
=options
.goal
, manual
=options
.manual
,
3048 branch
=(options
.branch
or options
.name
),
3051 MergeState
.set_default_name(options
.name
)
3052 elif options
.subcommand
== 'start':
3053 require_clean_work_tree('proceed')
3055 if not options
.first_parent
:
3057 'The --first-parent option is currently required for the "start" command'
3059 if not options
.name
:
3061 'Please specify the --name to be used for this incremental merge'
3064 tip1
= check_output(
3065 ['git', 'symbolic-ref', '--quiet', '--short', 'HEAD'],
3067 except CalledProcessError
:
3070 (merge_base
, commits1
, commits2
) = get_boundaries(tip1
, tip2
)
3071 merge_state
= MergeState
.initialize(
3072 options
.name
, merge_base
,
3075 goal
=options
.goal
, manual
=options
.manual
,
3076 branch
=(options
.branch
or options
.name
),
3079 MergeState
.set_default_name(options
.name
)
3082 merge_state
.auto_complete_frontier()
3083 except FrontierBlockedError
as e
:
3084 request_user_merge(merge_state
, e
.i1
, e
.i2
)
3086 sys
.stderr
.write('Merge is complete!\n')
3087 elif options
.subcommand
== 'merge':
3088 require_clean_work_tree('proceed')
3090 if not options
.first_parent
:
3092 'The --first-parent option is currently required for the "merge" command'
3100 # By default, name the imerge after the branch being merged:
3102 MergeState
.check_name_format(name
)
3105 tip1
= check_output(
3106 ['git', 'symbolic-ref', '--quiet', '--short', 'HEAD'],
3108 if not options
.branch
:
3109 # See if we can store the result to the checked-out branch:
3111 check_branch_name_format(tip1
)
3112 except InvalidBranchNameError
:
3115 options
.branch
= tip1
3116 except CalledProcessError
:
3119 if not options
.branch
:
3121 options
.branch
= options
.name
3124 'HEAD is not a simple branch. '
3125 'Please specify --branch for storing results.'
3129 (merge_base
, commits1
, commits2
) = get_boundaries(tip1
, tip2
)
3130 except NothingToDoError
as e
:
3131 sys
.stdout
.write('Already up-to-date.\n')
3133 merge_state
= MergeState
.initialize(
3137 goal
=options
.goal
, manual
=options
.manual
,
3138 branch
=options
.branch
,
3141 MergeState
.set_default_name(name
)
3144 merge_state
.auto_complete_frontier()
3145 except FrontierBlockedError
as e
:
3146 request_user_merge(merge_state
, e
.i1
, e
.i2
)
3148 sys
.stderr
.write('Merge is complete!\n')
3149 elif options
.subcommand
== 'rebase':
3150 require_clean_work_tree('proceed')
3152 if not options
.first_parent
:
3154 'The --first-parent option is currently required for the "rebase" command'
3160 tip2
= check_output(
3161 ['git', 'symbolic-ref', '--quiet', '--short', 'HEAD'],
3163 if not options
.branch
:
3164 # See if we can store the result to the current branch:
3166 check_branch_name_format(tip2
)
3167 except InvalidBranchNameError
:
3170 options
.branch
= tip2
3171 if not options
.name
:
3172 # By default, name the imerge after the branch being rebased:
3175 except CalledProcessError
:
3176 tip2
= rev_parse('HEAD')
3178 if not options
.name
:
3180 'The checked-out branch could not be used as the imerge name.\n'
3181 'Please use the --name option.'
3184 if not options
.branch
:
3186 options
.branch
= options
.name
3189 'HEAD is not a simple branch. '
3190 'Please specify --branch for storing results.'
3194 (merge_base
, commits1
, commits2
) = get_boundaries(tip1
, tip2
)
3195 except NothingToDoError
as e
:
3196 sys
.stdout
.write('Already up-to-date.\n')
3199 merge_state
= MergeState
.initialize(
3200 options
.name
, merge_base
,
3203 goal
=options
.goal
, manual
=options
.manual
,
3204 branch
=options
.branch
,
3207 MergeState
.set_default_name(options
.name
)
3210 merge_state
.auto_complete_frontier()
3211 except FrontierBlockedError
as e
:
3212 request_user_merge(merge_state
, e
.i1
, e
.i2
)
3214 sys
.stderr
.write('Merge is complete!\n')
3215 elif options
.subcommand
== 'remove':
3216 MergeState
.remove(choose_merge_name(options
.name
, default_to_unique
=False))
3217 elif options
.subcommand
== 'continue':
3218 merge_state
= read_merge_state(options
.name
)
3220 incorporate_user_merge(merge_state
, edit_log_msg
=options
.edit
)
3221 except NoManualMergeError
:
3223 except NotABlockingCommitError
as e
:
3224 raise Failure(str(e
))
3225 except ManualMergeUnusableError
as e
:
3226 raise Failure(str(e
))
3229 merge_state
.auto_complete_frontier()
3230 except FrontierBlockedError
as e
:
3231 request_user_merge(merge_state
, e
.i1
, e
.i2
)
3233 sys
.stderr
.write('Merge is complete!\n')
3234 elif options
.subcommand
== 'record':
3235 merge_state
= read_merge_state(options
.name
)
3237 incorporate_user_merge(merge_state
, edit_log_msg
=options
.edit
)
3238 except NoManualMergeError
as e
:
3239 raise Failure(str(e
))
3240 except NotABlockingCommitError
:
3241 raise Failure(str(e
))
3242 except ManualMergeUnusableError
as e
:
3243 raise Failure(str(e
))
3246 merge_state
.auto_complete_frontier()
3247 except FrontierBlockedError
as e
:
3250 sys
.stderr
.write('Merge is complete!\n')
3251 elif options
.subcommand
== 'autofill':
3252 require_clean_work_tree('proceed')
3253 merge_state
= read_merge_state(options
.name
)
3254 with
TemporaryHead():
3256 merge_state
.auto_complete_frontier()
3257 except FrontierBlockedError
as e
:
3258 raise Failure(str(e
))
3259 elif options
.subcommand
== 'simplify':
3260 require_clean_work_tree('proceed')
3261 merge_state
= read_merge_state(options
.name
)
3262 merge_frontier
= MergeFrontier
.map_known_frontier(merge_state
)
3263 if not merge_frontier
.is_complete():
3264 raise Failure('Merge %s is not yet complete!' % (merge_state
.name
,))
3265 refname
= 'refs/heads/%s' % ((options
.branch
or merge_state
.branch
),)
3266 if options
.goal
is not None:
3267 merge_state
.set_goal(options
.goal
)
3269 merge_state
.simplify(refname
, force
=options
.force
)
3270 elif options
.subcommand
== 'finish':
3271 require_clean_work_tree('proceed')
3272 merge_state
= read_merge_state(options
.name
, default_to_unique
=False)
3273 merge_frontier
= MergeFrontier
.map_known_frontier(merge_state
)
3274 if not merge_frontier
.is_complete():
3275 raise Failure('Merge %s is not yet complete!' % (merge_state
.name
,))
3276 refname
= 'refs/heads/%s' % ((options
.branch
or merge_state
.branch
),)
3277 if options
.goal
is not None:
3278 merge_state
.set_goal(options
.goal
)
3280 merge_state
.simplify(refname
, force
=options
.force
)
3281 MergeState
.remove(merge_state
.name
)
3282 elif options
.subcommand
== 'diagram':
3283 if not (options
.commits
or options
.frontier
):
3284 options
.frontier
= True
3285 if not (options
.color
or (options
.color
is None and sys
.stdout
.isatty())):
3288 merge_state
= read_merge_state(options
.name
)
3290 merge_state
.write(sys
.stdout
)
3291 sys
.stdout
.write('\n')
3292 if options
.frontier
:
3293 merge_frontier
= MergeFrontier
.map_known_frontier(merge_state
)
3294 merge_frontier
.write(sys
.stdout
)
3295 sys
.stdout
.write('\n')
3297 merge_frontier
= MergeFrontier
.map_known_frontier(merge_state
)
3298 html
= open(options
.html
, 'w')
3299 merge_frontier
.write_html(html
, merge_state
.name
)
3304 if options
.frontier
:
3306 ' |,-,+ = rectangles forming current merge frontier\n'
3309 ' * = merge done manually\n'
3310 ' . = merge done automatically\n'
3311 ' # = conflict that is currently blocking progress\n'
3312 ' @ = merge was blocked but has been resolved\n'
3313 ' ? = no merge recorded\n'
3316 elif options
.subcommand
== 'reparent':
3318 commit_sha1
= get_commit_sha1('HEAD')
3320 sys
.exit('HEAD is not a valid commit')
3323 parent_sha1s
= [get_commit_sha1(p
) for p
in options
.parents
]
3324 except ValueError as e
:
3327 sys
.stdout
.write('%s\n' % (reparent(commit_sha1
, parent_sha1s
),))
3329 parser
.error('Unrecognized subcommand')
3334 # vim: set expandtab ft=python: