def test_merge_poem(self): """Test case from diff3 manual""" m3 = Merge3(TZU, LAO, TAO) ml = list(m3.merge_lines('LAO', 'TAO')) self.log('merge result:') self.log(''.join(ml)) self.assertEquals(ml, MERGED_RESULT)
def test_null_insert(self): m3 = Merge3([], ['aaa', 'bbb'], []) # todo: should use a sentinal at end as from get_matching_blocks # to match without zz self.assertEquals(list(m3.find_sync_regions()), [(0, 0, 2, 2, 0, 0)]) self.assertEquals(list(m3.merge_regions()), [('a', 0, 2)]) self.assertEquals(list(m3.merge_lines()), ['aaa', 'bbb'])
def test_simple_merge(self): base = ['a', 'b', 'c'] # append d a = ['a', 'b', 'c', 'd'] # remove b b = ['a', 'c'] m3 = Merge3(base, a, b) self.assertEqual(['a', 'c', 'd'], list(m3.merge_lines()))
def test_conflict(self): base = ['a', 'b', 'c'] # b -> b1 a = ['a', 'b1', 'c'] # b -> b2 b = ['a', 'b2', 'c'] m3 = Merge3(base, a, b) self.assertEqual(['a', '<<<<<<<\n', 'b1', '=======\n', 'b2', '>>>>>>>\n', 'c'], list(m3.merge_lines()))
def test_insert_agreement(self): m3 = Merge3(['aaa\n', 'bbb\n'], ['aaa\n', '222\n', 'bbb\n'], ['aaa\n', '222\n', 'bbb\n']) ml = m3.merge_lines(name_a='a', name_b='b', start_marker='<<', mid_marker='--', end_marker='>>') self.assertEquals(''.join(ml), 'aaa\n222\nbbb\n')
def test_mac_text(self): base_text = 'a\r' this_text = 'b\r' other_text = 'c\r' m3 = Merge3(base_text.splitlines(True), other_text.splitlines(True), this_text.splitlines(True)) m_lines = m3.merge_lines('OTHER', 'THIS') self.assertEqual( '<<<<<<< OTHER\rc\r=======\rb\r' '>>>>>>> THIS\r'.splitlines(True), list(m_lines))
def merge_entries(base_entries, this_entries, other_entries, guess_edits=default_guess_edits): """Merge changelog given base, this, and other versions.""" m3 = Merge3(base_entries, this_entries, other_entries, allow_objects=True) result_entries = [] at_top = True for group in m3.merge_groups(): if 'changelog_merge' in debug.debug_flags: mutter('merge group:\n%r', group) group_kind = group[0] if group_kind == 'conflict': _, base, this, other = group # Find additions new_in_other = [ entry for entry in other if entry not in base] # Find deletions deleted_in_other = [ entry for entry in base if entry not in other] if at_top and deleted_in_other: # Magic! Compare deletions and additions to try spot edits new_in_other, deleted_in_other, edits_in_other = guess_edits( new_in_other, deleted_in_other) else: # Changes not made at the top are always preserved as is, no # need to try distinguish edits from adds and deletes. edits_in_other = [] if 'changelog_merge' in debug.debug_flags: mutter('at_top: %r', at_top) mutter('new_in_other: %r', new_in_other) mutter('deleted_in_other: %r', deleted_in_other) mutter('edits_in_other: %r', edits_in_other) # Apply deletes and edits updated_this = [ entry for entry in this if entry not in deleted_in_other] for old_entry, new_entry in edits_in_other: try: index = updated_this.index(old_entry) except ValueError: # edited entry no longer present in this! Just give up and # declare a conflict. raise EntryConflict() updated_this[index] = new_entry if 'changelog_merge' in debug.debug_flags: mutter('updated_this: %r', updated_this) if at_top: # Float new entries from other to the top result_entries = new_in_other + result_entries else: result_entries.extend(new_in_other) result_entries.extend(updated_this) else: # unchanged, same, a, or b. lines = group[1] result_entries.extend(lines) at_top = False return result_entries
def test_minimal_conflicts_common(self): """Reprocessing""" base_text = ("a\n" * 20).splitlines(True) this_text = ("a\n" * 10 + "b\n" * 10).splitlines(True) other_text = ("a\n" * 10 + "c\n" + "b\n" * 8 + "c\n").splitlines(True) 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)
def test_reprocess_and_base(self): """Reprocessing and showing base breaks correctly""" base_text = ("a\n" * 20).splitlines(True) this_text = ("a\n" * 10 + "b\n" * 10).splitlines(True) other_text = ("a\n" * 10 + "c\n" + "b\n" * 8 + "c\n").splitlines(True) m3 = Merge3(base_text, other_text, this_text) m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True, base_marker='|||||||') self.assertRaises(CantReprocessAndShowBase, list, m_lines)
def test_replace_clash(self): """Both try to insert lines in the same place.""" m3 = Merge3(['aaa', '000', 'bbb'], ['aaa', '111', 'bbb'], ['aaa', '222', 'bbb']) self.assertEquals(m3.find_unconflicted(), [(0, 1), (2, 3)]) self.assertEquals(list(m3.find_sync_regions()), [ (0, 1, 0, 1, 0, 1), (2, 3, 2, 3, 2, 3), (3, 3, 3, 3, 3, 3), ])
def test_merge3_cherrypick(self): base_text = "a\nb\n" this_text = "a\n" other_text = "a\nb\nc\n" # When cherrypicking, lines in base are not part of the conflict m3 = Merge3(base_text.splitlines(True), this_text.splitlines(True), other_text.splitlines(True), is_cherrypick=True) m_lines = m3.merge_lines() self.assertEqualDiff('a\n<<<<<<<\n=======\nc\n>>>>>>>\n', ''.join(m_lines)) # This is not symmetric m3 = Merge3(base_text.splitlines(True), other_text.splitlines(True), this_text.splitlines(True), is_cherrypick=True) m_lines = m3.merge_lines() self.assertEqualDiff('a\n<<<<<<<\nb\nc\n=======\n>>>>>>>\n', ''.join(m_lines))
def _merge_if_needed(self, base_revision, new_body): if self.revision == base_revision: return new_body base = WikiPageRevision.query( WikiPageRevision.title == self.title, WikiPageRevision.revision == base_revision).get().body merged = ''.join(Merge3(base, self.body, new_body).merge_lines()) conflicted = len(re.findall(PageOperationMixin.re_conflicted, merged)) > 0 if conflicted: raise ConflictError('Conflicted', base, new_body, merged) return merged
def test_replace_multi(self): """Replacement with regions of different size.""" m3 = Merge3(['aaa', '000', '000', 'bbb'], ['aaa', '111', '111', '111', 'bbb'], ['aaa', '222', '222', '222', '222', 'bbb']) self.assertEquals(m3.find_unconflicted(), [(0, 1), (3, 4)]) self.assertEquals(list(m3.find_sync_regions()), [ (0, 1, 0, 1, 0, 1), (3, 4, 4, 5, 5, 6), (4, 4, 5, 5, 6, 6), ])
def test_no_changes(self): """No conflicts because nothing changed""" m3 = Merge3(['aaa', 'bbb'], ['aaa', 'bbb'], ['aaa', 'bbb']) self.assertEquals(m3.find_unconflicted(), [(0, 2)]) self.assertEquals(list(m3.find_sync_regions()), [(0, 2, 0, 2, 0, 2), (2, 2, 2, 2, 2, 2)]) self.assertEquals(list(m3.merge_regions()), [('unchanged', 0, 2)]) self.assertEquals(list(m3.merge_groups()), [('unchanged', ['aaa', 'bbb'])])
def test_front_insert(self): m3 = Merge3(['zz'], ['aaa', 'bbb', 'zz'], ['zz']) # todo: should use a sentinal at end as from get_matching_blocks # to match without zz self.assertEquals(list(m3.find_sync_regions()), [ (0, 1, 2, 3, 0, 1), (1, 1, 3, 3, 1, 1), ]) self.assertEquals(list(m3.merge_regions()), [('a', 0, 2), ('unchanged', 0, 1)]) self.assertEquals(list(m3.merge_groups()), [('a', ['aaa', 'bbb']), ('unchanged', ['zz'])])
def test_no_conflicts(self): """No conflicts because only one side changed""" m3 = Merge3(['aaa', 'bbb'], ['aaa', '111', 'bbb'], ['aaa', 'bbb']) self.assertEquals(m3.find_unconflicted(), [(0, 1), (1, 2)]) self.assertEquals(list(m3.find_sync_regions()), [ (0, 1, 0, 1, 0, 1), (1, 2, 2, 3, 1, 2), (2, 2, 3, 3, 2, 2), ]) self.assertEquals(list(m3.merge_regions()), [ ('unchanged', 0, 1), ('a', 1, 2), ('unchanged', 1, 2), ])
def test_minimal_conflicts_nonunique(self): def add_newline(s): """Add a newline to each entry in the string""" return [(x + '\n') for x in s] base_text = add_newline("abacddefgghij") this_text = add_newline("abacddefgghijkalmontfprz") other_text = add_newline("abacddefgghijknlmontfprd") 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 = ''.join( add_newline("abacddefgghijk") + ["<<<<<<< OTHER\nn\n=======\na\n>>>>>>> THIS\n"] + add_newline('lmontfpr') + ["<<<<<<< OTHER\nd\n=======\nz\n>>>>>>> THIS\n"]) self.assertEqualDiff(optimal_text, merged_text)
def test_minimal_conflicts_unique(self): def add_newline(s): """Add a newline to each entry in the string""" return [(x + '\n') for x in s] base_text = add_newline("abcdefghijklm") this_text = add_newline("abcdefghijklmNOPQRSTUVWXYZ") other_text = add_newline("abcdefghijklm1OPQRSTUVWXY2") 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 = ''.join( add_newline("abcdefghijklm") + ["<<<<<<< OTHER\n1\n=======\nN\n>>>>>>> THIS\n"] + add_newline('OPQRSTUVWXY') + ["<<<<<<< OTHER\n2\n=======\nZ\n>>>>>>> THIS\n"]) self.assertEqualDiff(optimal_text, merged_text)
def test_append_clash(self): m3 = Merge3(['aaa\n', 'bbb\n'], ['aaa\n', 'bbb\n', '222\n'], ['aaa\n', 'bbb\n', '333\n']) ml = m3.merge_lines(name_a='a', name_b='b', start_marker='<<', mid_marker='--', end_marker='>>') self.assertEquals(''.join(ml), '''\ aaa bbb << a 222 -- 333 >> b ''')
def test_merge3_cherrypick_w_mixed(self): base_text = 'a\nb\nc\nd\ne\n' this_text = 'a\nb\nq\n' other_text = 'a\nb\nc\nd\nf\ne\ng\n' # When cherrypicking, lines in base are not part of the conflict m3 = Merge3(base_text.splitlines(True), this_text.splitlines(True), other_text.splitlines(True), is_cherrypick=True) m_lines = m3.merge_lines() self.assertEqualDiff( 'a\n' 'b\n' '<<<<<<<\n' 'q\n' '=======\n' 'f\n' '>>>>>>>\n' '<<<<<<<\n' '=======\n' 'g\n' '>>>>>>>\n', ''.join(m_lines))
def test_insert_clash(self): """Both try to insert lines in the same place.""" m3 = Merge3(['aaa\n', 'bbb\n'], ['aaa\n', '111\n', 'bbb\n'], ['aaa\n', '222\n', 'bbb\n']) self.assertEquals(m3.find_unconflicted(), [(0, 1), (1, 2)]) self.assertEquals(list(m3.find_sync_regions()), [ (0, 1, 0, 1, 0, 1), (1, 2, 2, 3, 2, 3), (2, 2, 3, 3, 3, 3), ]) self.assertEquals(list(m3.merge_regions()), [('unchanged', 0, 1), ('conflict', 1, 1, 1, 2, 1, 2), ('unchanged', 1, 2)]) self.assertEquals(list(m3.merge_groups()), [ ('unchanged', ['aaa\n']), ('conflict', [], ['111\n'], ['222\n']), ('unchanged', ['bbb\n']), ]) ml = m3.merge_lines(name_a='a', name_b='b', start_marker='<<', mid_marker='--', end_marker='>>') self.assertEquals(''.join(ml), '''aaa << a 111 -- 222 >> b bbb ''')
def test_append_agreement(self): m3 = Merge3(['aaa\n', 'bbb\n'], ['aaa\n', 'bbb\n', '222\n'], ['aaa\n', 'bbb\n', '222\n']) self.assertEquals(''.join(m3.merge_lines()), 'aaa\nbbb\n222\n')