예제 #1
0
    def test_append_agreement(self):
        m3 = merge3.Merge3(['aaa\n', 'bbb\n'],
                           ['aaa\n', 'bbb\n', '222\n'],
                           ['aaa\n', 'bbb\n', '222\n'])

        self.assertEqual(''.join(m3.merge_lines()),
                         'aaa\nbbb\n222\n')
예제 #2
0
 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.Merge3(base_text, other_text, this_text)
     m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True,
                              base_marker='|||||||')
     self.assertRaises(merge3.CantReprocessAndShowBase, list, m_lines)
예제 #3
0
 def test_merge_poem_bytes(self):
     """Test case from diff3 manual"""
     m3 = merge3.Merge3(
         [line.encode() for line in TZU],
         [line.encode() for line in LAO],
         [line.encode() for line in TAO])
     ml = list(m3.merge_lines('LAO', 'TAO'))
     self.assertEqual(
         ml, [line.encode() for line in MERGED_RESULT])
예제 #4
0
    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.Merge3(base_text.splitlines(True),
                           this_text.splitlines(True),
                           other_text.splitlines(True), is_cherrypick=True)
        m_lines = m3.merge_lines()
        self.assertEqual('a\n<<<<<<<\n=======\nc\n>>>>>>>\n',
                         ''.join(m_lines))

        # This is not symmetric
        m3 = merge3.Merge3(base_text.splitlines(True),
                           other_text.splitlines(True),
                           this_text.splitlines(True), is_cherrypick=True)
        m_lines = m3.merge_lines()
        self.assertEqual('a\n<<<<<<<\nb\nc\n=======\n>>>>>>>\n',
                         ''.join(m_lines))
예제 #5
0
    def test_insert_agreement(self):
        m3 = merge3.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.assertEqual(''.join(ml), 'aaa\n222\nbbb\n')
예제 #6
0
 def test_mac_text(self):
     base_text = 'a\r'
     this_text = 'b\r'
     other_text = 'c\r'
     m3 = merge3.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))
예제 #7
0
    def test_replace_multi(self):
        """Replacement with regions of different size."""
        m3 = merge3.Merge3([b'aaa', b'000', b'000', b'bbb'],
                           [b'aaa', b'111', b'111', b'111', b'bbb'],
                           [b'aaa', b'222', b'222', b'222', b'222', b'bbb'])

        self.assertEqual(m3.find_unconflicted(),
                         [(0, 1), (3, 4)])

        self.assertEqual(list(m3.find_sync_regions()),
                         [(0, 1, 0, 1, 0, 1),
                          (3, 4, 4, 5, 5, 6),
                          (4, 4, 5, 5, 6, 6), ])
예제 #8
0
    def test_replace_clash(self):
        """Both try to insert lines in the same place."""
        m3 = merge3.Merge3(['aaa', '000', 'bbb'],
                           ['aaa', '111', 'bbb'],
                           ['aaa', '222', 'bbb'])

        self.assertEqual(m3.find_unconflicted(),
                         [(0, 1), (2, 3)])

        self.assertEqual(list(m3.find_sync_regions()),
                         [(0, 1, 0, 1, 0, 1),
                          (2, 3, 2, 3, 2, 3),
                          (3, 3, 3, 3, 3, 3), ])
예제 #9
0
    def test_cherrypick(self):
        base_text = "ba\nb\n"
        this_text = "ba\n"
        other_text = "a\nb\nc\n"

        m3 = merge3.Merge3(
            base_text.splitlines(True),
            other_text.splitlines(True),
            this_text.splitlines(True))

        self.assertEqual(m3.find_unconflicted(), [])

        self.assertEqual(list(m3.find_sync_regions()), [(2, 2, 3, 3, 1, 1)])
예제 #10
0
    def test_null_insert(self):
        m3 = merge3.Merge3([],
                           ['aaa', 'bbb'],
                           [])
        # todo: should use a sentinal at end as from get_matching_blocks
        # to match without zz
        self.assertEqual(list(m3.find_sync_regions()),
                         [(0, 0, 2, 2, 0, 0)])

        self.assertEqual(list(m3.merge_regions()),
                         [('a', 0, 2)])

        self.assertEqual(list(m3.merge_lines()),
                         ['aaa', 'bbb'])
예제 #11
0
 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.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"
         "=======\n" + ">>>>>>> THIS\n" + 8 * "b\n" +
         "<<<<<<< OTHER\nc\n" + "=======\n" + 2 * "b\n" +
         ">>>>>>> THIS\n")
     self.assertEqual(optimal_text, merged_text)
예제 #12
0
    def merge_text(self, params):
        """Perform a simple 3-way merge of a bzr NEWS file.

        Each section of a bzr NEWS file is essentially an ordered set of bullet
        points, so we can simply take a set of bullet points, determine which
        bullets to add and which to remove, sort, and reserialize.
        """
        # Transform the different versions of the NEWS file into a bunch of
        # text lines where each line matches one part of the overall
        # structure, e.g. a heading or bullet.
        this_lines = list(simple_parse_lines(params.this_lines))
        other_lines = list(simple_parse_lines(params.other_lines))
        base_lines = list(simple_parse_lines(params.base_lines))
        m3 = merge3.Merge3(base_lines,
                           this_lines,
                           other_lines,
                           allow_objects=True)
        result_chunks = []
        for group in m3.merge_groups():
            if group[0] == 'conflict':
                _, base, a, b = group
                # Are all the conflicting lines bullets?  If so, we can merge
                # this.
                for line_set in [base, a, b]:
                    for line in line_set:
                        if line[0] != 'bullet':
                            # Something else :(
                            # Maybe the default merge can cope.
                            return 'not_applicable', None
                # Calculate additions and deletions.
                new_in_a = set(a).difference(base)
                new_in_b = set(b).difference(base)
                all_new = new_in_a.union(new_in_b)
                deleted_in_a = set(base).difference(a)
                deleted_in_b = set(base).difference(b)
                # Combine into the final set of bullet points.
                final = all_new.difference(deleted_in_a).difference(
                    deleted_in_b)
                # Sort, and emit.
                final = sorted(final, key=sort_key)
                result_chunks.extend(final)
            else:
                result_chunks.extend(group[1])
        # Transform the merged elements back into real blocks of lines.
        result_lines = '\n\n'.join(chunk[1] for chunk in result_chunks)
        return 'success', result_lines
예제 #13
0
    def test_no_changes(self):
        """No conflicts because nothing changed"""
        m3 = merge3.Merge3(['aaa', 'bbb'],
                           ['aaa', 'bbb'],
                           ['aaa', 'bbb'])

        self.assertEqual(m3.find_unconflicted(), [(0, 2)])

        self.assertEqual(
                list(m3.find_sync_regions()),
                [(0, 2, 0, 2, 0, 2), (2, 2, 2, 2, 2, 2)])

        self.assertEqual(list(m3.merge_regions()),
                         [('unchanged', 0, 2)])

        self.assertEqual(list(m3.merge_groups()),
                         [('unchanged', ['aaa', 'bbb'])])
예제 #14
0
    def test_front_insert(self):
        m3 = merge3.Merge3([b'zz'],
                           [b'aaa', b'bbb', b'zz'],
                           [b'zz'])

        # todo: should use a sentinal at end as from get_matching_blocks
        # to match without zz
        self.assertEqual(list(m3.find_sync_regions()),
                         [(0, 1, 2, 3, 0, 1),
                          (1, 1, 3, 3, 1, 1), ])

        self.assertEqual(list(m3.merge_regions()),
                         [('a', 0, 2),
                          ('unchanged', 0, 1)])

        self.assertEqual(list(m3.merge_groups()),
                         [('a', [b'aaa', b'bbb']),
                          ('unchanged', [b'zz'])])
예제 #15
0
    def test_no_conflicts(self):
        """No conflicts because only one side changed"""
        m3 = merge3.Merge3(['aaa', 'bbb'],
                           ['aaa', '111', 'bbb'],
                           ['aaa', 'bbb'])

        self.assertEqual(m3.find_unconflicted(),
                         [(0, 1), (1, 2)])

        self.assertEqual(list(m3.find_sync_regions()),
                         [(0, 1, 0, 1, 0, 1),
                          (1, 2, 2, 3, 1, 2),
                          (2, 2, 3, 3, 2, 2), ])

        self.assertEqual(list(m3.merge_regions()),
                         [('unchanged', 0, 1),
                          ('a', 1, 2),
                          ('unchanged', 1, 2), ])
예제 #16
0
    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.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.assertEqual(optimal_text, merged_text)
예제 #17
0
    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.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.assertEqual(optimal_text, merged_text)
예제 #18
0
    def test_append_clash(self):
        m3 = merge3.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.assertEqual(''.join(ml), '''\
aaa
bbb
<< a
222
--
333
>> b
''')
예제 #19
0
def merge3_has_conflict(mine, base, your, reprocess=False):
    """ Reimplementing Merge3.merge_lines to return a has conflict """

    had_conflict = False
    results = []

    merge = merge3.Merge3(base, mine, your)

    # Workout the new line standard to use
    newline = '\n'
    if len(base) > 0:
        if base[0].endswith('\r\n'):
            newline = '\r\n'
        elif base[0].endswith('\r'):
            newline = '\r'

    start_marker = '<<<<<<< local'
    mid_marker = '======='
    end_marker = '>>>>>>> remote'

    merge_regions = merge.merge_regions()
    if reprocess is True:
        merge_regions = merge.reprocess_merge_regions(merge_regions)
    for t in merge_regions:
        what = t[0]
        if what == 'unchanged':
            results.extend(base[t[1]:t[2]])
        elif what == 'a' or what == 'same':
            results.extend(mine[t[1]:t[2]])
        elif what == 'b':
            results.extend(your[t[1]:t[2]])
        elif what == 'conflict':
            results.append(start_marker + newline)
            results.extend(mine[t[1]:t[2]])
            results.append(mid_marker + newline)
            results.extend(your[t[1]:t[2]])
            results.append(end_marker + newline)
            had_conflict = True
        else:
            raise ValueError(what)

    return had_conflict, results
예제 #20
0
 def test_minimal_conflicts_common_with_patiencediff(self):
     """Reprocessing"""
     try:
         import patiencediff
     except ImportError:
         self.skipTest('patiencediff not available')
     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.Merge3(
         base_text, other_text, this_text,
         sequence_matcher=patiencediff.PatienceSequenceMatcher)
     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.assertEqual(optimal_text, merged_text)
예제 #21
0
    def test_allow_objects(self):
        """Objects other than strs may be used with Merge3.

        merge_groups and merge_regions work with non-str input.  Methods that
        return lines like merge_lines fail.
        """
        base = [(int2byte(x), int2byte(x)) for x in bytearray(b'abcde')]
        a = [(int2byte(x), int2byte(x)) for x in bytearray(b'abcdef')]
        b = [(int2byte(x), int2byte(x)) for x in bytearray(b'Zabcde')]
        m3 = merge3.Merge3(base, a, b)
        self.assertEqual(
            [('b', 0, 1),
             ('unchanged', 0, 5),
             ('a', 5, 6)],
            list(m3.merge_regions()))
        self.assertEqual(
            [('b', [(b'Z', b'Z')]),
             ('unchanged', [
                 (int2byte(x), int2byte(x)) for x in bytearray(b'abcde')]),
             ('a', [(b'f', b'f')])],
            list(m3.merge_groups()))
예제 #22
0
 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.Merge3(base_text.splitlines(True),
                        this_text.splitlines(True),
                        other_text.splitlines(True), is_cherrypick=True)
     m_lines = m3.merge_lines()
     self.assertEqual('a\n'
                      'b\n'
                      '<<<<<<<\n'
                      'q\n'
                      '=======\n'
                      'f\n'
                      '>>>>>>>\n'
                      '<<<<<<<\n'
                      '=======\n'
                      'g\n'
                      '>>>>>>>\n',
                      ''.join(m_lines))
예제 #23
0
    def test_insert_clash(self):
        """Both try to insert lines in the same place."""
        m3 = merge3.Merge3(['aaa\n', 'bbb\n'],
                           ['aaa\n', '111\n', 'bbb\n'],
                           ['aaa\n', '222\n', 'bbb\n'])

        self.assertEqual(m3.find_unconflicted(),
                         [(0, 1), (1, 2)])

        self.assertEqual(list(m3.find_sync_regions()),
                         [(0, 1, 0, 1, 0, 1),
                          (1, 2, 2, 3, 2, 3),
                          (2, 2, 3, 3, 3, 3), ])

        self.assertEqual(list(m3.merge_regions()),
                         [('unchanged', 0, 1),
                          ('conflict', 1, 1, 1, 2, 1, 2),
                          ('unchanged', 1, 2)])

        self.assertEqual(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.assertEqual(''.join(ml), '''\
aaa
<< a
111
--
222
>> b
bbb
''')
예제 #24
0
def resolve(name, base_version, latest_version):
    new = getformslot('content')
    base = content.get(name, rcstore.MARKDOWN, base_version) or ''
    latest = content.get(name, rcstore.MARKDOWN, latest_version) or ''
    m = merge3.Merge3(lines(base), lines(new), lines(latest))
    mg = list(m.merge_groups())
    conflicts = 0
    for g in mg:
        if g[0] == 'conflict': conflicts += 1
        pass
    merged = ''.join(
        m.merge_lines(start_marker='\n!!!--Conflict--!!!\n!--Your version--',
                      mid_marker='\n!--Other version--',
                      end_marker='\n!--End conflict--\n'))
    d = {
        'name': name,
        'md_content': merged,
        'spath': spath(),
        'base_version': latest_version,
        'helptext': helptext,
        'msg': conflict_msg if conflicts else merge_msg
    }
    return HTMLString(template % d)
예제 #25
0
async def syncGitToPijulCommit(git, pijul, commit, branch):
    # Check whether Pijul repo has this commit imported already
    # Notice that this duplicates code from presyncGitToPijulCommit, however,
    # this additional check will stop the commits from being duplicated.
    r = await run(
        f"cd {pijul}; pijul log --grep 'Imported from Git commit {commit}' --hash-only --branch {branch}"
    )
    for patch_id in r.split("\n"):
        patch_id = patch_id.split(":")[0]
        if len(
                patch_id
        ) == 88:  # this is to avoid repository id to be treated as a patch
            desc = await run(
                f"cd {pijul}; pijul patch --description {patch_id}")
            if desc.strip() == f"Imported from Git commit {commit}":
                # Yay, exported to Pijul already
                return
    if (commit, branch) in handled_git_commits:
        # Exported to Pijul already
        return

    # Check whether we've already imported the commit as a patch, and we can
    # reuse it. For example, look at the following tree:
    #
    # D <- hotfix-123
    # |
    # C
    # |
    # B <- master
    # |
    # A
    #
    # In this case, we don't want to import A and B twice.
    r = await run(
        f"cd {pijul}; pijul log --grep 'Imported from Git commit {commit}' --hash-only"
    )
    for patch_id in r.split("\n"):
        patch_id = patch_id.split(":")[0]
        if len(
                patch_id
        ) == 88:  # this is to avoid repository id to be treated as a patch
            desc = await run(
                f"cd {pijul}; pijul patch --description {patch_id}")
            if desc.strip() == f"Imported from Git commit {commit}":
                # Okay, the patch is on another branch. So we apply it
                print(f"  Syncing commit {commit}: {message}...")
                await run(
                    f"cd {pijul}; pijul apply {patch_id} --branch {branch}")
                print(chalk.green(f"  Done. Reapplied patch {patch_id}"))
                return

    # Sync the commit itself now
    author = (
        await
        run(f"cd {git}; git --no-pager show -s --format='%an <%ae>' {commit}")
    ).strip()
    date = (await
            run(f"cd {git}; git log -1 -s --format=%ci {commit}")).strip()
    date = "T".join(date.split(" ", 1))
    desc = f"Imported from Git commit {commit}"
    message = (
        await run(f"cd {git}; git log -1 --format=%B {commit}")).split("\n")[0]

    print(f"  Syncing commit {commit}: {message}...")

    await run(f"cd {pijul}; pijul checkout {branch}")

    # For each changed file
    for file in (await run(
            f"cd {git}; git diff-tree --no-commit-id --name-only -r {commit}")
                 ).split("\n"):
        if file == "":
            continue

        await run(f"cd {git}; git checkout {commit}")
        try:
            with open(f"{git}/{file}") as f:
                theirs = f.readlines()
        except IOError:
            theirs = None
        await run(f"cd {git}; git checkout {commit}^")
        try:
            with open(f"{git}/{file}") as f:
                base = f.readlines()
        except IOError:
            base = None
        try:
            with open(f"{pijul}/{file}") as f:
                ours = f.readlines()
        except IOError:
            ours = None

        # Perform a 3-way merge
        if base is None and ours is None:
            # Assume file creation
            base = []
            ours = []
        elif base is None and ours is not None:
            # Assume file recreation
            if ours == theirs:
                # No changes
                continue
            else:
                # Conflict
                with open(f"{pijul}/{file}", "w") as f:
                    f.write("/*\n")
                    f.write(
                        " * Notice by GitPijul proxy: this file was recreated on Git side (commit\n"
                    )
                    f.write(
                        f" * {commit[:10]}...). The original (Pijul) version is shown below; make sure to fix\n"
                    )
                    f.write(
                        " * the conflict yourself by merging the Git changes and remove this banner.\n"
                    )
                    f.write(" */\n")
                    f.write("".join(ours))
                    print(
                        chalk.yellow(
                            f"  Conflict: {file} recreated by Git with different contents"
                        ))
                continue
        elif base is not None and theirs is None:
            # Assume file deletion
            os.unlink(f"{pijul}/{file}")
            continue
        elif base is not None and ours is None:
            # Deleted by us
            continue
        elif base == ours:
            with open(f"{pijul}/{file}", "w") as f:
                f.write("".join(theirs))
            continue
        elif base == theirs:
            with open(f"{pijul}/{file}", "w") as f:
                f.write("".join(ours))
            continue

        # Assume file modifications on Git side or both sides
        merge = merge3.Merge3(base, ours, theirs, is_cherrypick=True)
        for t in merge.merge_regions():
            if t[0] == "conflict":
                # Aw!..
                header = ""
                header += "/*\n"
                header += " * Notice by GitPijul proxy: this file was modified by both Git and Pijul. Make\n"
                header += " * sure to merge the conflict yourself and remove this banner.\n"
                header += " */\n"
                print(
                    chalk.yellow(
                        f"  Conflict: {file} modified by both Git and Pijul"))
                break
        else:
            # Yay! No conflicts
            header = ""

        merged = merge.merge_lines(name_a="Pijul",
                                   name_b=f"Git (commit {commit})",
                                   start_marker=">" * 32,
                                   mid_marker="=" * 32,
                                   end_marker="<" * 32)
        os.makedirs(os.path.dirname(f"{pijul}/{file}"), exist_ok=True)
        with open(f"{pijul}/{file}", "w") as f:
            f.write(header)
            f.write("".join(merged))

    # Check whether there are any changes
    if await run(f"cd {pijul}; pijul status --short") == "":
        print(chalk.yellow("  No changes (fast-forward)"))
        handled_git_commits.append((commit, branch))
        return

    # Record changes
    author = shlex.quote(author)
    message = shlex.quote(message)
    r = await run(
        f"cd {pijul}; pijul record --add-new-files --all --author {author} --branch {branch} --date '{date}' --description '{desc}' --message {message}"
    )
    patch = r.replace("Recorded patch ", "").strip()

    print(chalk.green(f"  Done. Recorded patch {patch}"))
예제 #26
0
 def test_merge_poem(self):
     """Test case from diff3 manual"""
     m3 = merge3.Merge3(TZU, LAO, TAO)
     ml = list(m3.merge_lines('LAO', 'TAO'))
     self.assertEqual(ml, MERGED_RESULT)