Add mergetool support to the classic StGit infrastructure
authorCatalin Marinas <catalin.marinas@arm.com>
Thu, 9 Apr 2009 20:40:58 +0000 (23:40 +0300)
committerCatalinMarinas <cmarinas@laptop.(none)>
Thu, 9 Apr 2009 20:40:58 +0000 (23:40 +0300)
Since Git already has a tool for interactively solving conflicts which
is highly customisable, there is no need to duplicate this feature via
the i3merge and i2merge configuration options. The user-visible change
is that now mergetool is invoked rather than the previously customised
interactive merging tool.

The stgit.keeporig option is no longer available to be more consistent
with the Git behaviour.

Signed-off-by: Catalin Marinas <catalin.marinas@gmail.com>
Acked-by: Karl Hasselström <kha@treskal.com>
examples/gitconfig
stgit/commands/resolved.py
stgit/config.py
stgit/git.py
stgit/gitmergeonefile.py [deleted file]
t/t0002-status.sh

index 2fc5f52..f6e3a79 100644 (file)
        # To support local parent branches:
        #pull-policy = rebase
 
-       # Interactive two/three-way merge tool. It is executed by the
-       # 'resolved --interactive' command
-       #i3merge = xxdiff --title1 current --title2 ancestor --title3 patched \
-       #       --show-merged-pane -m -E -O -X -M \"%(output)s\" \
-       #       \"%(branch1)s\" \"%(ancestor)s\" \"%(branch2)s\"
-       #i2merge = xxdiff --title1 current --title2 patched \
-       #       --show-merged-pane -m -E -O -X -M \"%(output)s\" \
-       #       \"%(branch1)s\" \"%(branch2)s\"
-       #i3merge = emacs --eval '(ediff-merge-files-with-ancestor \
-       #       \"%(branch1)s\" \"%(branch2)s\" \"%(ancestor)s\" nil \
-       #       \"%(output)s\")'
-       #i2merge = emacs --eval '(ediff-merge-files \
-       #       \"%(branch1)s\" \"%(branch2)s\" nil \"%(output)s\")'
-
-       # Automatically invoke the interactive merger in case of conflicts
+       # Automatically invoke the interactive merger (git mergetool) in case
+       # of conflicts
        #autoimerge = no
 
-       # Leave the original files in the working tree in case of a
-       # merge conflict
-       #keeporig = yes
-
        # Optimize (repack) the object store after every pull
        #keepoptimized = yes
 
index 2ce7ec3..eba778d 100644 (file)
@@ -22,7 +22,6 @@ from stgit.commands.common import *
 from stgit.utils import *
 from stgit import argparse, stack, git, basedir
 from stgit.config import config, file_extensions
-from stgit.gitmergeonefile import interactive_merge
 
 help = 'Mark a file conflict as solved'
 kind = 'wc'
@@ -78,8 +77,6 @@ def func(parser, options, args):
 
     # resolved
     if options.interactive:
-        for filename in files:
-            interactive_merge(filename)
-            git.resolved([filename])
+        git.mergetool(files)
     else:
         git.resolved(files, options.reset)
index 05ef624..efce097 100644 (file)
@@ -35,7 +35,6 @@ class GitConfig:
         'stgit.fetchcmd':      'git fetch',
         'stgit.pull-policy':   'pull',
         'stgit.autoimerge':    'no',
-        'stgit.keeporig':      'yes',
         'stgit.keepoptimized': 'no',
         'stgit.extensions':    '.ancestor .current .patched',
         'stgit.shortnr':        '5'
index 4d01fc2..cbdefe8 100644 (file)
@@ -18,7 +18,7 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 """
 
-import sys, os, re, gitmergeonefile
+import sys, os, re
 from shutil import copyfile
 
 from stgit.exception import *
@@ -632,19 +632,23 @@ def merge_recursive(base, head1, head2):
     output = p.output_lines()
     if p.exitcode:
         # There were conflicts
-        conflicts = [l.strip() for l in output if l.startswith('CONFLICT')]
+        if config.get('stgit.autoimerge') == 'yes':
+            mergetool()
+        else:
+            conflicts = [l for l in output if l.startswith('CONFLICT')]
+            out.info(*conflicts)
+            raise GitException, "%d conflict(s)" % len(conflicts)
+
+def mergetool(files = ()):
+    """Invoke 'git mergetool' to resolve any outstanding conflicts. If 'not
+    files', all the files in an unmerged state will be processed."""
+    GRun('mergetool', *list(files)).returns([0, 1]).run()
+    # check for unmerged entries (prepend 'CONFLICT ' for consistency with
+    # merge_recursive())
+    conflicts = ['CONFLICT ' + f for f in get_conflicts()]
+    if conflicts:
         out.info(*conflicts)
-
-        # try the interactive merge or stage checkout (if enabled)
-        for filename in get_conflicts():
-            if (gitmergeonefile.merge(filename)):
-                # interactive merge succeeded
-                resolved([filename])
-
-        # any conflicts left unsolved?
-        cn = len(get_conflicts())
-        if cn:
-            raise GitException, "%d conflict(s)" % cn
+        raise GitException, "%d conflict(s)" % len(conflicts)
 
 def diff(files = None, rev1 = 'HEAD', rev2 = None, diff_flags = [],
          binary = True):
@@ -754,7 +758,6 @@ def resolved(filenames, reset = None):
              '--stdin', '-z').input_nulterm(filenames).no_output()
     GRun('update-index', '--add', '--').xargs(filenames)
     for filename in filenames:
-        gitmergeonefile.clean_up(filename)
         # update the access and modificatied times
         os.utime(filename, None)
 
diff --git a/stgit/gitmergeonefile.py b/stgit/gitmergeonefile.py
deleted file mode 100644 (file)
index 1fe226e..0000000
+++ /dev/null
@@ -1,150 +0,0 @@
-"""Performs a 3-way merge for GIT files
-"""
-
-__copyright__ = """
-Copyright (C) 2006, Catalin Marinas <catalin.marinas@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License version 2 as
-published by the Free Software Foundation.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-"""
-
-import sys, os
-from stgit.exception import *
-from stgit import basedir
-from stgit.config import config, file_extensions, ConfigOption
-from stgit.utils import append_string
-from stgit.out import *
-from stgit.run import *
-
-class GitMergeException(StgException):
-    pass
-
-
-#
-# Options
-#
-autoimerge = ConfigOption('stgit', 'autoimerge')
-keeporig = ConfigOption('stgit', 'keeporig')
-
-#
-# Utility functions
-#
-def __str2none(x):
-    if x == '':
-        return None
-    else:
-        return x
-
-class MRun(Run):
-    exc = GitMergeException # use a custom exception class on errors
-
-def __checkout_stages(filename):
-    """Check-out the merge stages in the index for the give file
-    """
-    extensions = file_extensions()
-    line = MRun('git', 'checkout-index', '--stage=all', '--', filename
-                ).output_one_line()
-    stages, path = line.split('\t')
-    stages = dict(zip(['ancestor', 'current', 'patched'],
-                      stages.split(' ')))
-
-    for stage, fn in stages.iteritems():
-        if stages[stage] == '.':
-            stages[stage] = None
-        else:
-            newname = filename + extensions[stage]
-            if os.path.exists(newname):
-                # remove the stage if it is already checked out
-                os.remove(newname)
-            os.rename(stages[stage], newname)
-            stages[stage] = newname
-
-    return stages
-
-def __remove_stages(filename):
-    """Remove the merge stages from the working directory
-    """
-    extensions = file_extensions()
-    for ext in extensions.itervalues():
-        fn = filename + ext
-        if os.path.isfile(fn):
-            os.remove(fn)
-
-def interactive_merge(filename):
-    """Run the interactive merger on the given file. Stages will be
-    removed according to stgit.keeporig. If successful and stages
-    kept, they will be removed via git.resolved().
-    """
-    stages = __checkout_stages(filename)
-
-    try:
-        # Check whether we have all the files for the merge.
-        if not (stages['current'] and stages['patched']):
-            raise GitMergeException('Cannot run the interactive merge')
-
-        if stages['ancestor']:
-            three_way = True
-            files_dict = {'branch1': stages['current'],
-                          'ancestor': stages['ancestor'],
-                          'branch2': stages['patched'],
-                          'output': filename}
-            imerger = config.get('stgit.i3merge')
-        else:
-            three_way = False
-            files_dict = {'branch1': stages['current'],
-                          'branch2': stages['patched'],
-                          'output': filename}
-            imerger = config.get('stgit.i2merge')
-
-        if not imerger:
-            raise GitMergeException, 'No interactive merge command configured'
-
-        mtime = os.path.getmtime(filename)
-
-        out.start('Trying the interactive %s merge'
-                  % (three_way and 'three-way' or 'two-way'))
-        err = os.system(imerger % files_dict)
-        out.done()
-        if err != 0:
-            raise GitMergeException, 'The interactive merge failed'
-        if not os.path.isfile(filename):
-            raise GitMergeException, 'The "%s" file is missing' % filename
-        if mtime == os.path.getmtime(filename):
-            raise GitMergeException, 'The "%s" file was not modified' % filename
-    finally:
-        # keep the merge stages?
-        if str(keeporig) != 'yes':
-            __remove_stages(filename)
-
-def clean_up(filename):
-    """Remove merge conflict stages if they were generated.
-    """
-    if str(keeporig) == 'yes':
-        __remove_stages(filename)
-
-def merge(filename):
-    """Merge one file if interactive is allowed or check out the stages
-    if keeporig is set.
-    """
-    if str(autoimerge) == 'yes':
-        try:
-            interactive_merge(filename)
-        except GitMergeException, ex:
-            out.error(str(ex))
-            return False
-        return True
-
-    if str(keeporig) == 'yes':
-        __checkout_stages(filename)
-
-    return False
index ac92aa8..d95a83b 100755 (executable)
@@ -107,9 +107,6 @@ test_expect_success 'Make a conflicting patch' '
 '
 
 cat > expected.txt <<EOF
-? foo/bar.ancestor
-? foo/bar.current
-? foo/bar.patched
 A fie
 C foo/bar
 EOF