# HG changeset patch # User Kyle Lippincott # Date 1521660989 25200 # Node ID 1e30a26a65d077b3503cd746ee73fb99f3fb8033 # Parent 66d478064d5f13ca3e317b23771fcd1d73b83ff1 filemerge: make the 'local' path match the format that 'base' and 'other' use If we pass a separate '$output' arg to the merge tool, we produce four files: local, base, other, and output. In this situation, 'output' will be the original filename, 'base' and 'other' are temporary files, and previously 'local' would be the backup file (so if 'output' was foo.txt, 'local' would be foo.txt.orig). This change makes it so that 'local' follows the same pattern as 'base' and 'other' - it will be a temporary file either in the `experimental.mergetempdirprefix`-controlled directory with a name like foo~local.txt, or in the normal system-wide temp dir with a name like foo~local.RaNd0m.txt. For the cases where the merge tool does not use an '$output' arg, 'local' is still the destination filename, and 'base' and 'other' are unchanged. The hope is that this is much easier for people to reason about; rather than having a tool like Meld pop up with three panes, one of them with the filename "foo.txt.orig", one with the filename "foo.txt", and one with "foo~other.StuFf2.txt", we can (when the merge temp dir stuff is enabled) make it show up as "foo~local.txt", "foo.txt" and "foo~other.txt", respectively. This also opens the door to future customization, such as getting the operation-provided labels and a hash prefix into the filenames (so we see something like "foo~dest.abc123", "foo.txt", and "foo~src.d4e5f6"). Differential Revision: https://phab.mercurial-scm.org/D2889 diff -r 66d478064d5f -r 1e30a26a65d0 mercurial/filemerge.py --- a/mercurial/filemerge.py Wed Mar 21 22:36:26 2018 -0400 +++ b/mercurial/filemerge.py Wed Mar 21 12:36:29 2018 -0700 @@ -512,8 +512,11 @@ return False, 1, None unused, unused, unused, back = files localpath = _workingpath(repo, fcd) - with _maketempfiles(repo, fco, fca) as temppaths: - basepath, otherpath = temppaths + args = _toolstr(repo.ui, tool, "args") + + with _maketempfiles(repo, fco, fca, repo.wvfs.join(back.path()), + "$output" in args) as temppaths: + basepath, otherpath, localoutputpath = temppaths outpath = "" mylabel, otherlabel = labels[:2] if len(labels) >= 3: @@ -533,11 +536,10 @@ } ui = repo.ui - args = _toolstr(ui, tool, "args") if "$output" in args: # read input from backup, write to original outpath = localpath - localpath = repo.wvfs.join(back.path()) + localpath = localoutputpath replace = {'local': localpath, 'base': basepath, 'other': otherpath, 'output': outpath, 'labellocal': mylabel, 'labelother': otherlabel, 'labelbase': baselabel} @@ -665,17 +667,18 @@ return context.arbitraryfilectx(back, repo=repo) @contextlib.contextmanager -def _maketempfiles(repo, fco, fca): - """Writes out `fco` and `fca` as temporary files, so an external merge - tool may use them. +def _maketempfiles(repo, fco, fca, localpath, uselocalpath): + """Writes out `fco` and `fca` as temporary files, and (if uselocalpath) + copies `localpath` to another temporary file, so an external merge tool may + use them. """ tmproot = None tmprootprefix = repo.ui.config('experimental', 'mergetempdirprefix') if tmprootprefix: tmproot = tempfile.mkdtemp(prefix=tmprootprefix) - def temp(prefix, ctx): - fullbase, ext = os.path.splitext(ctx.path()) + def maketempfrompath(prefix, path): + fullbase, ext = os.path.splitext(path) pre = "%s~%s" % (os.path.basename(fullbase), prefix) if tmproot: name = os.path.join(tmproot, pre) @@ -683,23 +686,42 @@ name += ext f = open(name, r"wb") else: - (fd, name) = tempfile.mkstemp(prefix=pre + '.', suffix=ext) + fd, name = tempfile.mkstemp(prefix=pre + '.', suffix=ext) f = os.fdopen(fd, r"wb") + return f, name + + def tempfromcontext(prefix, ctx): + f, name = maketempfrompath(prefix, ctx.path()) data = repo.wwritedata(ctx.path(), ctx.data()) f.write(data) f.close() return name - b = temp("base", fca) - c = temp("other", fco) + b = tempfromcontext("base", fca) + c = tempfromcontext("other", fco) + d = localpath + if uselocalpath: + # We start off with this being the backup filename, so remove the .orig + # to make syntax-highlighting more likely. + if d.endswith('.orig'): + d, _ = os.path.splitext(d) + f, d = maketempfrompath("local", d) + with open(localpath, 'rb') as src: + f.write(src.read()) + f.close() + try: - yield b, c + yield b, c, d finally: if tmproot: shutil.rmtree(tmproot) else: util.unlink(b) util.unlink(c) + # if not uselocalpath, d is the 'orig'/backup file which we + # shouldn't delete. + if d and uselocalpath: + util.unlink(d) def _filemerge(premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=None): """perform a 3-way merge in the working directory diff -r 66d478064d5f -r 1e30a26a65d0 tests/test-merge-tools.t --- a/tests/test-merge-tools.t Wed Mar 21 22:36:26 2018 -0400 +++ b/tests/test-merge-tools.t Wed Mar 21 12:36:29 2018 -0700 @@ -1585,7 +1585,7 @@ $ hg update -q -C 2 $ hg merge -y -r tip --tool echo --config merge-tools.echo.args='$base $local $other $output' merging f and f.txt to f.txt - */f~base.* $TESTTMP/f.txt.orig */f~other.*.txt $TESTTMP/f.txt (glob) + */f~base.* */f~local.*.txt */f~other.*.txt $TESTTMP/f.txt (glob) 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) @@ -1600,7 +1600,7 @@ > --config merge-tools.echo.args='$base $local $other $output' \ > --config experimental.mergetempdirprefix=$TESTTMP/hgmerge. merging f and f.txt to f.txt - $TESTTMP/hgmerge.*/f~base $TESTTMP/f.txt.orig $TESTTMP/hgmerge.*/f~other.txt $TESTTMP/f.txt (glob) + $TESTTMP/hgmerge.*/f~base $TESTTMP/hgmerge.*/f~local.txt $TESTTMP/hgmerge.*/f~other.txt $TESTTMP/f.txt (glob) 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit)