changeset 4360:d5c3a70f8422

polish the simplemerge command; add a test
author Alexis S. L. Carvalho <alexis@cecm.usp.br>
date Mon, 16 Apr 2007 20:17:39 -0300
parents 2e3c54fb79a3
children 46280c004f22
files contrib/simplemerge tests/test-simplemerge-cmd tests/test-simplemerge-cmd.out
diffstat 3 files changed, 171 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/simplemerge
+++ b/contrib/simplemerge
@@ -19,8 +19,10 @@
 # mbp: "you know that thing where cvs gives you conflict markers?"
 # s: "i hate that."
 
+from mercurial import demandimport
+demandimport.enable()
 
-from mercurial import util, mdiff
+from mercurial import util, mdiff, fancyopts
 from mercurial.i18n import _
 
 
@@ -97,6 +99,7 @@ class Merge3Text(object):
                     reprocess=False):
         """Return merge in cvs-like form.
         """
+        self.conflicts = False
         newline = '\n'
         if len(self.a) > 0:
             if self.a[0].endswith('\r\n'):
@@ -126,6 +129,7 @@ class Merge3Text(object):
                 for i in range(t[1], t[2]):
                     yield self.b[i]
             elif what == 'conflict':
+                self.conflicts = True
                 yield start_marker + newline
                 for i in range(t[3], t[4]):
                     yield self.a[i]
@@ -439,21 +443,115 @@ class Merge3(Merge3Text):
         Merge3Text.__init__(self, basetext, atext, btext, base, a, b)
 
 
-def main(argv):
-    # as for diff3 and meld the syntax is "MINE BASE OTHER"
-    a = file(argv[1], 'rt').readlines()
-    base = file(argv[2], 'rt').readlines()
-    b = file(argv[3], 'rt').readlines()
+def simplemerge(local, base, other, **opts):
+    def readfile(filename):
+        f = open(filename, "rb")
+        text = f.read()
+        f.close()
+        if util.binary(text):
+            msg = _("%s looks like a binary file.") % filename
+            if not opts.get('text'):
+                raise util.Abort(msg)
+            elif not opts.get('quiet'):
+                sys.stderr.write(_('warning: %s\n') % msg)
+        return text
+
+    name_a = local
+    name_b = other
+    labels = opts.get('label', [])
+    if labels:
+        name_a = labels.pop(0)
+    if labels:
+        name_b = labels.pop(0)
+    if labels:
+        raise util.Abort(_("can only specify two labels."))
+
+    localtext = readfile(local)
+    basetext = readfile(base)
+    othertext = readfile(other)
+
+    orig = local
+    local = os.path.realpath(local)
+    if not opts.get('print'):
+        opener = util.opener(os.path.dirname(local))
+        out = opener(os.path.basename(local), "w", atomictemp=True)
+    else:
+        out = sys.stdout
+
+    reprocess = not opts.get('no_minimal')
+
+    m3 = Merge3Text(basetext, localtext, othertext)
+    for line in m3.merge_lines(name_a=name_a, name_b=name_b,
+                               reprocess=reprocess):
+        out.write(line)
+
+    if not opts.get('print'):
+        out.rename()
+
+    if m3.conflicts:
+        if not opts.get('quiet'):
+            sys.stdout.flush()
+            sys.stderr.write(_("warning: conflicts during merge.\n"))
+        return 1
 
-    m3 = Merge3(base, a, b)
+options = [('L', 'label', [], _('labels to use on conflict markers')),
+           ('a', 'text', None, _('treat all files as text')),
+           ('p', 'print', None,
+            _('print results instead of overwriting LOCAL')),
+           ('', 'no-minimal', None,
+            _('do not try to minimize conflict regions')),
+           ('h', 'help', None, _('display help and exit')),
+           ('q', 'quiet', None, _('suppress output'))]
+
+usage = _('''simplemerge [OPTS] LOCAL BASE OTHER
+
+    Simple three-way file merge utility with a minimal feature set.
+    
+    Apply to LOCAL the changes necessary to go from BASE to OTHER.
+    
+    By default, LOCAL is overwritten with the results of this operation.
+''')
+
+def showhelp():
+    sys.stdout.write(usage)
+    sys.stdout.write('\noptions:\n')
 
-    #for sr in m3.find_sync_regions():
-    #    print sr
+    out_opts = []
+    for shortopt, longopt, default, desc in options:
+        out_opts.append(('%2s%s' % (shortopt and '-%s' % shortopt,
+                                    longopt and ' --%s' % longopt),
+                         '%s' % desc))
+    opts_len = max([len(opt[0]) for opt in out_opts])
+    for first, second in out_opts:
+        sys.stdout.write(' %-*s  %s\n' % (opts_len, first, second))
+
+class ParseError(Exception):
+    """Exception raised on errors in parsing the command line."""
 
-    # sys.stdout.writelines(m3.merge_lines(name_a=argv[1], name_b=argv[3]))
-    sys.stdout.writelines(m3.merge_annotated())
-
+def main(argv):
+    try:
+        opts = {}
+        try:
+            args = fancyopts.fancyopts(argv[1:], options, opts)
+        except fancyopts.getopt.GetoptError, e:
+            raise ParseError(e)
+        if opts['help']:
+            showhelp()
+            return 0
+        if len(args) != 3:
+                raise ParseError(_('wrong number of arguments'))
+        return simplemerge(*args, **opts)
+    except ParseError, e:
+        sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
+        showhelp()
+        return 1
+    except util.Abort, e:
+        sys.stderr.write("abort: %s\n" % e)
+        return 255
+    except KeyboardInterrupt:
+        return 255
 
 if __name__ == '__main__':
     import sys
+    import os
     sys.exit(main(sys.argv))
new file mode 100755
--- /dev/null
+++ b/tests/test-simplemerge-cmd
@@ -0,0 +1,61 @@
+#!/bin/sh
+
+cp "$TESTDIR"/../contrib/simplemerge .
+
+echo base > base
+
+echo local > local
+cat base >> local
+cp local orig
+
+cat base > other
+echo other >> other
+
+echo '% changing local directly'
+python simplemerge local base other && echo "merge succeeded"
+cat local
+cp orig local
+
+echo '% printing to stdout'
+python simplemerge -p local base other
+echo ' local:'
+cat local
+
+echo '% conflicts'
+cp base conflict-local
+cp other conflict-other
+echo not other >> conflict-local
+echo end >> conflict-local
+echo end >> conflict-other
+python simplemerge -p conflict-local base conflict-other || echo "merge failed"
+
+echo '% --no-minimal'
+python simplemerge -p --no-minimal conflict-local base conflict-other
+
+echo '% 1 label'
+python simplemerge -p -L foo conflict-local base conflict-other
+
+echo '% 2 labels'
+python simplemerge -p -L foo -L bar conflict-local base conflict-other
+
+echo '% too many labels'
+python simplemerge -p -L foo -L bar -L baz conflict-local base conflict-other
+
+echo '% binary file'
+printf '\x00' > binary-local
+cat orig >> binary-local
+python simplemerge -p binary-local base other
+
+echo '% binary file --text'
+python simplemerge -a -p binary-local base other
+
+echo '% help'
+python simplemerge --help
+
+echo '% wrong number of arguments'
+python simplemerge
+
+echo '% bad option'
+python simplemerge --foo -p local base other
+
+exit 0
new file mode 100644
index 0000000000000000000000000000000000000000..8141eb588a69a7b55ec50be707781ceb376ad3dc
GIT binary patch
literal 2581
zc%1E(J#X7E5QaPJSKOklGSH4jTfiv-bTHte&Q^4w#FIprA{p{jNB#9Zl2YuXA906n
z=3q#t`*@D`k}jd?h3S;(K-)%Wkji62dMXC=9YPqJ22r9c(&wTU0Sik#`l1Bqm7y3)
z76M5-(yv}Q?fU#f35_+aRt<%0c$e3sxysJQ667TB^O{qB$m`^u&1htC6y7k<`cwhr
z=p$lkXEk@#3Z=QF5~|ABYEVWE3*3t=vCLDRX!!%vi##Q~0WE5LR=u_MzqDV%+gvXE
zd%ap*T!gHJL6~W&C2H%51B3x<^aR0SquL4RXlIugRVyR>1TD{IiO8lr16Gnf1kmb$
zAkLki#qv-IALzt+VUkzBQFxEq6+sP7V^+@B-RHagH`wkzZnki<+26wM?$hm;q5xj|
z<OtMz#Of$!oE$ik(uyWHD(boNtT|{=(CGP`A+1u5^s{k}cZp9`uEQ|-m|_5f4Tcb@
z`EzHX^>)Z0{GM3O^4v@yu@$4H*K;Zrz<x)6^oj^gritl22KM5QEx$%5hhC7fW>pl{
z#fPvipxW~0rh!3Td3dq(7?5ZCs0kjwKcEjBBj$n3AwZsc2BCG@o&bX5<Gi49?3wPA
z*)u;cXvzdaAtU0_LKI!<D|LH*m;t1PsA$SNmCWh~o&Y^|u`4dyb7UO!0w`5*nx~g+
z!brde74h!b?|xLc;DymShU3i|$?@G+!qHnMGUHIAkDS7H<A8?mxibx|FLS#}?JBju
iNUe@|X}6wgE894nuk=L4f9JL{%p<Srx~l6Ub^Qd3d`%eu