mktar: Use `wc` instead of `du` in summary message
[sunny256-utils.git] / git-imerge
blobc0809eb4b19cf565dde51127c3008d5d2723cda0
1 #! /usr/bin/env python
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
26 resolution.
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.
36 Instructions:
38 To start an incremental merge or rebase, use one of the following
39 commands:
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
48 git add FILE...
49 git-imerge continue
51 You can view your progress at any time with
53 git-imerge diagram
55 When you have resolved all of the conflicts, simplify and record the
56 result by typing
58 git-imerge finish
60 To get more help about any git-imerge subcommand, type
62 git-imerge SUBCOMMAND --help
64 """
66 from __future__ import absolute_import
67 from __future__ import division
68 from __future__ import print_function
69 from __future__ import unicode_literals
71 import locale
72 import sys
73 import re
74 import subprocess
75 from subprocess import CalledProcessError
76 from subprocess import check_call
77 import itertools
78 import functools
79 import argparse
80 from io import StringIO
81 import json
82 import os
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()
96 if retcode:
97 cmd = kwargs.get("args")
98 if cmd is None:
99 cmd = popenargs[0]
100 # We don't store output in the CalledProcessError because
101 # the "output" keyword parameter was not supported in
102 # Python 2.6:
103 raise CalledProcessError(retcode, cmd)
104 return output
107 STATE_VERSION = (1, 3, 0)
109 ZEROS = '0' * 40
111 ALLOWED_GOALS = [
112 'full',
113 'rebase-with-history',
114 'rebase',
115 'merge',
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."""
126 @classmethod
127 def wrap(klass, f):
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."""
133 @functools.wraps(f)
134 def wrapper(*args, **kw):
135 try:
136 return f(*args, **kw)
137 except klass as e:
138 sys.exit(str(e))
140 return wrapper
142 pass
145 class AnsiColor:
146 BLACK = '\033[0;30m'
147 RED = '\033[0;31m'
148 GREEN = '\033[0;32m'
149 YELLOW = '\033[0;33m'
150 BLUE = '\033[0;34m'
151 MAGENTA = '\033[0;35m'
152 CYAN = '\033[0;36m'
153 B_GRAY = '\033[0;37m'
154 D_GRAY = '\033[1;30m'
155 B_RED = '\033[1;31m'
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'
161 WHITE = '\033[1;37m'
162 END = '\033[0m'
164 @classmethod
165 def disable(cls):
166 cls.BLACK = ''
167 cls.RED = ''
168 cls.GREEN = ''
169 cls.YELLOW = ''
170 cls.BLUE = ''
171 cls.MAGENTA = ''
172 cls.CYAN = ''
173 cls.B_GRAY = ''
174 cls.D_GRAY = ''
175 cls.B_RED = ''
176 cls.B_GREEN = ''
177 cls.B_YELLOW = ''
178 cls.B_BLUE = ''
179 cls.B_MAGENTA = ''
180 cls.B_CYAN = ''
181 cls.WHITE = ''
182 cls.END = ''
185 def iter_neighbors(iterable):
186 """For an iterable (x0, x1, x2, ...) generate [(x0,x1), (x1,x2), ...]."""
188 i = iter(iterable)
190 try:
191 last = next(i)
192 except StopIteration:
193 return
195 for x in i:
196 yield (last, x)
197 last = x
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
208 # for i >= hi.
210 while lo < hi:
211 mid = (lo + hi) // 2
212 if f(mid):
213 lo = mid + 1
214 else:
215 hi = mid
217 return lo
220 def call_silently(cmd):
221 try:
222 NULL = open(os.devnull, 'w')
223 except (IOError, AttributeError):
224 NULL = subprocess.PIPE
226 p = subprocess.Popen(cmd, stdout=NULL, stderr=NULL)
227 p.communicate()
228 retcode = p.wait()
229 if retcode:
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
248 # strings:
249 def env_encode(s):
250 """Encode unicode keys or values for use in os.environ."""
252 return s.encode(PREFERRED_ENCODING)
254 else:
255 # In Python 3.x, os.environ keys and values must be unicode
256 # strings:
257 def env_encode(s):
258 """Use unicode keys or values unchanged in os.environ."""
260 return s
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
273 '--no-edit'.
277 try:
278 return {'true' : True, 'false' : False}[
279 check_output(
280 ['git', 'config', '--bool', 'imerge.editmergemessages']
281 ).rstrip()
283 except CalledProcessError:
284 return False
287 class UncleanWorkTreeError(Failure):
288 pass
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
295 of the same name."""
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()
303 if retcode:
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()
312 if retcode:
313 raise UncleanWorkTreeError(err.rstrip() or out.rstrip())
315 error = []
316 try:
317 check_call(['git', 'diff-files', '--quiet', '--ignore-submodules'])
318 except CalledProcessError:
319 error.append('Cannot %s: You have unstaged changes.' % (action,))
321 try:
322 check_call([
323 'git', 'diff-index', '--cached', '--quiet',
324 '--ignore-submodules', 'HEAD', '--',
326 except CalledProcessError:
327 if not error:
328 error.append('Cannot %s: Your index contains uncommitted changes.' % (action,))
329 else:
330 error.append('Additionally, your index contains uncommitted changes.')
332 if error:
333 raise UncleanWorkTreeError('\n'.join(error))
336 class InvalidBranchNameError(Failure):
337 pass
340 def check_branch_name_format(name):
341 """Check that name is a valid branch name."""
343 try:
344 call_silently(
345 ['git', 'check-ref-format', 'refs/heads/%s' % (name,)]
347 except CalledProcessError:
348 raise InvalidBranchNameError('Name %r is not a valid branch name!' % (name,))
351 def rev_parse(arg):
352 return check_output(['git', 'rev-parse', '--verify', '--quiet', arg]).strip()
355 def rev_list(*args):
356 return [
357 l.strip()
358 for l in check_output(['git', 'rev-list'] + list(args),).splitlines()
362 def get_type(arg):
363 """Return the type of a git object ('commit', 'tree', 'blob', or 'tag')."""
365 return check_output(['git', 'cat-file', '-t', arg]).strip()
368 def get_tree(arg):
369 return rev_parse('%s^{tree}' % (arg,))
372 BRANCH_PREFIX = 'refs/heads/'
375 def checkout(refname, quiet=False):
376 cmd = ['git', 'checkout']
377 if quiet:
378 cmd += ['--quiet']
379 if refname.startswith(BRANCH_PREFIX):
380 target = refname[len(BRANCH_PREFIX):]
381 else:
382 target = '%s^0' % (refname,)
383 cmd += [target]
384 check_call(cmd)
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."""
392 try:
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."""
401 return check_output(
402 ['git', '--no-pager', 'log', '--no-walk', '--pretty=format:%P', commit]
403 ).strip().split()
406 def get_log_message(commit):
407 contents = check_output([
408 'git', 'cat-file', 'commit', commit,
409 ]).splitlines(True)
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
425 a = check_output([
426 'git', '--no-pager', 'log', '-n1',
427 '--format=%an%n%ae%n%ai', commit
428 ]).strip().splitlines()
430 return {
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()
451 env.update(metadata)
452 else:
453 env = os.environ
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()
461 if retcode:
462 # We don't store the output in the CalledProcessError because
463 # the "output" keyword parameter was not supported in Python
464 # 2.6:
465 raise CalledProcessError(retcode, cmd)
467 return out.strip()
470 class NothingToDoError(Failure):
471 def __init__(self, src_tip, dst_tip):
472 Failure.__init__(
473 self,
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'
489 order."""
491 ancestry_path = set(rev_list('--ancestry-path', '%s..%s' % (commit1, commit2)))
492 commits = [
493 sha1
494 for sha1 in rev_list('--first-parent', '%s..%s' % (commit1, commit2))
495 if sha1 in ancestry_path
497 commits.reverse()
498 return commits
501 def compute_best_merge_base(tip1, tip2):
502 try:
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))
506 if not merge_bases:
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
515 # computation.)
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
522 best_count = count
524 return best_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)
537 if not commits1:
538 raise NothingToDoError(tip1, tip2)
540 commits2 = linear_ancestry(merge_base, tip2)
541 if not commits2:
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
554 try:
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')
559 return self
561 def __exit__(self, exc_type, exc_val, exc_tb):
562 if self.head_name:
563 try:
564 check_call([
565 'git', 'symbolic-ref',
566 '-m', self.message, 'HEAD',
567 self.head_name,
569 except Exception as e:
570 raise Failure(
571 'Could not restore HEAD to %r!: %s\n'
572 % (self.head_name, e.message,)
574 else:
575 try:
576 check_call(['git', 'reset', '--hard', self.orig_head])
577 except Exception as e:
578 raise Failure(
579 'Could not restore HEAD to %r!: %s\n'
580 % (self.orig_head, e.message,)
582 return False
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
595 commit."""
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)):
604 line = headers[i]
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:
611 pass
612 else:
613 new_commit.write(line)
615 new_commit.write('\n')
616 if msg is None:
617 new_commit.write(rest)
618 else:
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()
629 if retcode:
630 raise Failure('Could not reparent commit %s' % (commit,))
631 return out.strip()
634 class AutomaticMergeFailed(Exception):
635 def __init__(self, commit1, commit2):
636 Exception.__init__(
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
647 worktree."""
649 call_silently(['git', 'checkout', '-f', commit1])
650 cmd = ['git', '-c', 'rerere.enabled=false', 'merge']
651 if msg is not None:
652 cmd += ['-m', msg]
653 cmd += [commit2]
654 try:
655 call_silently(cmd)
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)
661 else:
662 return get_commit_sha1('HEAD')
665 class MergeRecord(object):
666 # Bits for the flags field:
668 # There is a saved successful auto merge:
669 SAVED_AUTO = 0x01
671 # An auto merge (which may have been unsuccessful) has been done:
672 NEW_AUTO = 0x02
674 # There is a saved successful manual merge:
675 SAVED_MANUAL = 0x04
677 # A manual merge (which may have been unsuccessful) has been done:
678 NEW_MANUAL = 0x08
680 # A merge that is currently blocking the merge frontier:
681 BLOCKED = 0x10
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 = [
691 SAVED_AUTO,
692 SAVED_MANUAL,
693 NEW_AUTO,
694 NEW_MANUAL,
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.
700 self.sha1 = sha1
702 if self.sha1 is None:
703 if flags != 0:
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,))
708 # See bits above.
709 self.flags = 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:
720 self.sha1 = sha1
721 self.flags |= source
722 elif source == self.NEW_AUTO:
723 # NEW_AUTO is silently ignored if any MANUAL value is
724 # already recorded.
725 if self.flags & self.MANUAL == 0:
726 self.sha1 = sha1
727 self.flags |= source
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:
732 self.sha1 = sha1
733 self.flags |= source
734 elif source == self.NEW_MANUAL:
735 # NEW_MANUAL is always used, and also causes NEW_AUTO to
736 # be forgotten if present.
737 self.sha1 = sha1
738 self.flags = (self.flags | source) & ~self.NEW_AUTO
739 else:
740 raise ValueError('Undefined source: %s' % (source,))
742 def record_blocked(self, blocked):
743 if blocked:
744 self.flags |= self.BLOCKED
745 else:
746 self.flags &= ~self.BLOCKED
748 def is_known(self):
749 return self.sha1 is not None
751 def is_blocked(self):
752 return self.flags & self.BLOCKED != 0
754 def is_manual(self):
755 return self.flags & self.MANUAL != 0
757 def save(self, name, i1, i2):
758 """If this record has changed, save it."""
760 def set_ref(source):
761 check_call([
762 'git', 'update-ref',
763 '-m', 'imerge %r: Record %s merge' % (name, source,),
764 'refs/imerge/%s/%s/%d-%d' % (name, source, i1, i2),
765 self.sha1,
768 def clear_ref(source):
769 check_call([
770 'git', 'update-ref',
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:
779 clear_ref('auto')
781 self.flags &= ~self.AUTO
783 if self.flags & self.NEW_MANUAL:
784 # Convert NEW_MANUAL to SAVED_MANUAL.
785 if self.sha1:
786 set_ref('manual')
787 self.flags |= self.SAVED_MANUAL
788 elif self.flags & self.SAVED_MANUAL:
789 # Delete any existing SAVED_MANUAL:
790 clear_ref('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.
796 if self.sha1:
797 set_ref('auto')
798 self.flags |= self.SAVED_AUTO
799 elif self.flags & self.SAVED_AUTO:
800 # Delete any existing SAVED_AUTO:
801 clear_ref('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):
813 pass
816 class FrontierBlockedError(Exception):
817 def __init__(self, msg, i1, i2):
818 Exception.__init__(self, msg)
819 self.i1 = i1
820 self.i2 = i2
823 class NotABlockingCommitError(Exception):
824 pass
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
832 normalized form:
834 * Only non-empty blocks are retained
836 * Blocks are sorted from bottom left to upper right
838 * No redundant blocks
842 @staticmethod
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
848 (0,0).
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
856 # end:
858 # +++++++
859 # +?+++++
860 # +?+++++
861 # +?+++++
862 # +?*++++
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
875 # downwards.
876 path = []
878 def create_frontier(path):
879 blocks = []
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)
885 # Loop invariants:
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
892 # been attempted
893 (i1, i2) = (block.len1 - 1, 0)
894 down = True
895 while True:
896 if down:
897 if i2 == block.len2 - 1:
898 # Hit edge of block; can't move down:
899 down = False
900 elif (i1, i2 + 1) in block and not block.is_blocked(i1, i2 + 1):
901 # Can move down
902 path.append((i1, i2, True))
903 i2 += 1
904 else:
905 # Can't move down.
906 down = False
907 else:
908 if i1 == 0:
909 # Success!
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):
913 # Can move left
914 path.append((i1, i2, False))
915 down = True
916 i1 -= 1
917 else:
918 # There's no way to go forward; backtrack until we
919 # find a place where we can still try going left:
920 while True:
921 try:
922 (i1, i2, down) = path.pop()
923 except IndexError:
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!')
928 if down:
929 down = False
930 break
932 @staticmethod
933 def compute_by_bisection(block):
934 """Return a MergeFrontier instance for block.
936 We make the following assumptions (using Python subscript
937 notation):
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
964 # left.
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
976 # fact:
977 block[1, 1].record_blocked(True)
978 return MergeFrontier(block, [])
980 blocks = []
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
984 # block in column 1:
985 i1 = 1
986 i2 = find_first_false(
987 lambda i: block.is_mergeable(i1, i),
988 2, block.len2,
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
995 # not mergeable:
997 # i1
999 # 0123456
1000 # 0 *******
1001 # 1 **?????
1002 # i2 2 **?????
1003 # 3 **?????
1004 # 4 *Axxxxx
1005 # 5 *xxxxxx
1008 while True:
1009 if i2 == 1:
1010 break
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:
1017 # i1
1019 # 0123456
1020 # 0 **|****
1021 # 1 **|*???
1022 # i2 2 **|*???
1023 # 3 **|Axxx
1024 # 4 --+xxxx
1025 # 5 *xxxxxx
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.
1032 if (
1033 i1 == block.len1 - 1
1034 or block.is_mergeable(block.len1 - 1, i2 - 1)
1036 blocks.append(block[:block.len1, :i2])
1037 break
1038 else:
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:
1048 # i1
1050 # 0123456
1051 # 0 **|*|**
1052 # 1 **|*|??
1053 # i2 2 --+-+xx
1054 # 3 **|xxAx
1055 # 4 --+xxxx
1056 # 5 *xxxxxx
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):
1062 break
1063 else:
1064 i2 = find_first_false(
1065 lambda i: block.is_mergeable(i1, i),
1066 2, i2 - 1,
1069 return MergeFrontier(block, blocks)
1071 def __init__(self, block, blocks=None):
1072 self.block = block
1073 self.blocks = self._normalized_blocks(blocks or [])
1075 def __iter__(self):
1076 """Iterate over blocks from bottom left to upper right."""
1078 return iter(self.blocks)
1080 def __bool__(self):
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."""
1093 return (
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
1105 @classmethod
1106 def default_formatter(cls, node, legend=None):
1107 def color(node, within):
1108 if within:
1109 return AnsiColor.B_GREEN + node + AnsiColor.END
1110 else:
1111 return AnsiColor.B_RED + node + AnsiColor.END
1113 if legend is None:
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()
1142 try:
1143 next_block = self.blocks[0]
1144 except IndexError:
1145 next_block = None
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
1152 prev_block = None
1153 for n in range(len(self.blocks)):
1154 block = self.blocks[n]
1155 try:
1156 next_block = self.blocks[n + 1]
1157 except IndexError:
1158 next_block = None
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
1172 prev_block = block
1174 try:
1175 prev_block = self.blocks[-1]
1176 except IndexError:
1177 prev_block = None
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
1184 return diagram
1186 def format_diagram(self, formatter=None, diagram=None):
1187 if formatter is None:
1188 formatter = self.default_formatter
1189 if diagram is None:
1190 diagram = self.create_diagram()
1191 return [
1192 [formatter(diagram[i1][i2]) for i2 in range(self.block.len2)]
1193 for i1 in range(self.block.len1)]
1195 def write(self, f):
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])
1201 f.write('\n')
1203 def write_html(self, f, name, cssfile='imerge.css', abbrev_sha1=7):
1204 class_map = {
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]:
1220 if node & bit:
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')
1228 if i1 == 0:
1229 ret.append('col_left')
1230 if i1 == self.block.len1 - 1:
1231 ret.append('col_right')
1232 if i2 == 0:
1233 ret.append('row_top')
1234 if i2 == self.block.len2 - 1:
1235 ret.append('row_bottom')
1236 return ret
1238 f.write("""\
1239 <html>
1240 <head>
1241 <title>git-imerge: %s</title>
1242 <link rel="stylesheet" href="%s" type="text/css" />
1243 </head>
1244 <body>
1245 <table id="imerge">
1246 """ % (name, cssfile))
1248 diagram = self.create_diagram()
1250 f.write(' <tr>\n')
1251 f.write(' <th class="indexes">&nbsp;</td>\n')
1252 for i1 in range(self.block.len1):
1253 f.write(' <th class="indexes">%d-*</td>\n' % (i1,))
1254 f.write(' </tr>\n')
1256 for i2 in range(self.block.len2):
1257 f.write(' <tr>\n')
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))
1267 f.write(' </tr>\n')
1268 f.write('</table>\n</body>\n</html>\n')
1270 @staticmethod
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)
1288 ret = []
1290 for block in blocks:
1291 if block.len1 == 0 or block.len2 == 0:
1292 continue
1293 while True:
1294 if not ret:
1295 ret.append(block)
1296 break
1298 last = ret[-1]
1299 if contains(last, block):
1300 break
1301 elif contains(block, last):
1302 ret.pop()
1303 else:
1304 ret.append(block)
1305 break
1307 return ret
1309 def remove_failure(self, i1, i2):
1310 """Refine the merge frontier given that the specified merge fails."""
1312 newblocks = []
1313 shrunk_block = False
1315 for block in self.blocks:
1316 if i1 < block.len1 and i2 < block.len2:
1317 if i1 > 1:
1318 newblocks.append(block[:i1, :])
1319 if i2 > 1:
1320 newblocks.append(block[:, :i2])
1321 shrunk_block = True
1322 else:
1323 newblocks.append(block)
1325 if shrunk_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
1335 outlined."""
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.
1341 left = []
1342 right = []
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.
1346 pass
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:, :])
1351 else:
1352 raise ValueError(
1353 'MergeFrontier partitioned with inappropriate block'
1355 return (
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."""
1365 if not self:
1366 yield self.block
1367 return
1369 blockruns = []
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():
1385 try:
1386 (block_i1, block_i2) = block.convert_original_indexes(i1, i2)
1387 except IndexError:
1388 pass
1389 else:
1390 if (block_i1, block_i2) == (1, 1):
1391 # That's the one we need to improve this block:
1392 return block
1393 else:
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)
1400 else:
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
1413 scratch."""
1415 blocks = list(self.iter_blocker_blocks())
1416 if not 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():
1424 return
1425 else:
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),
1432 i1, i2
1436 class NoManualMergeError(Exception):
1437 pass
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):
1448 Exception.__init__(
1449 self,
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
1460 maybe implicitly).
1462 Members:
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):
1471 self.name = name
1472 self.len1 = len1
1473 self.len2 = len2
1475 def get_merge_state(self):
1476 """Return the MergeState instance containing this Block."""
1478 raise NotImplementedError()
1480 def get_area(self):
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)."""
1494 try:
1495 (i1, i2) = index
1496 except TypeError:
1497 raise IndexError('Block indexing requires exactly two indexes')
1499 if i1 < 0:
1500 i1 += self.len1
1501 if i2 < 0:
1502 i2 += self.len2
1504 self._check_indexes(i1, i2)
1505 return (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."""
1520 return (i1, i2)
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."""
1542 try:
1543 (i1, i2) = index
1544 except TypeError:
1545 raise IndexError('Block indexing requires exactly two indexes')
1546 if isinstance(i1, slice) or isinstance(i2, slice):
1547 return SubBlock(self, i1, i2)
1548 else:
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:
1568 return True
1569 else:
1570 sys.stderr.write(
1571 'Attempting automerge of %d-%d...' % self.get_original_indexes(i1, i2)
1573 try:
1574 automerge(self[i1, 0].sha1, self[0, i2].sha1)
1575 sys.stderr.write('success.\n')
1576 return True
1577 except AutomaticMergeFailed:
1578 sys.stderr.write('failure.\n')
1579 return False
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.
1588 merges = []
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)
1596 try:
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)
1602 if record:
1603 merges.append((i1, i2, merge))
1604 return merge
1606 i2 = self.len2 - 1
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)
1611 i1 = self.len1 - 1
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)...',
1625 record=False,
1627 vertex_v2 = do_merge(
1628 i1, above, i2, self[0, i2].sha1,
1629 msg='Autofilling %d-%d (second way)...',
1630 record=False,
1632 if get_tree(vertex_v1) == get_tree(vertex_v2):
1633 sys.stderr.write(
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])))
1640 else:
1641 sys.stderr.write(
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)
1646 else:
1647 do_merge(
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):
1664 return False
1666 i1, i2 = 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)
1670 try:
1671 merge = automerge(
1672 self[i1, i2 - 1].sha1,
1673 self[i1 - 1, i2].sha1,
1674 msg=logmsg,
1676 sys.stderr.write('success.\n')
1677 except AutomaticMergeFailed:
1678 sys.stderr.write('conflict.\n')
1679 self[i1, i2].record_blocked(True)
1680 return False
1681 else:
1682 self[i1, i2].record_merge(merge, MergeRecord.NEW_AUTO)
1683 return True
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:
1694 # Nothing to do.
1695 return False
1697 best_block = max(merge_frontier, key=lambda block: block.get_original_indexes(0, 0))
1699 try:
1700 best_block.auto_outline()
1701 except UnexpectedMergeFailure as e:
1702 # One of the merges that we expected to succeed in
1703 # fact failed.
1704 merge_frontier.remove_failure(e.i1, e.i2)
1705 return self.auto_outline_frontier(merge_frontier)
1706 else:
1707 f1, f2 = merge_frontier.partition(best_block)
1708 if f1:
1709 f1.block.auto_outline_frontier(f1)
1710 if f2:
1711 f2.block.auto_outline_frontier(f2)
1712 return True
1714 def auto_expand_frontier(self):
1715 merge_state = self.get_merge_state()
1716 if merge_state.manual:
1717 return False
1718 elif merge_state.goal == 'full':
1719 return self.auto_fill_micromerge()
1720 else:
1721 return self.auto_outline_frontier()
1723 # The codes in the 2D array returned from create_diagram()
1724 MERGE_UNKNOWN = 0
1725 MERGE_MANUAL = 1
1726 MERGE_AUTOMATIC = 2
1727 MERGE_BLOCKED = 3
1728 MERGE_UNBLOCKED = 4
1729 MERGE_MASK = 7
1731 # A map {(is_known(), manual, is_blocked()) : integer constant}
1732 MergeState = {
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()]
1756 diagram[i1][i2] = c
1758 return diagram
1760 def format_diagram(self, legend=None, diagram=None):
1761 if legend is None:
1762 legend = [
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,
1769 if diagram is None:
1770 diagram = self.create_diagram()
1771 return [
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):
1781 f.write('P3\n')
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):
1788 @staticmethod
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):
1795 if i < 0:
1796 i += len
1797 i = slice(i, i + 1)
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')
1801 else:
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._start1 = start1 + block._start1
1815 self._start2 = start2 + block._start2
1816 else:
1817 assert(isinstance(block, MergeState))
1818 self._merge_state = block
1819 self._start1 = start1
1820 self._start2 = 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(
1828 i1 + self._start1,
1829 i2 + self._start2,
1832 def convert_original_indexes(self, i1, i2):
1833 (i1, i2) = self._merge_state.convert_original_indexes(i1, i2)
1834 if not (
1835 self._start1 <= i1 < self._start1 + self.len1
1836 and self._start2 <= i2 < self._start2 + self.len2
1838 raise IndexError('Indexes are not within block')
1839 return (i1 - self._start1, i2 - self._start2)
1841 def _set_value(self, i1, i2, sha1, flags):
1842 self._check_indexes(i1, i2)
1843 self._merge_state._set_value(
1844 i1 + self._start1,
1845 i2 + self._start2,
1846 sha1, flags,
1849 def get_value(self, i1, i2):
1850 self._check_indexes(i1, i2)
1851 return self._merge_state.get_value(i1 + self._start1, i2 + self._start2)
1853 def __str__(self):
1854 return '%s[%d:%d,%d:%d]' % (
1855 self._merge_state,
1856 self._start1, self._start1 + self.len1,
1857 self._start2, self._start2 + self.len2,
1861 class MergeState(Block):
1862 SOURCE_TABLE = {
1863 'auto': MergeRecord.SAVED_AUTO,
1864 'manual': MergeRecord.SAVED_MANUAL,
1867 MERGE_STATE_RE = re.compile(
1868 r"""
1870 refs\/imerge\/
1871 (?P<name>.+)
1872 \/state
1874 """,
1875 re.VERBOSE,
1878 @staticmethod
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()
1884 if type == 'blob':
1885 m = MergeState.MERGE_STATE_RE.match(refname)
1886 if m:
1887 yield m.group('name')
1889 @staticmethod
1890 def get_scratch_refname(name):
1891 return 'refs/heads/imerge/%s' % (name,)
1893 @staticmethod
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]:
1900 raise Failure(
1901 'The format of imerge %s (%s) is not compatible with this script version.'
1902 % (name, state['version'],)
1904 state['version'] = version
1905 return state
1907 @staticmethod
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."""
1916 try:
1917 call_silently(
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:
1929 return True
1930 else:
1931 return False
1933 @staticmethod
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."""
1939 if name is None:
1940 try:
1941 check_call(['git', 'config', '--unset', 'imerge.default'])
1942 except CalledProcessError as e:
1943 if e.returncode == 5:
1944 # Value was not set
1945 pass
1946 else:
1947 raise
1948 else:
1949 check_call(['git', 'config', 'imerge.default', name])
1951 @staticmethod
1952 def get_default_name():
1953 """Get the name of the default merge, or None if none is currently set."""
1955 try:
1956 return check_output(['git', 'config', 'imerge.default']).rstrip()
1957 except CalledProcessError:
1958 return None
1960 @staticmethod
1961 def _check_no_merges(commits):
1962 multiparent_commits = [
1963 commit
1964 for commit in commits
1965 if len(get_commit_parents(commit)) > 1
1967 if multiparent_commits:
1968 raise Failure(
1969 'The following commits on the to-be-merged branch are merge commits:\n'
1970 ' %s\n'
1971 '--goal=\'rebase\' is not yet supported for branches that include merges.\n'
1972 % ('\n '.join(multiparent_commits),)
1975 @staticmethod
1976 def check_name_format(name):
1977 """Check that name is a valid imerge name."""
1979 try:
1980 call_silently(
1981 ['git', 'check-ref-format', 'refs/imerge/%s' % (name,)]
1983 except CalledProcessError:
1984 raise Failure('Name %r is not a valid refname component!' % (name,))
1986 @staticmethod
1987 def initialize(
1988 name, merge_base,
1989 tip1, commits1,
1990 tip2, commits2,
1991 goal=DEFAULT_GOAL, manual=False, branch=None,
1993 """Create and return a new MergeState object."""
1995 MergeState.check_name_format(name)
1996 if branch:
1997 check_branch_name_format(branch)
1998 else:
1999 branch = name
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)
2007 return MergeState(
2008 name, merge_base,
2009 tip1, commits1,
2010 tip2, commits2,
2011 MergeRecord.NEW_MANUAL,
2012 goal=goal,
2013 manual=manual,
2014 branch=branch,
2017 @staticmethod
2018 def read(name):
2019 merge_ref_re = re.compile(
2020 r"""
2022 refs\/imerge\/
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]*)
2029 """,
2030 re.VERBOSE,
2033 state_ref_re = re.compile(
2034 r"""
2036 refs\/imerge\/
2037 """ + re.escape(name) + r"""
2038 \/state
2040 """,
2041 re.VERBOSE,
2044 state = None
2046 # A map {(i1, i2) : (sha1, source)}:
2047 merges = {}
2049 # refnames that were found but not understood:
2050 unexpected = []
2052 for line in check_output([
2053 'git', 'for-each-ref', 'refs/imerge/%s' % (name,)
2054 ]).splitlines():
2055 (sha1, type, refname) = line.split()
2056 m = merge_ref_re.match(refname)
2057 if m:
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)
2063 continue
2065 m = state_ref_re.match(refname)
2066 if m:
2067 if type != 'blob':
2068 raise Failure('Reference %r is not a blob!' % (refname,))
2069 state = MergeState._read_state(name, sha1)
2070 continue
2072 unexpected.append(refname)
2074 if state is None:
2075 raise Failure(
2076 'No state found; it should have been a blob reference at '
2077 '"refs/imerge/%s/state"' % (name,)
2080 blockers = state.get('blockers', [])
2082 if unexpected:
2083 raise Failure(
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!')
2092 commits1 = []
2093 for i1 in itertools.count(1):
2094 try:
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)
2099 except KeyError:
2100 break
2102 commits2 = []
2103 for i2 in itertools.count(1):
2104 try:
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)
2109 except KeyError:
2110 break
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)
2122 state = MergeState(
2123 name, merge_base,
2124 tip1, commits1,
2125 tip2, commits2,
2126 MergeRecord.SAVED_MANUAL,
2127 goal=goal,
2128 manual=manual,
2129 branch=branch,
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:
2139 raise Failure(
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)
2149 return state
2151 @staticmethod
2152 def remove(name):
2153 # If HEAD is the scratch refname, abort any in-progress
2154 # commits and detach HEAD:
2155 scratch_refname = MergeState.get_scratch_refname(name)
2156 try:
2157 head_refname = check_output(['git', 'symbolic-ref', '--quiet', 'HEAD']).strip()
2158 except CalledProcessError:
2159 head_refname = None
2160 if head_refname == scratch_refname:
2161 try:
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:
2166 pass
2167 # Detach head so that we can delete scratch_refname:
2168 check_call([
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:
2175 check_call([
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,)
2184 ]).splitlines():
2185 (sha1, type, refname) = line.split()
2186 try:
2187 check_call([
2188 'git', 'update-ref',
2189 '-m', 'imerge: remove merge %r' % (name,),
2190 '-d', refname,
2192 except CalledProcessError as e:
2193 sys.stderr.write(
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)
2201 def __init__(
2202 self, name, merge_base,
2203 tip1, commits1,
2204 tip2, commits2,
2205 source,
2206 goal=DEFAULT_GOAL,
2207 manual=False,
2208 branch=None,
2210 Block.__init__(self, name, len(commits1) + 1, len(commits2) + 1)
2211 self.tip1 = tip1
2212 self.tip2 = tip2
2213 self.goal = goal
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):
2227 return 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)]
2238 self.goal = goal
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:
2246 if value is None:
2247 value = MergeRecord()
2248 self._data[i1][i2] = value
2249 return 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
2262 made."""
2264 progress_made = False
2265 try:
2266 while True:
2267 frontier = MergeFrontier.map_known_frontier(self)
2268 frontier.auto_expand()
2269 self.save()
2270 progress_made = True
2271 except BlockCompleteError:
2272 return
2273 except FrontierBlockedError as e:
2274 self.save()
2275 if not progress_made:
2276 # Adjust the error message:
2277 raise FrontierBlockedError(
2278 'No progress was possible; suggest manual merge of %d-%d'
2279 % (e.i1, e.i2),
2280 e.i1, e.i2,
2282 else:
2283 raise
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:
2295 return (i1, i2)
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...
2311 try:
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,
2318 swapped = False
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)
2322 swapped = True
2323 if i1first != i1second + 1 or i2first != i2second - 1:
2324 raise ManualMergeUnusableError(
2325 'it is not a pairwise merge of adjacent parents', commit,
2327 if swapped:
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)
2333 return (i1, i2)
2335 @staticmethod
2336 def _is_ancestor(commit1, commit2):
2337 """Return True iff commit1 is an ancestor (or equal to) commit2."""
2339 if commit1 == commit2:
2340 return True
2341 else:
2342 return int(
2343 check_output([
2344 'git', 'rev-list', '--count', '--ancestry-path',
2345 '%s..%s' % (commit1, commit2,),
2346 ]).strip()
2347 ) != 0
2349 @staticmethod
2350 def _set_refname(refname, commit, force=False):
2351 try:
2352 ref_oldval = get_commit_sha1(refname)
2353 except ValueError:
2354 # refname doesn't already exist; simply point it at commit:
2355 check_call(['git', 'update-ref', refname, commit])
2356 checkout(refname, quiet=True)
2357 else:
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)
2361 try:
2362 head_refname = check_output(['git', 'symbolic-ref', '--quiet', 'HEAD']).strip()
2363 except CalledProcessError:
2364 head_refname = None
2366 if not force:
2367 if not MergeState._is_ancestor(ref_oldval, commit):
2368 raise Failure(
2369 '%s cannot be fast-forwarded to %s!' % (refname, commit)
2372 if head_refname == refname:
2373 check_call(['git', 'reset', '--hard', commit])
2374 else:
2375 check_call([
2376 'git', 'update-ref',
2377 '-m', 'imerge: recording final merge',
2378 refname, commit,
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:
2386 raise Failure(
2387 'Cannot simplify to "full" because '
2388 'merge %d-%d is not yet done'
2389 % (i1, i2)
2392 self._set_refname(refname, self[-1, -1].sha1, force=force)
2394 def simplify_to_rebase_with_history(self, refname, force=False):
2395 i1 = self.len1 - 1
2396 for i2 in range(1, self.len2):
2397 if not (i1, i2) in self:
2398 raise Failure(
2399 'Cannot simplify to rebase-with-history because '
2400 'merge %d-%d is not yet done'
2401 % (i1, i2)
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:
2410 msg = (
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):
2419 i1 = self.len1 - 1
2420 for i2 in range(1, self.len2):
2421 if not (i1, i2) in self:
2422 raise Failure(
2423 'Cannot simplify to rebase because merge %d-%d is not yet done'
2424 % (i1, i2)
2427 if not force:
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.
2431 try:
2432 ref_oldval = get_commit_sha1(refname)
2433 except ValueError:
2434 # refname doesn't already exist; no problem.
2435 pass
2436 else:
2437 commit = self[-1, -1].sha1
2438 if not MergeState._is_ancestor(ref_oldval, commit):
2439 raise Failure(
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
2456 # force=True:
2457 self._set_refname(refname, commit, force=True)
2459 def simplify_to_merge(self, refname, force=False):
2460 if not (-1, -1) in self:
2461 raise Failure(
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:
2469 sha1 = commit_tree(
2470 tree, parents,
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)
2492 else:
2493 raise ValueError('Invalid value for goal (%r)' % (self.goal,))
2495 def save(self):
2496 """Write the current MergeState to the repository."""
2498 blockers = []
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))
2507 state = dict(
2508 version='.'.join(str(i) for i in STATE_VERSION),
2509 blockers=blockers,
2510 tip1=self.tip1, tip2=self.tip2,
2511 goal=self.goal,
2512 manual=self.manual,
2513 branch=self.branch,
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]
2520 retcode = p.poll()
2521 if retcode:
2522 raise CalledProcessError(retcode, cmd)
2523 sha1 = out.strip()
2524 check_call([
2525 'git', 'update-ref',
2526 '-m', 'imerge %r: Record state' % (self.name,),
2527 'refs/imerge/%s/state' % (self.name,),
2528 sha1,
2531 def __str__(self):
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)
2548 check_call([
2549 'git', 'update-ref',
2550 '-m', 'imerge %r: Prepare merge %d-%d' % (merge_state.name, i1, i2,),
2551 refname, above.sha1,
2553 checkout(refname)
2554 logmsg = 'imerge \'%s\': manual merge %d-%d' % (merge_state.name, i1, i2)
2555 try:
2556 check_call([
2557 'git', 'merge', '--no-commit',
2558 '-m', logmsg,
2559 left.sha1,
2561 except CalledProcessError:
2562 # We expect an error (otherwise we would have automerged!)
2563 pass
2564 sys.stderr.write(
2565 '\n'
2566 'Original first commit:\n'
2568 check_call(['git', '--no-pager', 'log', '--no-walk', merge_state[i1, 0].sha1])
2569 sys.stderr.write(
2570 '\n'
2571 'Original second commit:\n'
2573 check_call(['git', '--no-pager', 'log', '--no-walk', merge_state[0, i2].sha1])
2574 sys.stderr.write(
2575 '\n'
2576 'There was a conflict merging commit %d-%d, shown above.\n'
2577 'Please resolve the conflict, commit the result, then type\n'
2578 '\n'
2579 ' git-imerge continue\n'
2580 % (i1, i2)
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)
2599 try:
2600 commit = get_commit_sha1(refname)
2601 except ValueError:
2602 raise NoManualMergeError('Reference %s does not exist.' % (refname,))
2604 try:
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.
2615 try:
2616 merge_state.find_index(commit)
2617 except CommitNotFoundError:
2618 # It points to a commit that we don't have in our records.
2619 raise Failure(
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'
2623 '\n'
2624 ' git checkout %(refname)s\n'
2625 '\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'
2628 '\n'
2629 ' git update-ref -d %(refname)s\n'
2630 '\n'
2631 'and then continue.'
2632 % dict(refname=refname)
2634 else:
2635 # It points to a commit that is already recorded. We can
2636 # delete it without losing any information.
2637 check_call([
2638 'git', 'update-ref',
2639 '-m',
2640 'imerge %r: Remove obsolete scratch reference'
2641 % (merge_state.name,),
2642 '-d', refname,
2644 sys.stderr.write(
2645 '%s did not point to a new merge; it has been deleted.\n'
2646 % (refname,)
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
2654 # can be committed:
2656 merge_frontier = MergeFrontier.map_known_frontier(merge_state)
2658 try:
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()
2667 if edit_log_msg:
2668 cmd += ['--edit']
2669 else:
2670 cmd += ['--no-edit']
2672 try:
2673 check_call(cmd)
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.
2684 check_call([
2685 'git', 'update-ref', '--no-deref',
2686 '-m', 'Detach HEAD from %s' % (refname,),
2687 'HEAD', commit,
2690 check_call([
2691 'git', 'update-ref',
2692 '-m', 'imerge %s: remove scratch reference' % (merge_state.name,),
2693 '-d', refname,
2696 try:
2697 # This might throw NotABlockingCommitError:
2698 unblocked_block = merge_frontier.get_affected_blocker_block(i1, i2)
2699 unblocked_block[1, 1].record_blocked(False)
2700 sys.stderr.write(
2701 'Merge has been recorded for merge %d-%d.\n'
2702 % unblocked_block.get_original_indexes(1, 1)
2704 except NotABlockingCommitError:
2705 raise
2706 finally:
2707 merge_state.save()
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)
2716 return name
2718 # A name was not specified. Try to use the default name:
2719 default_name = MergeState.get_default_name()
2720 if default_name:
2721 if MergeState.check_exists(default_name):
2722 return default_name
2723 else:
2724 # There's no reason to keep the invalid default around:
2725 MergeState.set_default_name(None)
2726 raise Failure(
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'
2730 % (default_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])
2738 return 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))
2747 @Failure.wrap
2748 def main(args):
2749 NAME_INIT_HELP = 'name to use for this incremental merge'
2751 def add_name_argument(subparser, help=None):
2752 if help is 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'
2762 if default is None:
2763 help = (
2764 'the type of simplification to be made '
2765 '(default is the value provided to "init" or "start")'
2767 subparser.add_argument(
2768 '--goal',
2769 action='store', default=default,
2770 choices=ALLOWED_GOALS,
2771 help=help,
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']:
2778 help = (
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 '
2784 'specified.'
2786 subparser.add_argument(
2787 '--branch',
2788 action='store', default=None,
2789 help=help,
2792 def add_manual_argument(subparser):
2793 subparser.add_argument(
2794 '--manual',
2795 action='store_true', default=False,
2796 help=(
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 '
2800 'completed.'
2804 def add_first_parent_argument(subparser, default=None):
2805 subcommand = subparser.prog.split()[1]
2806 help = (
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(
2829 'start',
2830 help=(
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(
2843 'merge',
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(
2854 'rebase',
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',
2864 help=(
2865 'the tip of the branch onto which the current branch should '
2866 'be rebased'
2870 subparser = subparsers.add_parser(
2871 'continue',
2872 help=(
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(
2892 'finish',
2893 help=(
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(
2902 '--force',
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(
2908 'diagram',
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(
2934 'list',
2935 help=(
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(
2942 'init',
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(
2953 'record',
2954 help='record the merge at branch imerge/NAME',
2956 # record:
2957 add_name_argument(
2958 subparser,
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(
2972 'autofill',
2973 help='autofill non-conflicting merges',
2975 add_name_argument(subparser)
2977 subparser = subparsers.add_parser(
2978 'simplify',
2979 help=(
2980 'simplify a completed incremental merge by discarding unneeded '
2981 'intermediate merges and cleaning up the ancestry of the commits '
2982 'that are retained'
2985 add_name_argument(subparser)
2986 add_goal_argument(subparser, default=None)
2987 add_branch_argument(subparser)
2988 subparser.add_argument(
2989 '--force',
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(
2995 'remove',
2996 help='irrevocably remove an incremental merge',
2998 add_name_argument(subparser)
3000 subparser = subparsers.add_parser(
3001 'reparent',
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,))
3022 else:
3023 sys.stdout.write(' %s\n' % (name,))
3024 elif options.subcommand == 'init':
3025 require_clean_work_tree('proceed')
3027 if not options.first_parent:
3028 parser.error(
3029 'The --first-parent option is currently required for the "init" command'
3031 if not options.name:
3032 parser.error(
3033 'Please specify the --name to be used for this incremental merge'
3035 try:
3036 tip1 = check_output(
3037 ['git', 'symbolic-ref', '--quiet', '--short', 'HEAD'],
3038 ).strip()
3039 except CalledProcessError:
3040 tip1 = 'HEAD'
3041 tip2 = options.tip2
3042 (merge_base, commits1, commits2) = get_boundaries(tip1, tip2)
3043 merge_state = MergeState.initialize(
3044 options.name, merge_base,
3045 tip1, commits1,
3046 tip2, commits2,
3047 goal=options.goal, manual=options.manual,
3048 branch=(options.branch or options.name),
3050 merge_state.save()
3051 MergeState.set_default_name(options.name)
3052 elif options.subcommand == 'start':
3053 require_clean_work_tree('proceed')
3055 if not options.first_parent:
3056 parser.error(
3057 'The --first-parent option is currently required for the "start" command'
3059 if not options.name:
3060 parser.error(
3061 'Please specify the --name to be used for this incremental merge'
3063 try:
3064 tip1 = check_output(
3065 ['git', 'symbolic-ref', '--quiet', '--short', 'HEAD'],
3066 ).strip()
3067 except CalledProcessError:
3068 tip1 = 'HEAD'
3069 tip2 = options.tip2
3070 (merge_base, commits1, commits2) = get_boundaries(tip1, tip2)
3071 merge_state = MergeState.initialize(
3072 options.name, merge_base,
3073 tip1, commits1,
3074 tip2, commits2,
3075 goal=options.goal, manual=options.manual,
3076 branch=(options.branch or options.name),
3078 merge_state.save()
3079 MergeState.set_default_name(options.name)
3081 try:
3082 merge_state.auto_complete_frontier()
3083 except FrontierBlockedError as e:
3084 request_user_merge(merge_state, e.i1, e.i2)
3085 else:
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:
3091 parser.error(
3092 'The --first-parent option is currently required for the "merge" command'
3095 tip2 = options.tip2
3097 if options.name:
3098 name = options.name
3099 else:
3100 # By default, name the imerge after the branch being merged:
3101 name = tip2
3102 MergeState.check_name_format(name)
3104 try:
3105 tip1 = check_output(
3106 ['git', 'symbolic-ref', '--quiet', '--short', 'HEAD'],
3107 ).strip()
3108 if not options.branch:
3109 # See if we can store the result to the checked-out branch:
3110 try:
3111 check_branch_name_format(tip1)
3112 except InvalidBranchNameError:
3113 pass
3114 else:
3115 options.branch = tip1
3116 except CalledProcessError:
3117 tip1 = 'HEAD'
3119 if not options.branch:
3120 if options.name:
3121 options.branch = options.name
3122 else:
3123 parser.error(
3124 'HEAD is not a simple branch. '
3125 'Please specify --branch for storing results.'
3128 try:
3129 (merge_base, commits1, commits2) = get_boundaries(tip1, tip2)
3130 except NothingToDoError as e:
3131 sys.stdout.write('Already up-to-date.\n')
3132 sys.exit(0)
3133 merge_state = MergeState.initialize(
3134 name, merge_base,
3135 tip1, commits1,
3136 tip2, commits2,
3137 goal=options.goal, manual=options.manual,
3138 branch=options.branch,
3140 merge_state.save()
3141 MergeState.set_default_name(name)
3143 try:
3144 merge_state.auto_complete_frontier()
3145 except FrontierBlockedError as e:
3146 request_user_merge(merge_state, e.i1, e.i2)
3147 else:
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:
3153 parser.error(
3154 'The --first-parent option is currently required for the "rebase" command'
3157 tip1 = options.tip1
3159 try:
3160 tip2 = check_output(
3161 ['git', 'symbolic-ref', '--quiet', '--short', 'HEAD'],
3162 ).strip()
3163 if not options.branch:
3164 # See if we can store the result to the current branch:
3165 try:
3166 check_branch_name_format(tip2)
3167 except InvalidBranchNameError:
3168 pass
3169 else:
3170 options.branch = tip2
3171 if not options.name:
3172 # By default, name the imerge after the branch being rebased:
3173 options.name = tip2
3175 except CalledProcessError:
3176 tip2 = rev_parse('HEAD')
3178 if not options.name:
3179 parser.error(
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:
3185 if options.name:
3186 options.branch = options.name
3187 else:
3188 parser.error(
3189 'HEAD is not a simple branch. '
3190 'Please specify --branch for storing results.'
3193 try:
3194 (merge_base, commits1, commits2) = get_boundaries(tip1, tip2)
3195 except NothingToDoError as e:
3196 sys.stdout.write('Already up-to-date.\n')
3197 sys.exit(0)
3199 merge_state = MergeState.initialize(
3200 options.name, merge_base,
3201 tip1, commits1,
3202 tip2, commits2,
3203 goal=options.goal, manual=options.manual,
3204 branch=options.branch,
3206 merge_state.save()
3207 MergeState.set_default_name(options.name)
3209 try:
3210 merge_state.auto_complete_frontier()
3211 except FrontierBlockedError as e:
3212 request_user_merge(merge_state, e.i1, e.i2)
3213 else:
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)
3219 try:
3220 incorporate_user_merge(merge_state, edit_log_msg=options.edit)
3221 except NoManualMergeError:
3222 pass
3223 except NotABlockingCommitError as e:
3224 raise Failure(str(e))
3225 except ManualMergeUnusableError as e:
3226 raise Failure(str(e))
3228 try:
3229 merge_state.auto_complete_frontier()
3230 except FrontierBlockedError as e:
3231 request_user_merge(merge_state, e.i1, e.i2)
3232 else:
3233 sys.stderr.write('Merge is complete!\n')
3234 elif options.subcommand == 'record':
3235 merge_state = read_merge_state(options.name)
3236 try:
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))
3245 try:
3246 merge_state.auto_complete_frontier()
3247 except FrontierBlockedError as e:
3248 pass
3249 else:
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():
3255 try:
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)
3268 merge_state.save()
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)
3279 merge_state.save()
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())):
3286 AnsiColor.disable()
3288 merge_state = read_merge_state(options.name)
3289 if options.commits:
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')
3296 if options.html:
3297 merge_frontier = MergeFrontier.map_known_frontier(merge_state)
3298 html = open(options.html, 'w')
3299 merge_frontier.write_html(html, merge_state.name)
3300 html.close()
3301 sys.stdout.write(
3302 'Key:\n'
3304 if options.frontier:
3305 sys.stdout.write(
3306 ' |,-,+ = rectangles forming current merge frontier\n'
3308 sys.stdout.write(
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'
3314 '\n'
3316 elif options.subcommand == 'reparent':
3317 try:
3318 commit_sha1 = get_commit_sha1('HEAD')
3319 except ValueError:
3320 sys.exit('HEAD is not a valid commit')
3322 try:
3323 parent_sha1s = [get_commit_sha1(p) for p in options.parents]
3324 except ValueError as e:
3325 sys.exit(e.message)
3327 sys.stdout.write('%s\n' % (reparent(commit_sha1, parent_sha1s),))
3328 else:
3329 parser.error('Unrecognized subcommand')
3332 main(sys.argv[1:])
3334 # vim: set expandtab ft=python: