mercurial/store.py
author Dirkjan Ochtman <dirkjan@ochtman.nl>
Sat, 18 Oct 2008 18:24:37 +0200
changeset 7132 e9a2525d8834
parent 6989 32e68ffccbc5
child 7229 7946503ec76e
permissions -rw-r--r--
tests: fix output for test-notify

# store.py - repository store handling for Mercurial
#
# Copyright 2008 Matt Mackall <mpm@selenic.com>
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.

import os, stat, osutil, util

def _buildencodefun():
    e = '_'
    win_reserved = [ord(x) for x in '\\:*?"<>|']
    cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
    for x in (range(32) + range(126, 256) + win_reserved):
        cmap[chr(x)] = "~%02x" % x
    for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
        cmap[chr(x)] = e + chr(x).lower()
    dmap = {}
    for k, v in cmap.iteritems():
        dmap[v] = k
    def decode(s):
        i = 0
        while i < len(s):
            for l in xrange(1, 4):
                try:
                    yield dmap[s[i:i+l]]
                    i += l
                    break
                except KeyError:
                    pass
            else:
                raise KeyError
    return (lambda s: "".join([cmap[c] for c in s]),
            lambda s: "".join(list(decode(s))))

encodefilename, decodefilename = _buildencodefun()

def _calcmode(path):
    try:
        # files in .hg/ will be created using this mode
        mode = os.stat(path).st_mode
            # avoid some useless chmods
        if (0777 & ~util._umask) == (0777 & mode):
            mode = None
    except OSError:
        mode = None
    return mode

_data = 'data 00manifest.d 00manifest.i 00changelog.d  00changelog.i'

class basicstore:
    '''base class for local repository stores'''
    def __init__(self, path, opener, pathjoiner):
        self.pathjoiner = pathjoiner
        self.path = path
        self.createmode = _calcmode(path)
        self.opener = opener(self.path)
        self.opener.createmode = self.createmode

    def join(self, f):
        return self.pathjoiner(self.path, f)

    def _walk(self, relpath, recurse):
        '''yields (unencoded, encoded, size)'''
        path = self.pathjoiner(self.path, relpath)
        striplen = len(self.path) + len(os.sep)
        prefix = path[striplen:]
        l = []
        if os.path.isdir(path):
            visit = [path]
            while visit:
                p = visit.pop()
                for f, kind, st in osutil.listdir(p, stat=True):
                    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))
                    elif kind == stat.S_IFDIR and recurse:
                        visit.append(fp)
        return util.sort(l)

    def datafiles(self):
        return self._walk('data', True)

    def walk(self):
        '''yields (unencoded, encoded, size)'''
        # yield data files first
        for x in self.datafiles():
            yield x
        # yield manifest before changelog
        meta = self._walk('', False)
        meta.reverse()
        for x in meta:
            yield x

    def copylist(self):
        return ['requires'] + _data.split()

class encodedstore(basicstore):
    def __init__(self, path, opener, pathjoiner):
        self.pathjoiner = pathjoiner
        self.path = self.pathjoiner(path, 'store')
        self.createmode = _calcmode(self.path)
        op = opener(self.path)
        op.createmode = self.createmode
        self.opener = lambda f, *args, **kw: op(encodefilename(f), *args, **kw)

    def datafiles(self):
        for a, b, size in self._walk('data', True):
            try:
                a = decodefilename(a)
            except KeyError:
                a = None
            yield a, b, size

    def join(self, f):
        return self.pathjoiner(self.path, encodefilename(f))

    def copylist(self):
        return (['requires', '00changelog.i'] +
                [self.pathjoiner('store', f) for f in _data.split()])

def store(requirements, path, opener, pathjoiner=None):
    pathjoiner = pathjoiner or os.path.join
    if 'store' in requirements:
        return encodedstore(path, opener, pathjoiner)
    return basicstore(path, opener, pathjoiner)