hgext/lfs/pointer.py
author Matt Harbison <matt_harbison@yahoo.com>
Sat, 10 Mar 2018 23:58:01 -0500
changeset 36991 d683c7367989
parent 36602 e30be4d2ac60
child 37926 fb6226c15e54
permissions -rw-r--r--
wireproto: explicitly flush stdio to prevent stalls on Windows This is the key to fixing the hangs on Windows in D2720[1]. I put flushes in a bunch of other places that didn't help, but I suspect that's more a lack of test coverage than anything else. Chasing down stuff like this is pretty painful. I'm wondering if we can put a proxy around sys.stderr (and sys.stdout?) on Windows (only when daemonized?) that will flush on every write (or at least every write with a '\n'). [1] https://www.mercurial-scm.org/pipermail/mercurial-devel/2018-March/113352.html

# pointer.py - Git-LFS pointer serialization
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.

from __future__ import absolute_import

import re

from mercurial.i18n import _

from mercurial import (
    error,
    pycompat,
)

class InvalidPointer(error.RevlogError):
    pass

class gitlfspointer(dict):
    VERSION = 'https://git-lfs.github.com/spec/v1'

    def __init__(self, *args, **kwargs):
        self['version'] = self.VERSION
        super(gitlfspointer, self).__init__(*args)
        self.update(pycompat.byteskwargs(kwargs))

    @classmethod
    def deserialize(cls, text):
        try:
            return cls(l.split(' ', 1) for l in text.splitlines()).validate()
        except ValueError: # l.split returns 1 item instead of 2
            raise InvalidPointer(_('cannot parse git-lfs text: %r') % text)

    def serialize(self):
        sortkeyfunc = lambda x: (x[0] != 'version', x)
        items = sorted(self.validate().iteritems(), key=sortkeyfunc)
        return ''.join('%s %s\n' % (k, v) for k, v in items)

    def oid(self):
        return self['oid'].split(':')[-1]

    def size(self):
        return int(self['size'])

    # regular expressions used by _validate
    # see https://github.com/git-lfs/git-lfs/blob/master/docs/spec.md
    _keyre = re.compile(br'\A[a-z0-9.-]+\Z')
    _valuere = re.compile(br'\A[^\n]*\Z')
    _requiredre = {
        'size': re.compile(br'\A[0-9]+\Z'),
        'oid': re.compile(br'\Asha256:[0-9a-f]{64}\Z'),
        'version': re.compile(br'\A%s\Z' % re.escape(VERSION)),
    }

    def validate(self):
        """raise InvalidPointer on error. return self if there is no error"""
        requiredcount = 0
        for k, v in self.iteritems():
            if k in self._requiredre:
                if not self._requiredre[k].match(v):
                    raise InvalidPointer(_('unexpected value: %s=%r') % (k, v))
                requiredcount += 1
            elif not self._keyre.match(k):
                raise InvalidPointer(_('unexpected key: %s') % k)
            if not self._valuere.match(v):
                raise InvalidPointer(_('unexpected value: %s=%r') % (k, v))
        if len(self._requiredre) != requiredcount:
            miss = sorted(set(self._requiredre.keys()).difference(self.keys()))
            raise InvalidPointer(_('missed keys: %s') % ', '.join(miss))
        return self

deserialize = gitlfspointer.deserialize