[PATCH] store: encode first period or space in filenames (issue1713)

Adrian Buehlmann adrian at cadifra.com
Sat Oct 9 14:58:17 CDT 2010


# HG changeset patch
# User Adrian Buehlmann <adrian at cadifra.com>
# Date 1286654090 -7200
# Node ID f7bb27fc4f8715c25c912cf8290168c3808eb8b3
# Parent  7285b2824fb7c40d6f7569417cc10ac3e88ddf07
store: encode first period or space in filenames (issue1713)

- Mac OS X has problems with filenames starting with '._'
  (e.g. '.FOO' -> '._f_o_o' is now encoded as '~2e_f_o_o')

- Explorer of Windows Vista and Windows 7 strip leading spaces of
  path elements of filenames when copying trees

Above problems are avoided by encoding the first space (as '~20') or
period (as '~2e') of all path elements.

This introduces a new entry 'dotencode' in .hg/requires, that is,
a new repository filename layout (inside .hg/store).

Newly created repositories require 'dotencode' by default. Specifying

  [format]
  dotencode = False

in a config file will use the old format instead.

Prior Mercurial versions will abort with the message

   abort: requirement 'dotencode' not supported!

when trying to access a local repository that requires 'dotencode'.

New 'dotencode' repositories can be converted to the previous
repository format with

  hg --config format.dotencode=0 clone --pull repoA repoB

diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -22,7 +22,8 @@ propertycache = util.propertycache
 class localrepository(repo.repository):
     capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey'))
     supportedformats = set(('revlogv1', 'parentdelta'))
-    supported = supportedformats | set(('store', 'fncache', 'shared'))
+    supported = supportedformats | set(('store', 'fncache', 'shared',
+                                        'dotencode'))
 
     def __init__(self, baseui, path=None, create=0):
         repo.repository.__init__(self)
@@ -52,6 +53,8 @@ class localrepository(repo.repository):
                     requirements.append("store")
                     if self.ui.configbool('format', 'usefncache', True):
                         requirements.append("fncache")
+                        if self.ui.configbool('format', 'dotencode', True):
+                            requirements.append('dotencode')
                     # create an invalid changelog
                     self.opener("00changelog.i", "a").write(
                         '\0\0\0\2' # represents revlogv2
diff --git a/mercurial/store.py b/mercurial/store.py
--- a/mercurial/store.py
+++ b/mercurial/store.py
@@ -71,7 +71,7 @@ lowerencode = _build_lower_encodefun()
 _windows_reserved_filenames = '''con prn aux nul
     com1 com2 com3 com4 com5 com6 com7 com8 com9
     lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
-def auxencode(path):
+def _auxencode(path, dotencode):
     res = []
     for n in path.split('/'):
         if n:
@@ -83,13 +83,15 @@ def auxencode(path):
             if n[-1] in '. ':
                 # encode last period or space ('foo...' -> 'foo..~2e')
                 n = n[:-1] + "~%02x" % ord(n[-1])
+            if dotencode and n[0] in '. ':
+                n = "~%02x" % ord(n[0]) + n[1:]
         res.append(n)
     return '/'.join(res)
 
 MAX_PATH_LEN_IN_HGSTORE = 120
 DIR_PREFIX_LEN = 8
 _MAX_SHORTENED_DIRS_LEN = 8 * (DIR_PREFIX_LEN + 1) - 4
-def hybridencode(path):
+def _hybridencode(path, auxencode):
     '''encodes path with a length limit
 
     Encodes all paths that begin with 'data/', according to the following.
@@ -282,7 +284,8 @@ class fncache(object):
         return iter(self.entries)
 
 class fncachestore(basicstore):
-    def __init__(self, path, opener, pathjoiner):
+    def __init__(self, path, opener, pathjoiner, encode):
+        self.encode = encode
         self.pathjoiner = pathjoiner
         self.path = self.pathjoiner(path, 'store')
         self.createmode = _calcmode(self.path)
@@ -294,11 +297,11 @@ class fncachestore(basicstore):
         def fncacheopener(path, mode='r', *args, **kw):
             if mode not in ('r', 'rb') and path.startswith('data/'):
                 fnc.add(path)
-            return op(hybridencode(path), mode, *args, **kw)
+            return op(self.encode(path), mode, *args, **kw)
         self.opener = fncacheopener
 
     def join(self, f):
-        return self.pathjoiner(self.path, hybridencode(f))
+        return self.pathjoiner(self.path, self.encode(f))
 
     def datafiles(self):
         rewrite = False
@@ -306,7 +309,7 @@ class fncachestore(basicstore):
         pjoin = self.pathjoiner
         spath = self.path
         for f in self.fncache:
-            ef = hybridencode(f)
+            ef = self.encode(f)
             try:
                 st = os.stat(pjoin(spath, ef))
                 yield f, ef, st.st_size
@@ -328,6 +331,8 @@ def store(requirements, path, opener, pa
     pathjoiner = pathjoiner or os.path.join
     if 'store' in requirements:
         if 'fncache' in requirements:
-            return fncachestore(path, opener, pathjoiner)
+            auxencode = lambda f: _auxencode(f, 'dotencode' in requirements)
+            encode = lambda f: _hybridencode(f, auxencode)
+            return fncachestore(path, opener, pathjoiner, encode)
         return encodedstore(path, opener, pathjoiner)
     return basicstore(path, opener, pathjoiner)
diff --git a/tests/test-hybridencode.py b/tests/test-hybridencode.py
--- a/tests/test-hybridencode.py
+++ b/tests/test-hybridencode.py
@@ -2,7 +2,10 @@
 
 from mercurial import store
 
-enc = store.hybridencode # used for fncache repo format
+auxencode = lambda f: store._auxencode(f, True)
+hybridencode = lambda f: store._hybridencode(f, auxencode)
+
+enc = hybridencode # used for 'dotencode' repo format
 
 def show(s):
     print "A = '%s'" % s
@@ -22,4 +25,5 @@ show('data/Project Planning/Resources/An
      'Followedbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt')
 show('data/Project.Planning/Resources/AnotherLongDirectoryName/'
      'Followedbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt')
-show('data/foo.../foo   / /a./_. /__/.x../    bla/something.i')
+show('data/foo.../foo   / /a./_. /__/.x../    bla/.FOO/something.i')
+
diff --git a/tests/test-hybridencode.py.out b/tests/test-hybridencode.py.out
--- a/tests/test-hybridencode.py.out
+++ b/tests/test-hybridencode.py.out
@@ -16,6 +16,6 @@ B = 'dh/project_/resource/anotherl/follo
 A = 'data/Project.Planning/Resources/AnotherLongDirectoryName/Followedbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt'
 B = 'dh/project_/resource/anotherl/followed/andanoth/andthenanextremelylongfilena0fd7c506f5c9d58204444fc67e9499006bd2d445.txt'
 
-A = 'data/foo.../foo   / /a./_. /__/.x../    bla/something.i'
-B = 'data/foo..~2e/foo  ~20/~20/a~2e/__.~20/____/.x.~2e/    bla/something.i'
+A = 'data/foo.../foo   / /a./_. /__/.x../    bla/.FOO/something.i'
+B = 'data/foo..~2e/foo  ~20/~20/a~2e/__.~20/____/~2ex.~2e/~20   bla/~2e_f_o_o/something.i'
 
diff --git a/tests/test-init.t b/tests/test-init.t
--- a/tests/test-init.t
+++ b/tests/test-init.t
@@ -42,6 +42,7 @@ creating 'local'
   revlogv1
   store
   fncache
+  dotencode
   $ echo this > local/foo
   $ hg ci --cwd local -A -m "init"
   adding foo
@@ -157,6 +158,7 @@ creating 'local/sub/repo'
   revlogv1
   store
   fncache
+  dotencode
 
 prepare test of init of url configured from paths
 
@@ -173,6 +175,7 @@ init should (for consistency with clone)
   revlogv1
   store
   fncache
+  dotencode
 
 verify that clone also expand urls
 
@@ -185,3 +188,4 @@ verify that clone also expand urls
   revlogv1
   store
   fncache
+  dotencode


More information about the Mercurial-devel mailing list