raise DetachedHeadException()
def set_head_ref(self, ref, msg):
self.run(['git', 'symbolic-ref', '-m', msg, 'HEAD', ref]).no_output()
+ def get_merge_bases(self, commit1, commit2):
+ """Return a set of merge bases of two commits."""
+ sha1_list = self.run(['git', 'merge-base', '--all',
+ commit1.sha1, commit2.sha1]).output_lines()
+ return set(self.get_commit(sha1) for sha1 in sha1_list)
+ def describe(self, commit):
+ """Use git describe --all on the given commit."""
+ return self.run(['git', 'describe', '--all', commit.sha1]
+ ).discard_stderr().discard_exitcode().raw_output()
def simple_merge(self, base, ours, theirs):
index = self.temp_index()
try:
class MergeConflictException(MergeException):
"""Exception raised when a merge fails due to conflicts."""
+ def __init__(self, conflicts):
+ MergeException.__init__(self)
+ self.conflicts = conflicts
class Index(RunWithEnv):
"""Represents a git index file."""
).output_one_line())
except run.RunException:
raise MergeException('Conflicting merge')
- def is_clean(self):
+ def is_clean(self, tree):
+ """Check whether the index is clean relative to the given treeish."""
try:
- self.run(['git', 'update-index', '--refresh']).discard_output()
+ self.run(['git', 'diff-index', '--quiet', '--cached', tree.sha1]
+ ).discard_output()
except run.RunException:
return False
else:
).discard_output()
except run.RunException:
raise CheckoutException('Index/workdir dirty')
- def merge(self, base, ours, theirs):
+ def merge(self, base, ours, theirs, interactive = False):
assert isinstance(base, Tree)
assert isinstance(ours, Tree)
assert isinstance(theirs, Tree)
env = { 'GITHEAD_%s' % base.sha1: 'ancestor',
'GITHEAD_%s' % ours.sha1: 'current',
'GITHEAD_%s' % theirs.sha1: 'patched'})
- r.discard_output()
+ r.returns([0, 1])
+ output = r.output_lines()
+ if r.exitcode:
+ # There were conflicts
+ if interactive:
+ self.mergetool()
+ else:
+ conflicts = [l for l in output if l.startswith('CONFLICT')]
+ raise MergeConflictException(conflicts)
except run.RunException, e:
- if r.exitcode == 1:
- raise MergeConflictException()
- else:
- raise MergeException('Index/worktree dirty')
+ raise MergeException('Index/worktree dirty')
+ def mergetool(self, files = ()):
+ """Invoke 'git mergetool' on the current IndexAndWorktree to resolve
+ any outstanding conflicts. If 'not files', all the files in an
+ unmerged state will be processed."""
+ self.run(['git', 'mergetool'] + list(files)).returns([0, 1]).run()
+ # check for unmerged entries (prepend 'CONFLICT ' for consistency with
+ # merge())
+ conflicts = ['CONFLICT ' + f for f in self.index.conflicts()]
+ if conflicts:
+ raise MergeConflictException(conflicts)
def changed_files(self, tree, pathlimits = []):
"""Return the set of files in the worktree that have changed with
respect to C{tree}. The listing is optionally restricted to
cmd = ['git', 'update-index', '--remove']
self.run(cmd + ['-z', '--stdin']
).input_nulterm(paths).discard_output()
+ def worktree_clean(self):
+ """Check whether the worktree is clean relative to index."""
+ try:
+ self.run(['git', 'update-index', '--refresh']).discard_output()
+ except run.RunException:
+ return False
+ else:
+ return True
class Branch(object):
"""Represents a Git branch."""
"""Return the diffstat of the supplied diff."""
return run.Run('git', 'apply', '--stat', '--summary'
).raw_input(diff).raw_output()
+
+def clone(remote, local):
+ """Clone a remote repository using 'git clone'."""
+ run.Run('git', 'clone', remote, local).run()