diff --git a/mercurial/context.py b/mercurial/context.py --- a/mercurial/context.py +++ b/mercurial/context.py @@ -632,6 +632,42 @@ class workingctx(changectx): def __contains__(self, key): return self._repo.dirstate[key] not in "?r" + def _buildflagfunc(self): + # Create a fallback function for getting file flags when the + # filesystem doesn't support them + + copiesget = self._repo.dirstate.copies().get + + if len(self._parents) < 2: + # when we have one parent, it's easy: copy from parent + man = self._parents[0].manifest() + def func(f): + f = copiesget(f, f) + return man.flags(f) + else: + # merges are tricky: we try to reconstruct the unstored + # result from the merge (issue1802) + p1, p2 = self._parents + pa = p1.ancestor(p2) + m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest() + + def func(f): + f = copiesget(f, f) # may be wrong for merges with copies + fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f) + if fl1 == fl2: + return fl1 + if fl1 == fla: + return fl2 + if fl2 == fla: + return fl1 + return '' # punt for conflicts + + return func + + @propertycache + def _flagfunc(self): + return self._repo.dirstate.flagfunc(self._buildflagfunc) + @propertycache def _manifest(self): """generate a manifest corresponding to the working directory""" @@ -640,7 +676,6 @@ class workingctx(changectx): self.status(unknown=True) man = self._parents[0].manifest().copy() - copied = self._repo.dirstate.copies() if len(self._parents) > 1: man2 = self.p2().manifest() def getman(f): @@ -649,10 +684,9 @@ class workingctx(changectx): return man2 else: getman = lambda f: man - def cf(f): - f = copied.get(f, f) - return getman(f).flags(f) - ff = self._repo.dirstate.flagfunc(cf) + + copied = self._repo.dirstate.copies() + ff = self._flagfunc modified, added, removed, deleted = self._status unknown = self._unknown for i, l in (("a", added), ("m", modified), ("u", unknown)): @@ -767,23 +801,10 @@ class workingctx(changectx): except KeyError: return '' - orig = self._repo.dirstate.copies().get(path, path) - - def findflag(ctx): - mnode = ctx.changeset()[0] - node, flag = self._repo.manifest.find(mnode, orig) - ff = self._repo.dirstate.flagfunc(lambda x: flag or '') - try: - return ff(path) - except OSError: - pass - - flag = findflag(self._parents[0]) - if flag is None and len(self.parents()) > 1: - flag = findflag(self._parents[1]) - if flag is None or self._repo.dirstate[path] == 'r': + try: + return self._flagfunc(path) + except OSError: return '' - return flag def filectx(self, path, filelog=None): """get a file context from the working directory""" diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py --- a/mercurial/dirstate.py +++ b/mercurial/dirstate.py @@ -131,17 +131,19 @@ class dirstate(object): # it's safe because f is always a relative path return self._rootdir + f - def flagfunc(self, fallback): + def flagfunc(self, buildfallback): + if self._checklink and self._checkexec: + def f(x): + p = self._join(x) + if os.path.islink(p): + return 'l' + if util.isexec(p): + return 'x' + return '' + return f + + fallback = buildfallback() if self._checklink: - if self._checkexec: - def f(x): - p = self._join(x) - if os.path.islink(p): - return 'l' - if util.isexec(p): - return 'x' - return '' - return f def f(x): if os.path.islink(self._join(x)): return 'l' @@ -157,7 +159,8 @@ class dirstate(object): return 'x' return '' return f - return fallback + else: + return fallback def getcwd(self): cwd = os.getcwd() diff --git a/tests/test-issue1802.t b/tests/test-issue1802.t new file mode 100644 --- /dev/null +++ b/tests/test-issue1802.t @@ -0,0 +1,69 @@ +Create extension that can disable exec checks: + + $ cat > noexec.py < from mercurial import extensions, util + > def setflags(orig, f, l, x): + > pass + > def checkexec(orig, path): + > return False + > def extsetup(ui): + > extensions.wrapfunction(util, 'setflags', setflags) + > extensions.wrapfunction(util, 'checkexec', checkexec) + > EOF + + $ hg init unix-repo + $ cd unix-repo + $ touch a + $ hg add a + $ hg commit -m 'unix: add a' + $ hg clone . ../win-repo + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ chmod +x a + $ hg commit -m 'unix: chmod a' + $ hg manifest -v + 755 * a + + $ cd ../win-repo + + $ touch b + $ hg add b + $ hg commit -m 'win: add b' + + $ hg manifest -v + 644 a + 644 b + + $ hg pull + pulling from $TESTTMP/unix-repo + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 0 changes to 0 files (+1 heads) + (run 'hg heads' to see heads, 'hg merge' to merge) + + $ hg manifest -v -r tip + 755 * a + +Simulate a Windows merge: + + $ hg --config extensions.n=$TESTTMP/noexec.py merge --debug + searching for copies back to rev 1 + unmatched files in local: + b + resolving manifests + overwrite None partial False + ancestor a03b0deabf2b local d6fa54f68ae1+ remote 2d8bcf2dda39 + a: update permissions -> e + updating: a 1/1 files (100.00%) + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + +Simulate a Windows commit: + + $ hg --config extensions.n=$TESTTMP/noexec.py commit -m 'win: merge' + + $ hg manifest -v + 755 * a + 644 b