Mercurial > hg
changeset 16542:e596a631210e stable
dirstate: preserve path components case on renames (issue3402)
The original issue was something like:
$ hg init repo
$ cd repo
$ mkdir D
$ echo a > D/a
$ hg ci -Am adda
adding D/a
$ mv D temp
$ mv temp d
$ echo b > d/b
$ hg add d/b
adding D/b
$ hg ci -m addb
$ hg mv d/b d/c
moving D/b to d/c
$ hg st
A d/c
R D/b
Here we expected:
A D/c
R D/b
the logic being we try to preserve case of path components already known in the
dirstate. This is fixed by the current patch.
Note the following stories are not still not supported:
Changing directory case
$ hg mv D d
moving D/a to D/D/a
moving D/b to D/D/b
$ hg st
A D/D/a
A D/D/b
R D/a
R D/b
or:
$ hg mv D/* d
D/a: not overwriting - file exists
D/b: not overwriting - file exists
And if they were, there are probably similar issues with diffing/patching.
author | Patrick Mezard <patrick@mezard.eu> |
---|---|
date | Sat, 28 Apr 2012 20:29:21 +0200 |
parents | bb3334806ace |
children | be786c5ac0a8 |
files | mercurial/cmdutil.py mercurial/dirstate.py tests/test-casefolding.t |
diffstat | 3 files changed, 74 insertions(+), 17 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/cmdutil.py Sat Apr 28 02:00:04 2012 +0200 +++ b/mercurial/cmdutil.py Sat Apr 28 20:29:21 2012 +0200 @@ -268,6 +268,11 @@ # otarget: ossep def copyfile(abssrc, relsrc, otarget, exact): abstarget = scmutil.canonpath(repo.root, cwd, otarget) + if '/' in abstarget: + # We cannot normalize abstarget itself, this would prevent + # case only renames, like a => A. + abspath, absname = abstarget.rsplit('/', 1) + abstarget = repo.dirstate.normalize(abspath) + '/' + absname reltarget = repo.pathto(abstarget, cwd) target = repo.wjoin(abstarget) src = repo.wjoin(abssrc)
--- a/mercurial/dirstate.py Sat Apr 28 02:00:04 2012 +0200 +++ b/mercurial/dirstate.py Sat Apr 28 20:29:21 2012 +0200 @@ -408,32 +408,48 @@ self._droppath(f) del self._map[f] - def _normalize(self, path, isknown): + def _normalize(self, path, isknown, ignoremissing=False, exists=None): normed = util.normcase(path) folded = self._foldmap.get(normed, None) if folded is None: - if isknown or not os.path.lexists(os.path.join(self._root, path)): + if isknown: folded = path else: - # recursively normalize leading directory components - # against dirstate - if '/' in normed: - d, f = normed.rsplit('/', 1) - d = self._normalize(d, isknown) - r = self._root + "/" + d - folded = d + "/" + util.fspath(f, r) + if exists is None: + exists = os.path.lexists(os.path.join(self._root, path)) + if not exists: + # Maybe a path component exists + if not ignoremissing and '/' in path: + d, f = path.rsplit('/', 1) + d = self._normalize(d, isknown, ignoremissing, None) + folded = d + "/" + f + else: + # No path components, preserve original case + folded = path else: - folded = util.fspath(normed, self._root) - self._foldmap[normed] = folded + # recursively normalize leading directory components + # against dirstate + if '/' in normed: + d, f = normed.rsplit('/', 1) + d = self._normalize(d, isknown, ignoremissing, True) + r = self._root + "/" + d + folded = d + "/" + util.fspath(f, r) + else: + folded = util.fspath(normed, self._root) + self._foldmap[normed] = folded return folded - def normalize(self, path, isknown=False): + def normalize(self, path, isknown=False, ignoremissing=False): ''' normalize the case of a pathname when on a casefolding filesystem isknown specifies whether the filename came from walking the - disk, to avoid extra filesystem access + disk, to avoid extra filesystem access. + + If ignoremissing is True, missing path are returned + unchanged. Otherwise, we try harder to normalize possibly + existing path components. The normalized case is determined based on the following precedence: @@ -443,7 +459,7 @@ ''' if self._checkcase: - return self._normalize(path, isknown) + return self._normalize(path, isknown, ignoremissing) return path def clear(self): @@ -575,7 +591,7 @@ normalize = self._normalize skipstep3 = False else: - normalize = lambda x, y: x + normalize = lambda x, y, z: x files = sorted(match.files()) subrepos.sort() @@ -596,7 +612,7 @@ # step 1: find all explicit files for ff in files: - nf = normalize(normpath(ff), False) + nf = normalize(normpath(ff), False, True) if nf in results: continue @@ -646,7 +662,7 @@ continue raise for f, kind, st in entries: - nf = normalize(nd and (nd + "/" + f) or f, True) + nf = normalize(nd and (nd + "/" + f) or f, True, True) if nf not in results: if kind == dirkind: if not ignore(nf):
--- a/tests/test-casefolding.t Sat Apr 28 02:00:04 2012 +0200 +++ b/tests/test-casefolding.t Sat Apr 28 20:29:21 2012 +0200 @@ -32,6 +32,42 @@ $ hg mv a A $ hg mv A a $ hg st + +test changing case of path components + + $ mkdir D + $ echo b > D/b + $ hg ci -Am addb D/b + $ hg mv D/b d/b + D/b: not overwriting - file exists + $ hg mv D/b d/c + $ hg st + A D/c + R D/b + $ mv D temp + $ mv temp d + $ hg st + A D/c + R D/b + $ hg revert -aq + $ rm d/c + $ echo c > D/c + $ hg add D/c + $ hg st + A D/c + $ hg ci -m addc D/c + $ hg mv d/b d/e + moving D/b to D/e + $ hg st + A D/e + R D/b + $ hg revert -aq + $ rm d/e + $ hg mv d/b D/B + moving D/b to D/B + $ hg st + A D/B + R D/b $ cd .. test case collision between revisions (issue912)