changeset 4359:2e3c54fb79a3

actually port simplemerge to hg - use bdiff instead of patiencediff; this is a larger change, since bdiff works on 2 multi-line strings, while patiencediff works on 2 lists; - rename the main class from Merge3 to Merge3Text and add a Merge3 class that derives from Merge3Text. This new Merge3 class has the same interface from the original class, so that the tests still work; - Merge3 uses util.binary to detect binary data and raises util.Abort instead of a specific exception; - don't use the @decorator syntax, to keep python2.3 compatibility; - the test uses unittest, which likes to print how long it took to run. This obviously doesn't play too well with hg's test suite, so we override time.time to fool unittest; - one test has a different (but still valid) output because of the different diff algorithm used; - the TestCase class used by bzr has some extras to help debugging. test-merge3.py used 2 of them: - log method to log some data - assertEqualDiff method to ease viewing diffs of diffs We add a dummy log method and use regular assertEquals instead of assertEqualDiff. - make simplemerge executable and add "#!/usr/bin/env python" header
author Alexis S. L. Carvalho <alexis@cecm.usp.br>
date Mon, 16 Apr 2007 20:17:39 -0300
parents 465b9ea02868
children d5c3a70f8422
files contrib/simplemerge tests/test-simplemerge.py tests/test-simplemerge.py.out
diffstat 3 files changed, 82 insertions(+), 32 deletions(-) [+]
line wrap: on
line diff
old mode 100644
new mode 100755
--- a/contrib/simplemerge
+++ b/contrib/simplemerge
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
 # Copyright (C) 2004, 2005 Canonical Ltd
 #
 # This program is free software; you can redistribute it and/or modify
@@ -19,9 +20,12 @@
 # s: "i hate that."
 
 
-from bzrlib.errors import CantReprocessAndShowBase
-import bzrlib.patiencediff
-from bzrlib.textfile import check_text_lines
+from mercurial import util, mdiff
+from mercurial.i18n import _
+
+
+class CantReprocessAndShowBase(Exception):
+    pass
 
 
 def intersect(ra, rb):
@@ -61,16 +65,21 @@ def compare_range(a, astart, aend, b, bs
 
 
 
-class Merge3(object):
+class Merge3Text(object):
     """3-way merge of texts.
 
-    Given BASE, OTHER, THIS, tries to produce a combined text
-    incorporating the changes from both BASE->OTHER and BASE->THIS.
-    All three will typically be sequences of lines."""
-    def __init__(self, base, a, b):
-        check_text_lines(base)
-        check_text_lines(a)
-        check_text_lines(b)
+    Given strings BASE, OTHER, THIS, tries to produce a combined text
+    incorporating the changes from both BASE->OTHER and BASE->THIS."""
+    def __init__(self, basetext, atext, btext, base=None, a=None, b=None):
+        self.basetext = basetext
+        self.atext = atext
+        self.btext = btext
+        if base is None:
+            base = mdiff.splitnewlines(basetext)
+        if a is None:
+            a = mdiff.splitnewlines(atext)
+        if b is None:
+            b = mdiff.splitnewlines(btext)
         self.base = base
         self.a = a
         self.b = b
@@ -300,8 +309,8 @@ class Merge3(object):
             type, iz, zmatch, ia, amatch, ib, bmatch = region
             a_region = self.a[ia:amatch]
             b_region = self.b[ib:bmatch]
-            matches = bzrlib.patiencediff.PatienceSequenceMatcher(
-                    None, a_region, b_region).get_matching_blocks()
+            matches = mdiff.get_matching_blocks(''.join(a_region),
+                                                ''.join(b_region))
             next_a = ia
             next_b = ib
             for region_ia, region_ib, region_len in matches[:-1]:
@@ -319,10 +328,10 @@ class Merge3(object):
                 yield reg
 
 
-    @staticmethod
     def mismatch_region(next_a, region_ia,  next_b, region_ib):
         if next_a < region_ia or next_b < region_ib:
             return 'conflict', None, None, next_a, region_ia, next_b, region_ib
+    mismatch_region = staticmethod(mismatch_region)
             
 
     def find_sync_regions(self):
@@ -333,10 +342,8 @@ class Merge3(object):
         """
 
         ia = ib = 0
-        amatches = bzrlib.patiencediff.PatienceSequenceMatcher(
-                None, self.base, self.a).get_matching_blocks()
-        bmatches = bzrlib.patiencediff.PatienceSequenceMatcher(
-                None, self.base, self.b).get_matching_blocks()
+        amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
+        bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
         len_a = len(amatches)
         len_b = len(bmatches)
 
@@ -392,10 +399,8 @@ class Merge3(object):
 
     def find_unconflicted(self):
         """Return a list of ranges in base that are not conflicted."""
-        am = bzrlib.patiencediff.PatienceSequenceMatcher(
-                None, self.base, self.a).get_matching_blocks()
-        bm = bzrlib.patiencediff.PatienceSequenceMatcher(
-                None, self.base, self.b).get_matching_blocks()
+        am = mdiff.get_matching_blocks(self.basetext, self.atext)
+        bm = mdiff.get_matching_blocks(self.basetext, self.btext)
 
         unc = []
 
@@ -418,6 +423,22 @@ class Merge3(object):
         return unc
 
 
+# bzr compatible interface, for the tests
+class Merge3(Merge3Text):
+    """3-way merge of texts.
+
+    Given BASE, OTHER, THIS, tries to produce a combined text
+    incorporating the changes from both BASE->OTHER and BASE->THIS.
+    All three will typically be sequences of lines."""
+    def __init__(self, base, a, b):
+        basetext = '\n'.join([i.strip('\n') for i in base] + [''])
+        atext = '\n'.join([i.strip('\n') for i in a] + [''])
+        btext = '\n'.join([i.strip('\n') for i in b] + [''])
+        if util.binary(basetext) or util.binary(atext) or util.binary(btext):
+            raise util.Abort(_("don't know how to merge binary files"))
+        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()
--- a/tests/test-simplemerge.py
+++ b/tests/test-simplemerge.py
@@ -14,10 +14,20 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
+import os
+import unittest
+from unittest import TestCase
+import imp
+import shutil
+from mercurial import util
 
-from bzrlib.tests import TestCaseInTempDir, TestCase
-from bzrlib.merge3 import Merge3
-from bzrlib.errors import CantReprocessAndShowBase, BinaryFile
+# copy simplemerge to the cwd to avoid creating a .pyc file in the source tree
+shutil.copyfile(os.path.join(os.environ['TESTDIR'], os.path.pardir,
+                             'contrib', 'simplemerge'),
+                'simplemerge.py')
+simplemerge = imp.load_source('simplemerge', 'simplemerge.py')
+Merge3 = simplemerge.Merge3
+CantReprocessAndShowBase = simplemerge.CantReprocessAndShowBase
 
 def split_lines(t):
     from cStringIO import StringIO
@@ -92,6 +102,8 @@ MERGED_RESULT = split_lines("""     The 
 """)
 
 class TestMerge3(TestCase):
+    def log(self, msg):
+        pass
 
     def test_no_changes(self):
         """No conflicts because nothing changed"""
@@ -310,10 +322,11 @@ bbb
         m3 = Merge3(base_text, other_text, this_text)
         m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
         merged_text = "".join(list(m_lines))
-        optimal_text = ("a\n" * 10 + "<<<<<<< OTHER\nc\n"
-            + 8* "b\n" + "c\n=======\n"
-            + 10*"b\n" + ">>>>>>> THIS\n")
-        self.assertEqualDiff(optimal_text, merged_text)
+        optimal_text = ("a\n" * 10 + "<<<<<<< OTHER\nc\n=======\n"
+            + ">>>>>>> THIS\n"
+            + 8* "b\n" + "<<<<<<< OTHER\nc\n=======\n"
+            + 2* "b\n" + ">>>>>>> THIS\n")
+        self.assertEquals(optimal_text, merged_text)
 
     def test_minimal_conflicts_unique(self):
         def add_newline(s):
@@ -331,7 +344,7 @@ bbb
             + add_newline('OPQRSTUVWXY')
             + ["<<<<<<< OTHER\n2\n=======\nZ\n>>>>>>> THIS\n"]
             )
-        self.assertEqualDiff(optimal_text, merged_text)
+        self.assertEquals(optimal_text, merged_text)
 
     def test_minimal_conflicts_nonunique(self):
         def add_newline(s):
@@ -349,7 +362,7 @@ bbb
             + add_newline('lmontfpr')
             + ["<<<<<<< OTHER\nd\n=======\nz\n>>>>>>> THIS\n"]
             )
-        self.assertEqualDiff(optimal_text, merged_text)
+        self.assertEquals(optimal_text, merged_text)
 
     def test_reprocess_and_base(self):
         """Reprocessing and showing base breaks correctly"""
@@ -362,7 +375,7 @@ bbb
         self.assertRaises(CantReprocessAndShowBase, list, m_lines)
 
     def test_binary(self):
-        self.assertRaises(BinaryFile, Merge3, ['\x00'], ['a'], ['b'])
+        self.assertRaises(util.Abort, Merge3, ['\x00'], ['a'], ['b'])
 
     def test_dos_text(self):
         base_text = 'a\r\n'
@@ -383,3 +396,14 @@ bbb
         m_lines = m3.merge_lines('OTHER', 'THIS')
         self.assertEqual('<<<<<<< OTHER\rc\r=======\rb\r'
             '>>>>>>> THIS\r'.splitlines(True), list(m_lines))
+
+if __name__ == '__main__':
+    # hide the timer
+    import time
+    orig = time.time
+    try:
+        time.time = lambda: 0
+        unittest.main()
+    finally:
+        time.time = orig
+
new file mode 100644
--- /dev/null
+++ b/tests/test-simplemerge.py.out
@@ -0,0 +1,5 @@
+....................
+----------------------------------------------------------------------
+Ran 20 tests in 0.000s
+
+OK