Add support for merge-friendly branches
[stgit] / stgit / lib / git.py
index 07079b8..6e3bb4f 100644 (file)
@@ -609,6 +609,15 @@ class Repository(RunWithEnv):
             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:
@@ -824,7 +833,7 @@ class IndexAndWorktree(RunWithEnvCwd):
                      ).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)
@@ -838,10 +847,23 @@ class IndexAndWorktree(RunWithEnvCwd):
             output = r.output_lines()
             if r.exitcode:
                 # There were conflicts
-                conflicts = [l for l in output if l.startswith('CONFLICT')]
-                raise MergeConflictException(conflicts)
+                if interactive:
+                    self.mergetool()
+                else:
+                    conflicts = [l for l in output if l.startswith('CONFLICT')]
+                    raise MergeConflictException(conflicts)
         except run.RunException, e:
             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
@@ -920,3 +942,7 @@ def diffstat(diff):
     """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()