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)