# HG changeset patch # User Benoit Boissinot # Date 2009-05-20 16:35:47 # Node ID 810387f59696e46c9980d9777301cce067d32ff6 # Parent 03196ac9a8b915f113bc08c4b42a7383b9fef512 filelog encoding: move the encoding/decoding into store the escaping of directories ending with .i or .d doesn't really belong to filelog. we put the encoding/decoding in store instead, for backwards compat, streamclone and the fncache file format still uses the partially encoded filenames. diff --git a/mercurial/filelog.py b/mercurial/filelog.py --- a/mercurial/filelog.py +++ b/mercurial/filelog.py @@ -10,21 +10,7 @@ import revlog class filelog(revlog.revlog): def __init__(self, opener, path): revlog.revlog.__init__(self, opener, - "/".join(("data", self.encodedir(path + ".i")))) - - # This avoids a collision between a file named foo and a dir named - # foo.i or foo.d - def encodedir(self, path): - return (path - .replace(".hg/", ".hg.hg/") - .replace(".i/", ".i.hg/") - .replace(".d/", ".d.hg/")) - - def decodedir(self, path): - return (path - .replace(".d.hg/", ".d/") - .replace(".i.hg/", ".i/") - .replace(".hg.hg/", ".hg/")) + "/".join(("data", path + ".i"))) def read(self, node): t = self.revision(node) diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -2026,7 +2026,8 @@ class localrepository(repo.repository): raise error.ResponseError( _('Unexpected response from remote server:'), l) self.ui.debug(_('adding %s (%s)\n') % (name, util.bytecount(size))) - ofp = self.sopener(name, 'w') + # for backwards compat, name was partially encoded + ofp = self.sopener(store.decodedir(name), 'w') for chunk in util.filechunkiter(fp, limit=size): ofp.write(chunk) ofp.close() diff --git a/mercurial/store.py b/mercurial/store.py --- a/mercurial/store.py +++ b/mercurial/store.py @@ -11,6 +11,24 @@ import os, stat _sha = util.sha1 +# This avoids a collision between a file named foo and a dir named +# foo.i or foo.d +def encodedir(path): + if not path.startswith('data/'): + return path + return (path + .replace(".hg/", ".hg.hg/") + .replace(".i/", ".i.hg/") + .replace(".d/", ".d.hg/")) + +def decodedir(path): + if not path.startswith('data/'): + return path + return (path + .replace(".d.hg/", ".d/") + .replace(".i.hg/", ".i/") + .replace(".hg.hg/", ".hg/")) + def _buildencodefun(): e = '_' win_reserved = [ord(x) for x in '\\:*?"<>|'] @@ -34,8 +52,8 @@ def _buildencodefun(): pass else: raise KeyError - return (lambda s: "".join([cmap[c] for c in s]), - lambda s: "".join(list(decode(s)))) + return (lambda s: "".join([cmap[c] for c in encodedir(s)]), + lambda s: decodedir("".join(list(decode(s))))) encodefilename, decodefilename = _buildencodefun() @@ -104,6 +122,8 @@ def hybridencode(path): ''' if not path.startswith('data/'): return path + # escape directories ending with .i and .d + path = encodedir(path) ndpath = path[len('data/'):] res = 'data/' + auxencode(encodefilename(ndpath)) if len(res) > MAX_PATH_LEN_IN_HGSTORE: @@ -155,7 +175,7 @@ class basicstore: self.opener.createmode = self.createmode def join(self, f): - return self.pathjoiner(self.path, f) + return self.pathjoiner(self.path, encodedir(f)) def _walk(self, relpath, recurse): '''yields (unencoded, encoded, size)''' @@ -170,7 +190,7 @@ class basicstore: fp = self.pathjoiner(p, f) if kind == stat.S_IFREG and f[-2:] in ('.d', '.i'): n = util.pconvert(fp[striplen:]) - l.append((n, n, st.st_size)) + l.append((decodedir(n), n, st.st_size)) elif kind == stat.S_IFDIR and recurse: visit.append(fp) return sorted(l) @@ -215,6 +235,8 @@ class encodedstore(basicstore): [self.pathjoiner('store', f) for f in _data.split()]) class fncache(object): + # the filename used to be partially encoded + # hence the encodedir/decodedir dance def __init__(self, opener): self.opener = opener self.entries = None @@ -231,20 +253,20 @@ class fncache(object): if (len(line) < 2) or (line[-1] != '\n'): t = _('invalid entry in fncache, line %s') % (n + 1) raise util.Abort(t) - self.entries.add(line[:-1]) + self.entries.add(decodedir(line[:-1])) fp.close() def rewrite(self, files): fp = self.opener('fncache', mode='wb') for p in files: - fp.write(p + '\n') + fp.write(encodedir(p) + '\n') fp.close() self.entries = set(files) def add(self, fn): if self.entries is None: self._load() - self.opener('fncache', 'ab').write(fn + '\n') + self.opener('fncache', 'ab').write(encodedir(fn) + '\n') def __contains__(self, fn): if self.entries is None: diff --git a/mercurial/streamclone.py b/mercurial/streamclone.py --- a/mercurial/streamclone.py +++ b/mercurial/streamclone.py @@ -8,6 +8,8 @@ import util, error from i18n import _ +from mercurial import store + class StreamException(Exception): def __init__(self, code): Exception.__init__(self) @@ -46,7 +48,8 @@ def stream_out(repo, untrusted=False): try: repo.ui.debug(_('scanning\n')) for name, ename, size in repo.store.walk(): - entries.append((name, size)) + # for backwards compat, name was partially encoded + entries.append((store.encodedir(name), size)) total_bytes += size finally: lock.release() diff --git a/tests/test-fncache.out b/tests/test-fncache.out --- a/tests/test-fncache.out +++ b/tests/test-fncache.out @@ -36,8 +36,8 @@ checking manifests crosschecking files in changesets and manifests checking files data/a.i@0: missing revlog! - data/a.i.hg.hg/c.i@2: missing revlog! - data/a.i.hg/b.i@1: missing revlog! + data/a.i.hg/c.i@2: missing revlog! + data/a.i/b.i@1: missing revlog! 3 files, 3 changesets, 3 total revisions 3 integrity errors encountered! (first damaged changeset appears to be 0)