# HG changeset patch # User Adrian Buehlmann # Date 2010-10-09 19:54:50 # Node ID 34d8247a4595f41ed9f22a065975ecd84f670651 # Parent fe31f834a9ff935185626416d93fd80055bef1f4 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