diff --git a/mercurial/context.py b/mercurial/context.py --- a/mercurial/context.py +++ b/mercurial/context.py @@ -697,7 +697,11 @@ def matches(self, match): return self.walk(match) -class basefilectx(object): +class abstractfilectx(object): + def data(self): + raise error.ProgrammingError("Must be implemented by subclasses") + +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, @@ -858,6 +862,9 @@ or self.size() == fctx.size()): return self._filelog.cmp(self._filenode, fctx.data()) + if isinstance(fctx, abstractfilectx): + return self.data() != fctx.data() + return True def _adjustlinkrev(self, srcrev, inclusive=False): @@ -2124,6 +2131,9 @@ self._parent = parent self._path = path + def cmp(self, fctx): + return self.data() != fctx.data() + def ctx(self): return self._parent 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) 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): diff --git a/tests/test-dirstate-race.t b/tests/test-dirstate-race.t --- a/tests/test-dirstate-race.t +++ b/tests/test-dirstate-race.t @@ -80,9 +80,9 @@ $ hg status --config extensions.dirstaterace=$TESTTMP/dirstaterace.py M d - M e ! b ! dir1/c + ! e $ hg debugdirstate n 644 2 * a (glob) n 0 -1 unset b