1. Armin Ronacher
  2. heechee

Commits

Andrew Godwin  committed fa4366d

It can now serve flat-file hg repos, have rearranged into a module. Mostly so I can test directories ^_^

  • Participants
  • Parent commits dea001a
  • Branches default

Comments (0)

Files changed (7)

File heechee/__init__.py

Empty file added.

File heechee/repo.py

View file
+
+from mercurial import ui, hg
+
+
+class Repository(object):
+    
+    uuid = "00000000-80c4-4579-810c-ce57b7db7bfe"
+    
+    def __init__(self, directory="."):
+        self.hg_repo = hg.repository(ui.ui(), directory)
+    
+    def get_rev(self, rev):
+        "Returns the repository's file tree."
+        # Find the matching Mercurial revision
+        hg_rev = self.hg_repo.changectx("tip")
+        
+        root = Directory(name=None, parent=None, rev=rev)
+        # Loop through the files. We sort them by length - that ensures
+        # a directory will always come before its children
+        stack = [""]
+        for path in sorted(hg_rev, key=lambda x: len(x)):
+            File(
+                name = path,
+                contents = hg_rev[path].data(),
+                parent = root,
+                rev = rev,
+            )
+        # Return!
+        return root
+
+
+    
+class Versioned(object):
+    
+    def __init__(self, name, parent, rev):
+        self.name = name
+        self.parent = parent
+        self.rev = rev
+        if parent is not None:
+            parent.add_child(self)
+    
+    
+
+class Directory(Versioned):
+    
+    def __init__(self, *args, **kwargs):
+        self.children = []
+        Versioned.__init__(self, *args, **kwargs)
+    
+    def add_child(self, child):
+        self.children.append(child)
+    
+    def get_children(self):
+        return self.children
+
+
+
+class File(Versioned):
+    
+    def __init__(self, contents, *args, **kwargs):
+        self.contents = contents
+        Versioned.__init__(self, *args, **kwargs)
+    

File heechee/svndiff.py

View file
+
+import zlib
+
+
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
+OP_SOURCE_COPY = 0
+OP_TARGET_COPY = 1
+OP_NEW_DATA = 2
+
+
+def read_integer(handle):
+    """
+    Integers in svndiff are a string of bytes, with the first byte being
+    1 if there's more data in the next byte.
+    """
+    
+    number_so_far = 0
+    
+    while True:
+        byte = ord(handle.read(1))
+        # Add the lower 7 bits onto our current lot
+        number_so_far += byte & 127
+        # If the top byte is 1, continue.
+        if byte >> 7:
+            number_so_far <<= 7
+        else:
+            return number_so_far
+
+
+def make_integer(integer):
+    "Encodes an integer in the svndiff variable-length format"
+    output = ""
+    
+    was_shift = False
+    while True:
+        # Encode the lower 7 bits, then shift
+        lower = integer & 127
+        integer >>= 7
+        # Is there more?
+        if was_shift:
+            lower |= 128
+        output = chr(lower) + output
+        # If there was more, remember that
+        if integer:
+            was_shift = True
+        # If not, we're done
+        else:
+            return output
+
+
+def read_section(handle, current_length, use_zlib):
+    "Reads a section from the handle and possibly decompresses it."
+    if use_zlib:
+        offset_before = handle.tell()
+        original_length = read_integer(handle)
+        integer_length = handle.tell() - offset_before
+    else:
+        integer_length = 0
+    # Read the data and possible decode it
+    data = handle.read(current_length - integer_length)
+    #print repr(data), len(data), original_length
+    # If the length is different, zlib decode it
+    if use_zlib and len(data) != original_length:
+        data = zlib.decompress(data[1:])
+    return data
+
+
+def undiff(source, diff):
+    
+    # Initialise the target
+    target = StringIO()
+    
+    # Get the header and the diff version
+    assert diff.read(3) == "SVN"
+    version_byte = diff.read(1)
+    if version_byte == "\0":
+        use_zlib = False
+    elif version_byte == "\1":
+        use_zlib = True
+    else:
+        raise ValueError("Invalid svndiff version: %i" % ord(version_byte))
+    
+    # Get the various window information
+    source_offset = read_integer(diff)
+    source_length = read_integer(diff)
+    target_length = read_integer(diff)
+    instructions_length = read_integer(diff)
+    newdata_length = read_integer(diff)
+    
+    #print use_zlib, source_offset, source_length, target_length, instructions_length, newdata_length
+    
+    # Read the instructions and newdata into their own stringios.
+    # First, get the "original length" header, and see how many bits it was
+    instruction_data = read_section(diff, instructions_length, use_zlib)
+    newdata_data = read_section(diff, newdata_length, use_zlib)
+    
+    instructions = StringIO(instruction_data)
+    newdata = StringIO(newdata_data)
+    
+    while True:
+        # The first byte should be an op plus either an offset or 0s.
+        try:
+            first_byte = ord(instructions.read(1))
+        except TypeError:
+            # End of file.
+            break
+        
+        op = first_byte >> 6      # Top 2 bytes
+        length = first_byte & 63  # Get lower 6 bytes
+
+        # If the length is zero, the length is an integer following.
+        if length == 0:
+            length = read_integer(instructions)
+        
+        if op is OP_SOURCE_COPY:
+            offset = read_integer(instructions)
+            source.seek(offset)
+            target.write(source.read(length))
+            #print "source copy", length, offset
+        elif op is OP_TARGET_COPY:
+            offset = read_integer(instructions)
+            for i in range(length):
+                target.seek(offset+i)
+                byte = target.read(1)
+                target.seek(0, 2)
+                target.write(byte)
+            #print "target copy", length, offset
+        elif op is OP_NEW_DATA:
+            target.write(newdata.read(length))
+            #print "newdata copy", length
+        else:
+            raise ValueError("Unknown op")
+        
+    return target.getvalue()
+
+
+def make_cheap_diff(text):
+    "Makes a completely uncompressed svndiff of the given text."
+    
+    instructions = chr(OP_NEW_DATA << 6) + make_integer(len(text))
+    
+    return ("SVN\0" +
+           "\0" + # Source offset
+           "\0" + # Source length
+           make_integer(len(text)) + # Target length
+           make_integer(len(instructions)) + # Instructions length
+           make_integer(len(text)) + # New data length
+           instructions +
+           text)

File heechee/webdav.py

View file
+
+from lxml.etree import fromstring, tostring, Element, SubElement
+from werkzeug import Request, Response
+from werkzeug.exceptions import HTTPException, MethodNotAllowed, BadRequest
+
+from heechee.svndiff import make_cheap_diff
+from heechee.repo import Repository, File, Directory
+
+DAV_NS = "{DAV:}"
+SVN_NS = "{svn:}"
+SVN_DAV_NS = "{http://subversion.tigris.org/xmlns/dav/}"
+
+
+
+class Application(object):
+    
+    verbs = ["OPTIONS", "PROPFIND", "REPORT"]
+    
+    def __init__(self, repo):
+        self.repo = repo
+    
+    @Request.application
+    def __call__(self, request):
+        try:
+            if hasattr(self, request.method):
+                return getattr(self, request.method)(request)
+            else:
+                raise MethodNotAllowed(self.verbs)
+        except HTTPException, e:
+            return e
+    
+    def OPTIONS(self, request):
+        """
+        Look, Ma, we support ALL THESE methods. Really.
+        """
+        return Response(headers = {
+            "Allow": ",".join(self.verbs),
+        })
+    
+    def PROPFIND(self, request):
+        """
+        Responds to SVN's PROPFIND requests, which usually detail finding out
+        various things about the repo.
+        """
+        # Do they want the properties of the overall repo?
+        data = request.input_stream.read(request.content_length)
+        tree = fromstring(data)
+        
+        props = [x.tag.split("}")[-1] for x in tree.find("{DAV:}prop").getchildren()]
+        
+        if request.path == "/":
+            response = self._propstat_response(request, {
+                DAV_NS+"version-controlled-configuration": self._href("/!svn/vcc/default"),
+                DAV_NS+"resourcetype": Element(DAV_NS+"collection"),
+                SVN_DAV_NS+"baseline-relative-path": None,
+                SVN_DAV_NS+"repository-uuid": self.repo.uuid,
+            })
+        
+        elif request.path == "/!svn/vcc/default":
+            answers = {}
+            
+            if "checked-in" in props:
+                answers[DAV_NS+"checked-in"] = self._href("/!svn/bln/1/")
+            
+            if "baseline-collection" in props:
+                answers[DAV_NS+"baseline-collection"] = self._href("/!svn/bc/1/")
+            
+            if "version-name" in props:
+                answers[DAV_NS+"version-name"] = "1"
+            
+            response = self._propstat_response(request, answers)
+        
+        elif request.path == "/!svn/bln/1":
+            response = self._propstat_response(request, {
+                DAV_NS+"baseline-collection": self._href("/!svn/bc/1/"),
+                DAV_NS+"version-name": '1',
+            })
+        
+        elif request.path == "/!svn/bc/1":
+            response = self._propstat_response(request, {
+                DAV_NS+"version-controlled-configuration": self._href("/!svn/vcc/default"),
+                DAV_NS+"resourcetype": Element(DAV_NS+"collection"),
+                SVN_NS+"baseline-relative-path": None,
+                SVN_NS+"repository-uuid": self.repo.uuid,
+            })
+        
+        # We don't recognise it! How unlikely.
+        else:
+            raise BadRequest()
+        
+        return Response(response, status = 207)
+    
+    def REPORT(self, request):
+        "i.e. SVN-wants-a-massive-diff time."
+        up_rep = Element(SVN_NS+"update-report")
+        up_rep.attrib['send-all'] = "true"
+        
+        SubElement(up_rep, SVN_NS+"target-revision", rev="1")
+        
+        # The root directory is special.
+        root_directory = self.repo.get_rev(1)
+        dir = SubElement(up_rep, SVN_NS+"open-directory", rev="1")
+        dir_ch_in = SubElement(dir, DAV_NS+"checked-in")
+        dir_ch_in.append(self._href("/!svn/ver/1/"))
+        
+        # The queue is a list of (parent, item) tuples.
+        queue = [(dir, child) for child in root_directory.get_children()]
+        while queue:
+            parent, item = queue.pop()
+            if isinstance(item, Directory):
+                # Make the directory's entry
+                dir = SubElement(parent, SVN_NS+"add-directory", name=item.name, rev="1")
+                dir_ch_in = SubElement(dir, DAV_NS+"checked-in")
+                dir_ch_in.append(self._href("/!svn/ver/1/"))
+                # Add its children
+                for child in item.get_children():
+                    queue.append((dir, child))
+            elif isinstance(item, File):
+                # Make the file object
+                file = SubElement(parent, SVN_NS+"add-file", name=item.name)
+                dir_ch_in = SubElement(file, DAV_NS+"checked-in")
+                dir_ch_in.append(self._href("/!svn/ver/1/hello.txt"))
+                rev = SubElement(file, SVN_NS+"set-prop", name="svn:entry:committed-rev")
+                rev.text = "1"
+                txdelta = SubElement(file, SVN_NS+"txdelta")
+                txdelta.text = make_cheap_diff(item.contents).encode("base64")
+        
+        return Response(tostring(up_rep))
+    
+    def _href(self, target):
+        "Single-call making of DAV:href tags."
+        elem = Element(DAV_NS+"href")
+        elem.text = target
+        return elem
+    
+    def _propstat_response(self, request, props, status="HTTP/1.1 200 OK"):
+        "Creates a multi-status response."
+        multi = Element(DAV_NS + "multistatus")
+        response = SubElement(multi, DAV_NS + "response")
+        # Path of this request
+        href = SubElement(response, DAV_NS + "href")
+        href.text = request.path
+        # It's gonna be a propstat
+        propstat = SubElement(response, DAV_NS + "propstat")
+        prop = SubElement(propstat, DAV_NS + "prop")
+        # Add in the props we're told about
+        for tag, value in props.items():
+            elem = SubElement(prop, tag)
+            # Is it a string value?
+            if isinstance(value, basestring):
+                elem.text = value
+            # Is it None? (do nothing)
+            elif value is None:
+                pass
+            # Then it's hopefully an element.
+            else:
+                elem.append(value)
+        # Finally, add in the status
+        SubElement(propstat, DAV_NS + "status").text = status
+        return tostring(multi)
+
+if __name__ == "__main__":
+    from werkzeug import run_simple
+    run_simple('localhost', 8080, Application(Repository()))

File main.py

-
-from lxml.etree import fromstring, tostring, Element, SubElement
-from werkzeug import Request, Response
-from werkzeug.exceptions import HTTPException, MethodNotAllowed, BadRequest
-
-from svndiff import make_cheap_diff
-from repo import Repository, File, Directory
-
-DAV_NS = "{DAV:}"
-SVN_NS = "{svn:}"
-SVN_DAV_NS = "{http://subversion.tigris.org/xmlns/dav/}"
-
-
-
-class Application(object):
-    
-    verbs = ["OPTIONS", "PROPFIND", "REPORT"]
-    
-    def __init__(self, repo):
-        self.repo = repo
-    
-    @Request.application
-    def __call__(self, request):
-        try:
-            if hasattr(self, request.method):
-                return getattr(self, request.method)(request)
-            else:
-                raise MethodNotAllowed(self.verbs)
-        except HTTPException, e:
-            return e
-    
-    def OPTIONS(self, request):
-        """
-        Look, Ma, we support ALL THESE methods. Really.
-        """
-        return Response(headers = {
-            "Allow": ",".join(self.verbs),
-        })
-    
-    def PROPFIND(self, request):
-        """
-        Responds to SVN's PROPFIND requests, which usually detail finding out
-        various things about the repo.
-        """
-        # Do they want the properties of the overall repo?
-        data = request.input_stream.read(request.content_length)
-        tree = fromstring(data)
-        
-        props = [x.tag.split("}")[-1] for x in tree.find("{DAV:}prop").getchildren()]
-        
-        if request.path == "/":
-            response = self._propstat_response(request, {
-                DAV_NS+"version-controlled-configuration": self._href("/!svn/vcc/default"),
-                DAV_NS+"resourcetype": Element(DAV_NS+"collection"),
-                SVN_DAV_NS+"baseline-relative-path": None,
-                SVN_DAV_NS+"repository-uuid": self.repo.uuid,
-            })
-        
-        elif request.path == "/!svn/vcc/default":
-            answers = {}
-            
-            if "checked-in" in props:
-                answers[DAV_NS+"checked-in"] = self._href("/!svn/bln/1/")
-            
-            if "baseline-collection" in props:
-                answers[DAV_NS+"baseline-collection"] = self._href("/!svn/bc/1/")
-            
-            if "version-name" in props:
-                answers[DAV_NS+"version-name"] = "1"
-            
-            response = self._propstat_response(request, answers)
-        
-        elif request.path == "/!svn/bln/1":
-            response = self._propstat_response(request, {
-                DAV_NS+"baseline-collection": self._href("/!svn/bc/1/"),
-                DAV_NS+"version-name": '1',
-            })
-        
-        elif request.path == "/!svn/bc/1":
-            response = self._propstat_response(request, {
-                DAV_NS+"version-controlled-configuration": self._href("/!svn/vcc/default"),
-                DAV_NS+"resourcetype": Element(DAV_NS+"collection"),
-                SVN_NS+"baseline-relative-path": None,
-                SVN_NS+"repository-uuid": self.repo.uuid,
-            })
-        
-        # We don't recognise it! How unlikely.
-        else:
-            raise BadRequest()
-        
-        return Response(response, status = 207)
-    
-    def REPORT(self, request):
-        "i.e. SVN-wants-a-massive-diff time."
-        up_rep = Element(SVN_NS+"update-report")
-        up_rep.attrib['send-all'] = "true"
-        
-        SubElement(up_rep, SVN_NS+"target-revision", rev="1")
-        
-        # The queue is a list of (parent, item) tuples.
-        queue = [(up_rep, self.repo.get_rev(1))]
-        while queue:
-            parent, item = queue.pop()
-            if isinstance(item, Directory):
-                # Make the directory's entry
-                dir = SubElement(parent, SVN_NS+"open-directory", rev="1")
-                dir_ch_in = SubElement(dir, DAV_NS+"checked-in")
-                dir_ch_in.append(self._href("/!svn/ver/1/"))
-                # Add its children
-                for child in item.get_children():
-                    queue.append((dir, child))
-            elif isinstance(item, File):
-                # Make the file object
-                file = SubElement(parent, SVN_NS+"add-file", name=item.name)
-                dir_ch_in = SubElement(file, DAV_NS+"checked-in")
-                dir_ch_in.append(self._href("/!svn/ver/1/hello.txt"))
-                rev = SubElement(file, SVN_NS+"set-prop", name="svn:entry:committed-rev")
-                rev.text = "1"
-                txdelta = SubElement(file, SVN_NS+"txdelta")
-                txdelta.text = make_cheap_diff(item.contents).encode("base64")
-        
-        return Response(tostring(up_rep))
-    
-    def _href(self, target):
-        "Single-call making of DAV:href tags."
-        elem = Element(DAV_NS+"href")
-        elem.text = target
-        return elem
-    
-    def _propstat_response(self, request, props, status="HTTP/1.1 200 OK"):
-        "Creates a multi-status response."
-        multi = Element(DAV_NS + "multistatus")
-        response = SubElement(multi, DAV_NS + "response")
-        # Path of this request
-        href = SubElement(response, DAV_NS + "href")
-        href.text = request.path
-        # It's gonna be a propstat
-        propstat = SubElement(response, DAV_NS + "propstat")
-        prop = SubElement(propstat, DAV_NS + "prop")
-        # Add in the props we're told about
-        for tag, value in props.items():
-            elem = SubElement(prop, tag)
-            # Is it a string value?
-            if isinstance(value, basestring):
-                elem.text = value
-            # Is it None? (do nothing)
-            elif value is None:
-                pass
-            # Then it's hopefully an element.
-            else:
-                elem.append(value)
-        # Finally, add in the status
-        SubElement(propstat, DAV_NS + "status").text = status
-        return tostring(multi)
-
-if __name__ == "__main__":
-    from werkzeug import run_simple
-    run_simple('localhost', 8080, Application(Repository()))

File repo.py

-
-from mercurial import *
-
-
-class Repository(object):
-    
-    uuid = "00000000-80c4-4579-810c-ce57b7db7bfe"
-    
-    def get_rev(self, rev):
-        "Returns the repository's file tree."
-        file_1 = File("hello-world.txt", "Hello, world!\nI'm not really from SVN. AHAHAHAHA.\n", rev=rev)
-        return Directory(None, [file_1], rev=rev)
-
-
-    
-class Versioned(object):
-    
-    def __init__(self, rev):
-        self.rev = rev
-    
-    
-
-class Directory(Versioned):
-    
-    def __init__(self, name, children=None, *args, **kwargs):
-        self.name = name
-        if children is None:
-            children = []
-        self.children = children
-        Versioned.__init__(self, *args, **kwargs)
-    
-    def get_children(self):
-        return self.children
-
-
-
-class File(Versioned):
-    
-    def __init__(self, name, contents, *args, **kwargs):
-        self.name = name
-        self.contents = contents
-        Versioned.__init__(self, *args, **kwargs)
-    

File svndiff.py

-
-import zlib
-
-
-try:
-    from cStringIO import StringIO
-except ImportError:
-    from StringIO import StringIO
-
-OP_SOURCE_COPY = 0
-OP_TARGET_COPY = 1
-OP_NEW_DATA = 2
-
-
-def read_integer(handle):
-    """
-    Integers in svndiff are a string of bytes, with the first byte being
-    1 if there's more data in the next byte.
-    """
-    
-    number_so_far = 0
-    
-    while True:
-        byte = ord(handle.read(1))
-        # Add the lower 7 bits onto our current lot
-        number_so_far += byte & 127
-        # If the top byte is 1, continue.
-        if byte >> 7:
-            number_so_far <<= 7
-        else:
-            return number_so_far
-
-
-def make_integer(integer):
-    "Encodes an integer in the svndiff variable-length format"
-    output = ""
-    
-    was_shift = False
-    while True:
-        # Encode the lower 7 bits, then shift
-        lower = integer & 127
-        integer >>= 7
-        # Is there more?
-        if was_shift:
-            lower |= 128
-        output = chr(lower) + output
-        # If there was more, remember that
-        if integer:
-            was_shift = True
-        # If not, we're done
-        else:
-            return output
-
-
-def read_section(handle, current_length, use_zlib):
-    "Reads a section from the handle and possibly decompresses it."
-    if use_zlib:
-        offset_before = handle.tell()
-        original_length = read_integer(handle)
-        integer_length = handle.tell() - offset_before
-    else:
-        integer_length = 0
-    # Read the data and possible decode it
-    data = handle.read(current_length - integer_length)
-    #print repr(data), len(data), original_length
-    # If the length is different, zlib decode it
-    if use_zlib and len(data) != original_length:
-        data = zlib.decompress(data[1:])
-    return data
-
-
-def undiff(source, diff):
-    
-    # Initialise the target
-    target = StringIO()
-    
-    # Get the header and the diff version
-    assert diff.read(3) == "SVN"
-    version_byte = diff.read(1)
-    if version_byte == "\0":
-        use_zlib = False
-    elif version_byte == "\1":
-        use_zlib = True
-    else:
-        raise ValueError("Invalid svndiff version: %i" % ord(version_byte))
-    
-    # Get the various window information
-    source_offset = read_integer(diff)
-    source_length = read_integer(diff)
-    target_length = read_integer(diff)
-    instructions_length = read_integer(diff)
-    newdata_length = read_integer(diff)
-    
-    #print use_zlib, source_offset, source_length, target_length, instructions_length, newdata_length
-    
-    # Read the instructions and newdata into their own stringios.
-    # First, get the "original length" header, and see how many bits it was
-    instruction_data = read_section(diff, instructions_length, use_zlib)
-    newdata_data = read_section(diff, newdata_length, use_zlib)
-    
-    instructions = StringIO(instruction_data)
-    newdata = StringIO(newdata_data)
-    
-    while True:
-        # The first byte should be an op plus either an offset or 0s.
-        try:
-            first_byte = ord(instructions.read(1))
-        except TypeError:
-            # End of file.
-            break
-        
-        op = first_byte >> 6      # Top 2 bytes
-        length = first_byte & 63  # Get lower 6 bytes
-
-        # If the length is zero, the length is an integer following.
-        if length == 0:
-            length = read_integer(instructions)
-        
-        if op is OP_SOURCE_COPY:
-            offset = read_integer(instructions)
-            source.seek(offset)
-            target.write(source.read(length))
-            #print "source copy", length, offset
-        elif op is OP_TARGET_COPY:
-            offset = read_integer(instructions)
-            for i in range(length):
-                target.seek(offset+i)
-                byte = target.read(1)
-                target.seek(0, 2)
-                target.write(byte)
-            #print "target copy", length, offset
-        elif op is OP_NEW_DATA:
-            target.write(newdata.read(length))
-            #print "newdata copy", length
-        else:
-            raise ValueError("Unknown op")
-        
-    return target.getvalue()
-
-
-def make_cheap_diff(text):
-    "Makes a completely uncompressed svndiff of the given text."
-    
-    instructions = chr(OP_NEW_DATA << 6) + make_integer(len(text))
-    
-    return ("SVN\0" +
-           "\0" + # Source offset
-           "\0" + # Source length
-           make_integer(len(text)) + # Target length
-           make_integer(len(instructions)) + # Instructions length
-           make_integer(len(text)) + # New data length
-           instructions +
-           text)