comparison mercurial/filemerge.py @ 37077:1e30a26a65d0

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
author Kyle Lippincott <spectral@google.com>
date Wed, 21 Mar 2018 12:36:29 -0700
parents e349ad5cbb71
children f0b6fbea00cf
comparison
equal deleted inserted replaced
37076:66d478064d5f 37077:1e30a26a65d0
510 repo.ui.warn(_('warning: %s cannot merge change/delete conflict ' 510 repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
511 'for %s\n') % (tool, fcd.path())) 511 'for %s\n') % (tool, fcd.path()))
512 return False, 1, None 512 return False, 1, None
513 unused, unused, unused, back = files 513 unused, unused, unused, back = files
514 localpath = _workingpath(repo, fcd) 514 localpath = _workingpath(repo, fcd)
515 with _maketempfiles(repo, fco, fca) as temppaths: 515 args = _toolstr(repo.ui, tool, "args")
516 basepath, otherpath = temppaths 516
517 with _maketempfiles(repo, fco, fca, repo.wvfs.join(back.path()),
518 "$output" in args) as temppaths:
519 basepath, otherpath, localoutputpath = temppaths
517 outpath = "" 520 outpath = ""
518 mylabel, otherlabel = labels[:2] 521 mylabel, otherlabel = labels[:2]
519 if len(labels) >= 3: 522 if len(labels) >= 3:
520 baselabel = labels[2] 523 baselabel = labels[2]
521 else: 524 else:
531 'HG_OTHER_LABEL': otherlabel, 534 'HG_OTHER_LABEL': otherlabel,
532 'HG_BASE_LABEL': baselabel, 535 'HG_BASE_LABEL': baselabel,
533 } 536 }
534 ui = repo.ui 537 ui = repo.ui
535 538
536 args = _toolstr(ui, tool, "args")
537 if "$output" in args: 539 if "$output" in args:
538 # read input from backup, write to original 540 # read input from backup, write to original
539 outpath = localpath 541 outpath = localpath
540 localpath = repo.wvfs.join(back.path()) 542 localpath = localoutputpath
541 replace = {'local': localpath, 'base': basepath, 'other': otherpath, 543 replace = {'local': localpath, 'base': basepath, 'other': otherpath,
542 'output': outpath, 'labellocal': mylabel, 544 'output': outpath, 'labellocal': mylabel,
543 'labelother': otherlabel, 'labelbase': baselabel} 545 'labelother': otherlabel, 'labelbase': baselabel}
544 args = util.interpolate(br'\$', replace, args, 546 args = util.interpolate(br'\$', replace, args,
545 lambda s: util.shellquote(util.localpath(s))) 547 lambda s: util.shellquote(util.localpath(s)))
663 # A arbitraryfilectx is returned, so we can run the same functions on 665 # A arbitraryfilectx is returned, so we can run the same functions on
664 # the backup context regardless of where it lives. 666 # the backup context regardless of where it lives.
665 return context.arbitraryfilectx(back, repo=repo) 667 return context.arbitraryfilectx(back, repo=repo)
666 668
667 @contextlib.contextmanager 669 @contextlib.contextmanager
668 def _maketempfiles(repo, fco, fca): 670 def _maketempfiles(repo, fco, fca, localpath, uselocalpath):
669 """Writes out `fco` and `fca` as temporary files, so an external merge 671 """Writes out `fco` and `fca` as temporary files, and (if uselocalpath)
670 tool may use them. 672 copies `localpath` to another temporary file, so an external merge tool may
673 use them.
671 """ 674 """
672 tmproot = None 675 tmproot = None
673 tmprootprefix = repo.ui.config('experimental', 'mergetempdirprefix') 676 tmprootprefix = repo.ui.config('experimental', 'mergetempdirprefix')
674 if tmprootprefix: 677 if tmprootprefix:
675 tmproot = tempfile.mkdtemp(prefix=tmprootprefix) 678 tmproot = tempfile.mkdtemp(prefix=tmprootprefix)
676 679
677 def temp(prefix, ctx): 680 def maketempfrompath(prefix, path):
678 fullbase, ext = os.path.splitext(ctx.path()) 681 fullbase, ext = os.path.splitext(path)
679 pre = "%s~%s" % (os.path.basename(fullbase), prefix) 682 pre = "%s~%s" % (os.path.basename(fullbase), prefix)
680 if tmproot: 683 if tmproot:
681 name = os.path.join(tmproot, pre) 684 name = os.path.join(tmproot, pre)
682 if ext: 685 if ext:
683 name += ext 686 name += ext
684 f = open(name, r"wb") 687 f = open(name, r"wb")
685 else: 688 else:
686 (fd, name) = tempfile.mkstemp(prefix=pre + '.', suffix=ext) 689 fd, name = tempfile.mkstemp(prefix=pre + '.', suffix=ext)
687 f = os.fdopen(fd, r"wb") 690 f = os.fdopen(fd, r"wb")
691 return f, name
692
693 def tempfromcontext(prefix, ctx):
694 f, name = maketempfrompath(prefix, ctx.path())
688 data = repo.wwritedata(ctx.path(), ctx.data()) 695 data = repo.wwritedata(ctx.path(), ctx.data())
689 f.write(data) 696 f.write(data)
690 f.close() 697 f.close()
691 return name 698 return name
692 699
693 b = temp("base", fca) 700 b = tempfromcontext("base", fca)
694 c = temp("other", fco) 701 c = tempfromcontext("other", fco)
702 d = localpath
703 if uselocalpath:
704 # We start off with this being the backup filename, so remove the .orig
705 # to make syntax-highlighting more likely.
706 if d.endswith('.orig'):
707 d, _ = os.path.splitext(d)
708 f, d = maketempfrompath("local", d)
709 with open(localpath, 'rb') as src:
710 f.write(src.read())
711 f.close()
712
695 try: 713 try:
696 yield b, c 714 yield b, c, d
697 finally: 715 finally:
698 if tmproot: 716 if tmproot:
699 shutil.rmtree(tmproot) 717 shutil.rmtree(tmproot)
700 else: 718 else:
701 util.unlink(b) 719 util.unlink(b)
702 util.unlink(c) 720 util.unlink(c)
721 # if not uselocalpath, d is the 'orig'/backup file which we
722 # shouldn't delete.
723 if d and uselocalpath:
724 util.unlink(d)
703 725
704 def _filemerge(premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=None): 726 def _filemerge(premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
705 """perform a 3-way merge in the working directory 727 """perform a 3-way merge in the working directory
706 728
707 premerge = whether this is a premerge 729 premerge = whether this is a premerge