mirror of
https://github.com/facebook/sapling.git
synced 2024-10-06 06:47:41 +03:00
mergetools: add new conflict marker format with diffs in
Summary: Written by Martin von Zweigbergk at https://phab.mercurial-scm.org/D9551, or https://www.mercurial-scm.org/repo/hg/rev/bdc2bf68f19e. He suggested it and it's a useful feature. I did minor compatibility changes (encoding, avoid rev numbers, foo_bar -> foobar). Original commit message: I use 3-way conflict markers. Often when I resolve them, I manually compare one the base with one side and apply the differences to the other side. That can be hard when the conflict marker is large. This patch introduces a new type of conflict marker, which I'm hoping will make it easier to resolve conflicts. The new format uses `<<<<<<<` and `>>>>>>>` to open and close the markers, just like our existing 2-way and 3-way conflict markers. Instead of having 2 or 3 snapshots (left+right or left+base+right), it has a sequence of diffs. A diff looks like this: ``` ------- base +++++++ left a -b +c d ``` A diff that adds one side ("diff from nothing") has a `=======` header instead and does not have have `+` prefixed on its lines. A regular 3-way merge can be viewed as adding one side plus a diff between the base and the other side. It thus has two ways of being represented, depending on which side is being diffed: ``` <<<<<<< ======= left contents on left ------- base +++++++ right contents on -left +right >>>>>>> ``` or ``` <<<<<<< ------- base +++++++ left contents on -right +left ======= right contents on right >>>>>>> ``` I've made it so the new merge tool tries to pick a version that has the most common lines (no difference in the example above). I've called the new tool "mergediff" to stick to the convention of starting with "merge" if the tool tries a regular 3-way merge. The idea came from my pet VCS (placeholder name `jj`), which has support for octopus merges and other ways of ending up with merges of more than 3 versions. I wanted to be able to represent such conflicts in the working copy and therefore thought of this format (although I have not yet implemented it in my VCS). I then attended a meeting with Larry McVoy, who said BitKeeper has an option (`bk smerge -g`) for showing a similar format, which reminded me to actually attempt this in Mercurial. Reviewed By: DurhamG Differential Revision: D26947920 fbshipit-source-id: 8b4446862897ff9a6dfdf5a2e35617d4db09e883
This commit is contained in:
parent
0c20c9643c
commit
2a4db136e5
@ -595,6 +595,28 @@ def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
|
||||
return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
|
||||
|
||||
|
||||
@internaltool(
|
||||
"mergediff",
|
||||
fullmerge,
|
||||
_("warning: conflicts while merging %s! " "(edit, then use 'hg resolve --mark')\n"),
|
||||
precheck=_mergecheck,
|
||||
)
|
||||
def _imergediff(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
|
||||
"""
|
||||
Uses the internal non-interactive simple merge algorithm for merging
|
||||
files. It will fail if there are any conflicts and leave markers in
|
||||
the partially merged file. The marker will have two sections, one with the
|
||||
content from one side of the merge, and one with a diff from the base
|
||||
content to the content on the other side. (experimental)"""
|
||||
if not labels:
|
||||
labels = _defaultconflictlabels
|
||||
if len(labels) < 3:
|
||||
labels = labels + ["base"]
|
||||
return _merge(
|
||||
repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, "mergediff"
|
||||
)
|
||||
|
||||
|
||||
def _imergeauto(
|
||||
repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None, localorother=None
|
||||
):
|
||||
|
@ -428,6 +428,64 @@ def _picklabels(defaults, overrides):
|
||||
return result
|
||||
|
||||
|
||||
def _mergediff(m3, name_a, name_b, name_base):
|
||||
lines = []
|
||||
conflicts = False
|
||||
for group in m3.merge_groups():
|
||||
if group[0] == "conflict":
|
||||
base_lines, a_lines, b_lines = group[1:]
|
||||
basetext = b"".join(base_lines)
|
||||
bblocks = list(
|
||||
mdiff.allblocks(
|
||||
basetext,
|
||||
b"".join(b_lines),
|
||||
lines1=base_lines,
|
||||
lines2=b_lines,
|
||||
)
|
||||
)
|
||||
ablocks = list(
|
||||
mdiff.allblocks(
|
||||
basetext,
|
||||
b"".join(a_lines),
|
||||
lines1=base_lines,
|
||||
lines2=b_lines,
|
||||
)
|
||||
)
|
||||
|
||||
def matchinglines(blocks):
|
||||
return sum(block[1] - block[0] for block, kind in blocks if kind == "=")
|
||||
|
||||
def difflines(blocks, lines1, lines2):
|
||||
for block, kind in blocks:
|
||||
if kind == "=":
|
||||
for line in lines1[block[0] : block[1]]:
|
||||
yield b" " + line
|
||||
else:
|
||||
for line in lines1[block[0] : block[1]]:
|
||||
yield b"-" + line
|
||||
for line in lines2[block[2] : block[3]]:
|
||||
yield b"+" + line
|
||||
|
||||
lines.append(b"<<<<<<<\n")
|
||||
if matchinglines(ablocks) < matchinglines(bblocks):
|
||||
lines.append(b"======= %s\n" % name_a)
|
||||
lines.extend(a_lines)
|
||||
lines.append(b"------- %s\n" % name_base)
|
||||
lines.append(b"+++++++ %s\n" % name_b)
|
||||
lines.extend(difflines(bblocks, base_lines, b_lines))
|
||||
else:
|
||||
lines.append(b"------- %s\n" % name_base)
|
||||
lines.append(b"+++++++ %s\n" % name_a)
|
||||
lines.extend(difflines(ablocks, base_lines, a_lines))
|
||||
lines.append(b"======= %s\n" % name_b)
|
||||
lines.extend(b_lines)
|
||||
lines.append(b">>>>>>>\n")
|
||||
conflicts = True
|
||||
else:
|
||||
lines.extend(group[1])
|
||||
return lines, conflicts
|
||||
|
||||
|
||||
def simplemerge(ui, localctx, basectx, otherctx, **opts):
|
||||
"""Performs the simplemerge algorithm.
|
||||
|
||||
@ -471,7 +529,11 @@ def simplemerge(ui, localctx, basectx, otherctx, **opts):
|
||||
extrakwargs["name_base"] = name_base
|
||||
extrakwargs["minimize"] = False
|
||||
|
||||
lines = list(m3.merge_lines(name_a=name_a, name_b=name_b, **extrakwargs))
|
||||
if mode == "mergediff":
|
||||
lines, conflicts = _mergediff(m3, name_a, name_b, name_base)
|
||||
else:
|
||||
lines = list(m3.merge_lines(name_a=name_a, name_b=name_b, **extrakwargs))
|
||||
conflicts = m3.conflicts
|
||||
mergedtext = b"".join(lines)
|
||||
if opts.get("print"):
|
||||
ui.fout.write(mergedtext)
|
||||
@ -481,5 +543,5 @@ def simplemerge(ui, localctx, basectx, otherctx, **opts):
|
||||
flags = getattr(localctx, "workingflags", localctx.flags)()
|
||||
localctx.write(mergedtext, flags)
|
||||
|
||||
if m3.conflicts and not mode == "union":
|
||||
return m3.conflictscount
|
||||
if conflicts and not mode == "union":
|
||||
return getattr(m3, "conflictscount", 1)
|
||||
|
@ -298,3 +298,77 @@ Now test :merge-other and :merge-local
|
||||
|
||||
|
||||
End of file
|
||||
|
||||
internal:mergediff
|
||||
|
||||
$ hg co -C 'desc(branch1)'
|
||||
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
|
||||
$ cat << EOF > a
|
||||
> Small Mathematical Series.
|
||||
> 1
|
||||
> 2
|
||||
> 3
|
||||
> 4
|
||||
> 4.5
|
||||
> 5
|
||||
> Hop we are done.
|
||||
> EOF
|
||||
$ hg co -m 'desc(branch2)' -t internal:mergediff
|
||||
merging a
|
||||
warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
|
||||
0 files updated, 0 files merged, 0 files removed, 1 files unresolved
|
||||
use 'hg resolve' to retry unresolved file merges
|
||||
[1]
|
||||
$ cat a
|
||||
Small Mathematical Series.
|
||||
1
|
||||
2
|
||||
3
|
||||
<<<<<<<
|
||||
------- base
|
||||
+++++++ working copy
|
||||
4
|
||||
+4.5
|
||||
5
|
||||
======= destination
|
||||
6
|
||||
8
|
||||
>>>>>>>
|
||||
Hop we are done.
|
||||
Test the same thing as above but modify a bit more so we instead get the working
|
||||
copy in full and the diff from base to destination.
|
||||
$ hg co -C 'desc(branch1)'
|
||||
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
|
||||
$ cat << EOF > a
|
||||
> Small Mathematical Series.
|
||||
> 1
|
||||
> 2
|
||||
> 3.5
|
||||
> 4.5
|
||||
> 5.5
|
||||
> Hop we are done.
|
||||
> EOF
|
||||
$ hg co -m 'desc(branch2)' -t internal:mergediff
|
||||
merging a
|
||||
warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
|
||||
0 files updated, 0 files merged, 0 files removed, 1 files unresolved
|
||||
use 'hg resolve' to retry unresolved file merges
|
||||
[1]
|
||||
$ cat a
|
||||
Small Mathematical Series.
|
||||
1
|
||||
2
|
||||
<<<<<<<
|
||||
======= working copy
|
||||
3.5
|
||||
4.5
|
||||
5.5
|
||||
------- base
|
||||
+++++++ destination
|
||||
3
|
||||
-4
|
||||
-5
|
||||
+6
|
||||
+8
|
||||
>>>>>>>
|
||||
Hop we are done.
|
||||
|
@ -1707,6 +1707,13 @@ Test dynamic list of merge tools only shows up once
|
||||
partially merged file. Marker will have three sections, one from each
|
||||
side of the merge and one for the base content.
|
||||
|
||||
":mergediff"
|
||||
Uses the internal non-interactive simple merge algorithm for merging
|
||||
files. It will fail if there are any conflicts and leave markers in the
|
||||
partially merged file. The marker will have two sections, one with the
|
||||
content from one side of the merge, and one with a diff from the base
|
||||
content to the content on the other side. (experimental)
|
||||
|
||||
":other"
|
||||
Uses the other 'p2()' version of files as the merged version.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user