hgext/lfs/pointer.py
changeset 35098 66c5a8cf2868
child 35099 b8e5fb8d2389
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/lfs/pointer.py	Tue Nov 14 00:06:23 2017 -0500
@@ -0,0 +1,72 @@
+# 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 import (
+    error,
+)
+from mercurial.i18n import _
+
+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, **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(r'\A[a-z0-9.-]+\Z')
+    _valuere = re.compile(r'\A[^\n]*\Z')
+    _requiredre = {
+        'size': re.compile(r'\A[0-9]+\Z'),
+        'oid': re.compile(r'\Asha256:[0-9a-f]{64}\Z'),
+        'version': re.compile(r'\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