comparison contrib/simplemerge @ 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 1ef4445c6506
comparison
equal deleted inserted replaced
4359:2e3c54fb79a3 4360:d5c3a70f8422
17 17
18 18
19 # mbp: "you know that thing where cvs gives you conflict markers?" 19 # mbp: "you know that thing where cvs gives you conflict markers?"
20 # s: "i hate that." 20 # s: "i hate that."
21 21
22 22 from mercurial import demandimport
23 from mercurial import util, mdiff 23 demandimport.enable()
24
25 from mercurial import util, mdiff, fancyopts
24 from mercurial.i18n import _ 26 from mercurial.i18n import _
25 27
26 28
27 class CantReprocessAndShowBase(Exception): 29 class CantReprocessAndShowBase(Exception):
28 pass 30 pass
95 end_marker='>>>>>>>', 97 end_marker='>>>>>>>',
96 base_marker=None, 98 base_marker=None,
97 reprocess=False): 99 reprocess=False):
98 """Return merge in cvs-like form. 100 """Return merge in cvs-like form.
99 """ 101 """
102 self.conflicts = False
100 newline = '\n' 103 newline = '\n'
101 if len(self.a) > 0: 104 if len(self.a) > 0:
102 if self.a[0].endswith('\r\n'): 105 if self.a[0].endswith('\r\n'):
103 newline = '\r\n' 106 newline = '\r\n'
104 elif self.a[0].endswith('\r'): 107 elif self.a[0].endswith('\r'):
124 yield self.a[i] 127 yield self.a[i]
125 elif what == 'b': 128 elif what == 'b':
126 for i in range(t[1], t[2]): 129 for i in range(t[1], t[2]):
127 yield self.b[i] 130 yield self.b[i]
128 elif what == 'conflict': 131 elif what == 'conflict':
132 self.conflicts = True
129 yield start_marker + newline 133 yield start_marker + newline
130 for i in range(t[3], t[4]): 134 for i in range(t[3], t[4]):
131 yield self.a[i] 135 yield self.a[i]
132 if base_marker is not None: 136 if base_marker is not None:
133 yield base_marker + newline 137 yield base_marker + newline
437 if util.binary(basetext) or util.binary(atext) or util.binary(btext): 441 if util.binary(basetext) or util.binary(atext) or util.binary(btext):
438 raise util.Abort(_("don't know how to merge binary files")) 442 raise util.Abort(_("don't know how to merge binary files"))
439 Merge3Text.__init__(self, basetext, atext, btext, base, a, b) 443 Merge3Text.__init__(self, basetext, atext, btext, base, a, b)
440 444
441 445
446 def simplemerge(local, base, other, **opts):
447 def readfile(filename):
448 f = open(filename, "rb")
449 text = f.read()
450 f.close()
451 if util.binary(text):
452 msg = _("%s looks like a binary file.") % filename
453 if not opts.get('text'):
454 raise util.Abort(msg)
455 elif not opts.get('quiet'):
456 sys.stderr.write(_('warning: %s\n') % msg)
457 return text
458
459 name_a = local
460 name_b = other
461 labels = opts.get('label', [])
462 if labels:
463 name_a = labels.pop(0)
464 if labels:
465 name_b = labels.pop(0)
466 if labels:
467 raise util.Abort(_("can only specify two labels."))
468
469 localtext = readfile(local)
470 basetext = readfile(base)
471 othertext = readfile(other)
472
473 orig = local
474 local = os.path.realpath(local)
475 if not opts.get('print'):
476 opener = util.opener(os.path.dirname(local))
477 out = opener(os.path.basename(local), "w", atomictemp=True)
478 else:
479 out = sys.stdout
480
481 reprocess = not opts.get('no_minimal')
482
483 m3 = Merge3Text(basetext, localtext, othertext)
484 for line in m3.merge_lines(name_a=name_a, name_b=name_b,
485 reprocess=reprocess):
486 out.write(line)
487
488 if not opts.get('print'):
489 out.rename()
490
491 if m3.conflicts:
492 if not opts.get('quiet'):
493 sys.stdout.flush()
494 sys.stderr.write(_("warning: conflicts during merge.\n"))
495 return 1
496
497 options = [('L', 'label', [], _('labels to use on conflict markers')),
498 ('a', 'text', None, _('treat all files as text')),
499 ('p', 'print', None,
500 _('print results instead of overwriting LOCAL')),
501 ('', 'no-minimal', None,
502 _('do not try to minimize conflict regions')),
503 ('h', 'help', None, _('display help and exit')),
504 ('q', 'quiet', None, _('suppress output'))]
505
506 usage = _('''simplemerge [OPTS] LOCAL BASE OTHER
507
508 Simple three-way file merge utility with a minimal feature set.
509
510 Apply to LOCAL the changes necessary to go from BASE to OTHER.
511
512 By default, LOCAL is overwritten with the results of this operation.
513 ''')
514
515 def showhelp():
516 sys.stdout.write(usage)
517 sys.stdout.write('\noptions:\n')
518
519 out_opts = []
520 for shortopt, longopt, default, desc in options:
521 out_opts.append(('%2s%s' % (shortopt and '-%s' % shortopt,
522 longopt and ' --%s' % longopt),
523 '%s' % desc))
524 opts_len = max([len(opt[0]) for opt in out_opts])
525 for first, second in out_opts:
526 sys.stdout.write(' %-*s %s\n' % (opts_len, first, second))
527
528 class ParseError(Exception):
529 """Exception raised on errors in parsing the command line."""
530
442 def main(argv): 531 def main(argv):
443 # as for diff3 and meld the syntax is "MINE BASE OTHER" 532 try:
444 a = file(argv[1], 'rt').readlines() 533 opts = {}
445 base = file(argv[2], 'rt').readlines() 534 try:
446 b = file(argv[3], 'rt').readlines() 535 args = fancyopts.fancyopts(argv[1:], options, opts)
447 536 except fancyopts.getopt.GetoptError, e:
448 m3 = Merge3(base, a, b) 537 raise ParseError(e)
449 538 if opts['help']:
450 #for sr in m3.find_sync_regions(): 539 showhelp()
451 # print sr 540 return 0
452 541 if len(args) != 3:
453 # sys.stdout.writelines(m3.merge_lines(name_a=argv[1], name_b=argv[3])) 542 raise ParseError(_('wrong number of arguments'))
454 sys.stdout.writelines(m3.merge_annotated()) 543 return simplemerge(*args, **opts)
455 544 except ParseError, e:
545 sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
546 showhelp()
547 return 1
548 except util.Abort, e:
549 sys.stderr.write("abort: %s\n" % e)
550 return 255
551 except KeyboardInterrupt:
552 return 255
456 553
457 if __name__ == '__main__': 554 if __name__ == '__main__':
458 import sys 555 import sys
556 import os
459 sys.exit(main(sys.argv)) 557 sys.exit(main(sys.argv))