# HG changeset patch # User Alexis S. L. Carvalho # Date 1176765459 10800 # Node ID d5c3a70f8422359bfd611cf5b8a3608a2fe878f7 # Parent 2e3c54fb79a359665ea2fe669db188e608898d50 polish the simplemerge command; add a test diff --git a/contrib/simplemerge b/contrib/simplemerge --- 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)) diff --git a/tests/test-simplemerge-cmd b/tests/test-simplemerge-cmd 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 diff --git a/tests/test-simplemerge-cmd.out b/tests/test-simplemerge-cmd.out 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)Z0{GM3O^4v@yu@$4H*K;Zrzpl{ z#fPvipxW~0rh!3Td3dq(7?5ZCs0kjwKcEjBBj$n3AwZsc2BCG@o&bX5