diff --git a/mercurial/context.py b/mercurial/context.py --- a/mercurial/context.py +++ b/mercurial/context.py @@ -8,6 +8,7 @@ from __future__ import absolute_import import errno +import filecmp import os import re import stat @@ -697,7 +698,11 @@ def matches(self, match): return self.walk(match) -class basefilectx(object): +class abstractfilectx(object): + def data(self): + raise NotImplementedError + +class basefilectx(abstractfilectx): """A filecontext object represents the common logic for its children: filectx: read-only access to a filerevision that is already present in the repo, @@ -2121,6 +2126,9 @@ self._parent = parent self._path = path + def cmp(self, fctx): + return self.data() != fctx.data() + def ctx(self): return self._parent @@ -2578,11 +2586,17 @@ """Allows you to use filectx-like functions on a file in an arbitrary location on disk, possibly not in the working directory. """ - def __init__(self, path): + def __init__(self, path, repo=None): + # Repo is optional because contrib/simplemerge uses this class. + self._repo = repo self._path = path - def cmp(self, otherfilectx): - return self.data() != otherfilectx.data() + def cmp(self, fctx): + if isinstance(fctx, workingfilectx) and self._repo: + # Add a fast-path for merge if both sides are disk-backed. + # Note that filecmp uses the opposite return values as cmp. + return not filecmp.cmp(self.path(), self._repo.wjoin(fctx.path())) + return self.data() != fctx.data() def path(self): return self._path diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py --- a/mercurial/filemerge.py +++ b/mercurial/filemerge.py @@ -7,7 +7,6 @@ from __future__ import absolute_import -import filecmp import os import re import tempfile @@ -226,9 +225,9 @@ return '\n' return None # unknown -def _matcheol(file, origfile): +def _matcheol(file, back): "Convert EOL markers in a file to match origfile" - tostyle = _eoltype(util.readfile(origfile)) + tostyle = _eoltype(back.data()) # No repo.wread filters? if tostyle: data = util.readfile(file) style = _eoltype(data) @@ -468,6 +467,12 @@ a = _workingpath(repo, fcd) fd = fcd.path() + # Run ``flushall()`` to make any missing folders the following wwrite + # calls might be depending on. + from . import context + if isinstance(fcd, context.overlayworkingfilectx): + fcd.ctx().flushall() + util.writefile(a + ".local", fcd.decodeddata()) repo.wwrite(fd + ".other", fco.data(), fco.flags()) repo.wwrite(fd + ".base", fca.data(), fca.flags()) @@ -505,7 +510,9 @@ args = _toolstr(ui, tool, "args", '$local $base $other') if "$output" in args: - out, a = a, back # read input from backup, write to original + # read input from backup, write to original + out = a + a = repo.wvfs.join(back.path()) replace = {'local': a, 'base': b, 'other': c, 'output': out} args = util.interpolate(r'\$', replace, args, lambda s: util.shellquote(util.localpath(s))) @@ -588,10 +595,10 @@ def _restorebackup(fcd, back): # TODO: Add a workingfilectx.write(otherfilectx) path so we can use # util.copy here instead. - fcd.write(util.readfile(back), fcd.flags()) + fcd.write(back.data(), fcd.flags()) def _makebackup(repo, ui, fcd, premerge): - """Makes a backup of the local `fcd` file prior to merging. + """Makes and returns a filectx-like object for ``fcd``'s backup file. In addition to preserving the user's pre-existing modifications to `fcd` (if any), the backup is used to undo certain premerges, confirm whether a @@ -600,12 +607,27 @@ """ if fcd.isabsent(): return None + from . import context + back = scmutil.origpath(ui, repo, repo.wjoin(fcd.path())) - a = _workingpath(repo, fcd) - back = scmutil.origpath(ui, repo, a) - if premerge: - util.copyfile(a, back) - return back + inworkingdir = (back.startswith(repo.wvfs.base) and not + back.startswith(repo.vfs.base)) + + if isinstance(fcd, context.overlayworkingfilectx) and inworkingdir: + # If the backup file is to be in the working directory, and we're + # merging in-memory, we must redirect the backup to the memory context + # so we don't disturb the working directory. + relpath = back[len(repo.wvfs.base) + 1:] + fcd.ctx()[relpath].write(fcd.data(), fcd.flags()) + return fcd.ctx()[relpath] + else: + # Otherwise, write to wherever the user specified the backups should go. + # + # A arbitraryfilectx is returned, so we can run the same functions on + # the backup context regardless of where it lives. + if premerge: + util.copyfile(_workingpath(repo, fcd), back) + return context.arbitraryfilectx(back, repo=repo) def _maketempfiles(repo, fco, fca): """Writes out `fco` and `fca` as temporary files, so an external merge @@ -719,7 +741,7 @@ return True, r, deleted finally: if not r and back is not None: - util.unlink(back) + back.remove() def _check(repo, r, ui, tool, fcd, files): fd = fcd.path() @@ -741,7 +763,7 @@ if not r and not checked and (_toolbool(ui, tool, "checkchanged") or 'changed' in _toollist(ui, tool, "check")): - if back is not None and filecmp.cmp(_workingpath(repo, fcd), back): + if back is not None and not fcd.cmp(back): if ui.promptchoice(_(" output file %s appears unchanged\n" "was merge successful (yn)?" "$$ &Yes $$ &No") % fd, 1):