diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -268,6 +268,11 @@ def copy(ui, repo, pats, opts, rename=Fa # 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) diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py --- a/mercurial/dirstate.py +++ b/mercurial/dirstate.py @@ -408,32 +408,48 @@ class dirstate(object): 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 @@ class dirstate(object): ''' if self._checkcase: - return self._normalize(path, isknown) + return self._normalize(path, isknown, ignoremissing) return path def clear(self): @@ -575,7 +591,7 @@ class dirstate(object): 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 @@ class dirstate(object): # 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 @@ class dirstate(object): 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): diff --git a/tests/test-casefolding.t b/tests/test-casefolding.t --- a/tests/test-casefolding.t +++ b/tests/test-casefolding.t @@ -32,6 +32,42 @@ Case-changing renames should work: $ 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)