Andrew Godwin avatar Andrew Godwin committed dea001a

A slightly-more-well-written example, that actually checks out!

Comments (0)

Files changed (2)

 
-from lxml.etree import fromstring
+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(["OPTIONS", "PROPFIND"])
+                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": "OPTIONS,GET,HEAD,POST,DELETE,TRACE,PROPFIND,PROPPATCH,COPY,MOVE,LOCK,UNLOCK,CHECKOUT"
+            "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 == "/":
-            vcc = "/!svn/vcc/default"
-            uuid = "00000000-80c4-4579-810c-ce57b7db7bfe"
-            response = """<?xml version="1.0" encoding="utf-8"?>
-<D:multistatus xmlns:D="DAV:" xmlns:ns1="http://subversion.tigris.org/xmlns/dav/" xmlns:ns0="DAV:">
-<D:response xmlns:lp1="DAV:" xmlns:lp2="http://subversion.tigris.org/xmlns/dav/">
-<D:href>/</D:href>
-<D:propstat>
-<D:prop>
-<lp1:version-controlled-configuration><D:href>/!svn/vcc/default</D:href></lp1:version-controlled-configuration>
-<lp1:resourcetype><D:collection/></lp1:resourcetype>
-<lp2:baseline-relative-path/>
-<lp2:repository-uuid></lp2:repository-uuid>
-</D:prop>
-<D:status>HTTP/1.1 200 OK</D:status>
-</D:propstat>
-</D:response>
-</D:multistatus>
-"""
+            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())
+    run_simple('localhost', 8080, Application(Repository()))
+
+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)
+    
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.