Browse Source

Merge John.

Jelmer Vernooij 16 years ago
parent
commit
7483f5db10
7 changed files with 313 additions and 227 deletions
  1. 1 108
      bin/dul-daemon
  2. 34 0
      bin/dul-receive-pack
  3. 34 0
      bin/dul-upload-pack
  4. 34 0
      docs/protocol.txt
  5. 18 50
      dulwich/client.py
  6. 71 0
      dulwich/protocol.py
  7. 121 69
      dulwich/server.py

+ 1 - 108
bin/dul-daemon

@@ -17,114 +17,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 # MA  02110-1301, USA.
 # MA  02110-1301, USA.
 
 
-import os, sys, tempfile, struct
+from dulwich.server import GitBackend, TCPGitServer
-from dulwich.server import Backend, TCPGitServer
-from dulwich.repo import Repo
-from dulwich.pack import PackData, Pack, write_pack_data
-
-import sha
-
-class PackWriteWrapper(object):
-
-    def __init__(self, write):
-        self.writefn = write
-        self.sha = sha.sha()
-
-    def write(self, blob):
-        self.sha.update(blob)
-        self.writefn(blob)
-
-    def tell(self):
-        pass
-
-    @property
-    def digest(self):
-        return self.sha.digest()
-
-class GitBackend(Backend):
-
-    def __init__(self, gitdir=None):
-        self.gitdir = gitdir
-
-        if not self.gitdir:
-            self.gitdir = tempfile.mkdtemp()
-            Repo.create(self.gitdir)
-
-        self.repo = Repo(self.gitdir)
-
-    def get_refs(self):
-        refs = []
-        if self.repo.head():
-            refs.append(('HEAD', self.repo.head()))
-        for ref, sha in self.repo.heads().items():
-            refs.append(('refs/heads/'+ref,sha))
-        return refs
-
-    def has_revision(self, sha):
-        return self.repo.get_object(sha) != None
-
-    def apply_pack(self, refs, read):
-        # store the incoming pack in the repository
-        fd, name = tempfile.mkstemp(suffix='.pack', prefix='', dir=self.repo.pack_dir())
-        os.write(fd, read())
-        os.close(fd)
-
-        # strip '.pack' off our filename
-        basename = name[:-5]
-
-        # generate an index for it
-        pd = PackData(name)
-        pd.create_index_v2(basename+".idx")
-
-        for oldsha, sha, ref in refs:
-            if ref == "0" * 40:
-                self.repo.remove_ref(ref)
-            else:
-                self.repo.set_ref(ref, sha)
-
-        print "pack applied"
-
-    def generate_pack(self, want, have, write, progress):
-        progress("dul-daemon says what\n")
-
-        sha_queue = []
-
-        commits_to_send = want[:]
-        for sha in commits_to_send:
-            if sha in sha_queue:
-                continue
-
-            sha_queue.append((1,sha))
-
-            c = self.repo.commit(sha)
-            for p in c.parents():
-                if not p in commits_to_send:
-                    commits_to_send.append(p)
-
-            def parse_tree(tree, sha_queue):
-                for mode, name, x in tree.entries():
-                    if not x in sha_queue:
-                        try:
-                            t = self.repo.get_tree(x)
-                            sha_queue.append((2, x))
-                            parse_tree(t, sha_queue)
-                        except:
-                            sha_queue.append((3, x))
-
-            treesha = c.tree()
-            if treesha not in sha_queue:
-                sha_queue.append((2, treesha))
-                t = self.repo.get_tree(treesha)
-                parse_tree(t, sha_queue)
-
-            progress("counting objects: %d\r" % len(sha_queue))
-
-        progress("counting objects: %d, done.\n" % len(sha_queue))
-
-        write_pack_data(write, (self.repo.get_object(sha).as_raw_string() for sha in sha_queue))
-
-        progress("how was that, then?\n")
-
 
 
 if __name__ == "__main__":
 if __name__ == "__main__":
     gitdir = None
     gitdir = None

+ 34 - 0
bin/dul-receive-pack

@@ -0,0 +1,34 @@
+#!/usr/bin/python
+# dul-receive-pack - git-receive-pack in python
+# Copyright (C) 2008 John Carr <john.carr@unrouted.co.uk>
+# 
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; version 2
+# of the License.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA  02110-1301, USA.
+
+import sys
+from dulwich.server import GitBackend, ReceivePackHandler
+
+def send_fn(data):
+    sys.stdout.write(data)
+    sys.stdout.flush()
+
+if __name__ == "__main__":
+    gitdir = None
+    if len(sys.argv) > 1:
+        gitdir = sys.argv[1]
+
+    backend = GitBackend(gitdir)
+    handler = ReceivePackHandler(backend, sys.stdin.read, send_fn)
+    handler.handle()

+ 34 - 0
bin/dul-upload-pack

@@ -0,0 +1,34 @@
+#!/usr/bin/python
+# dul-upload-pack - git-upload-pack in python
+# Copyright (C) 2008 John Carr <john.carr@unrouted.co.uk>
+# 
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; version 2
+# of the License.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA  02110-1301, USA.
+
+import sys
+from dulwich.server import GitBackend, UploadPackHandler
+
+def send_fn(data):
+    sys.stdout.write(data)
+    sys.stdout.flush()
+
+if __name__ == "__main__":
+    gitdir = None
+    if len(sys.argv) > 1:
+        gitdir = sys.argv[1]
+
+    backend = GitBackend(gitdir)
+    handler = UploadPackHandler(backend, sys.stdin.read, send_fn)
+    handler.handle()

+ 34 - 0
docs/protocol.txt

@@ -0,0 +1,34 @@
+= Git Server Protocol =
+
+== Transport ==
+The Git protocol operates over pipes or TCP/IP. When a client connects over TCP/IP, it sends a header that tells the server which program to run and what parameters to use. When invoked over SSH, git will run a program with the parameters as command line arguments.
+
+== Protocols ==
+
+=== Basics ====
+
+Git communicates with a server by piping data between a local program and a remote program.
+
+A common way of sending a unit of information is a pkt_line. This is a 4 byte size as human encoded hex (i.e. totally underusing the 4 bytes...) that tells you the size of the payload, followed by the payload. The size includes the 4 byes used by the size itself.
+
+{{{
+0009ABCD\n
+}}}
+
+Git can also multiplex data using the sideband. As well as 4 bytes size, there would be a 1 byte channel number. This is in binary, so 1 will be \x01.
+
+Typically Git will piggyback a list of capabilities on the first pkt_line it sends. It will also look for capabilities in the first pkt_like it receives. Git will degrade as much as possible when encountering a server or client with differing capabilities.
+
+==== git-upload-pack ===
+
+git-upload pack is used by git-ls-remote, git-clone, git-fetch and git-pull. And i'm sure others. Typically a client will connect a local git-fetch-pack to a remote git-upload-pack.
+
+Capabilities for this protocol include multi_ack, thin-pack, ofs-delta, sideband and sideband-64k A thin pack can reference objects not in the current pack.
+
+The server tells the client what refs it has. The client states which of those SHA1's it would like. It then starts to report which SHA1's it has. The server ACKs these allowing the client to work out when to stop sending SHA1's. This saves a lot of transfer because the client can make decisions like "well if it has this SHA, then it has all its parents so i dont need to care about those". When the client stops sending shas, the server can work out an optimal pack and then send it to the client.
+
+==== git-receive-pack ===
+
+git-receive-pack is used by git push. Typically a client connects a local git-send-pack to a remote git-receive-pack.
+
+Capabilities include report-status and delete-ref.

+ 18 - 50
dulwich/client.py

@@ -18,7 +18,7 @@
 
 
 import select
 import select
 import socket
 import socket
-
+from dulwich.protocol import Protocol
 
 
 def extract_capabilities(text):
 def extract_capabilities(text):
     if not "\0" in text:
     if not "\0" in text:
@@ -57,44 +57,12 @@ class GitClient(object):
     """
     """
 
 
     def __init__(self, fileno, read, write, host):
     def __init__(self, fileno, read, write, host):
-        self.read = read
+        self.proto = Protocol(read, write)
-        self.write = write
         self.fileno = fileno
         self.fileno = fileno
         self.host = host
         self.host = host
 
 
-    def read_pkt_line(self):
-        """
-        Reads a 'pkt line' from the remote git process
-
-        :return: The next string from the stream
-        """
-        sizestr = self.read(4)
-        if sizestr == "":
-            raise RuntimeError("Socket broken")
-        size = int(sizestr, 16)
-        if size == 0:
-            return None
-        return self.read(size-4)
-
-    def read_pkt_seq(self):
-        pkt = self.read_pkt_line()
-        while pkt:
-            yield pkt
-            pkt = self.read_pkt_line()
-
-    def write_pkt_line(self, line):
-        """
-        Sends a 'pkt line' to the remote git process
-
-        :param line: A string containing the data to send
-        """
-        if line is None:
-            self.write("0000")
-        else:
-            self.write("%04x%s" % (len(line)+4, line))
-
     def send_cmd(self, name, *args):
     def send_cmd(self, name, *args):
-        self.write_pkt_line("%s %s" % (name, "".join(["%s\0" % a for a in args])))
+        self.proto.write_pkt_line("%s %s" % (name, "".join(["%s\0" % a for a in args])))
 
 
     def capabilities(self):
     def capabilities(self):
         return "multi_ack side-band-64k thin-pack ofs-delta"
         return "multi_ack side-band-64k thin-pack ofs-delta"
@@ -103,7 +71,7 @@ class GitClient(object):
         server_capabilities = None
         server_capabilities = None
         refs = {}
         refs = {}
         # Receive refs from server
         # Receive refs from server
-        for pkt in self.read_pkt_seq():
+        for pkt in self.proto.read_pkt_seq():
             (sha, ref) = pkt.rstrip("\n").split(" ", 1)
             (sha, ref) = pkt.rstrip("\n").split(" ", 1)
             if server_capabilities is None:
             if server_capabilities is None:
                 (ref, server_capabilities) = extract_capabilities(ref)
                 (ref, server_capabilities) = extract_capabilities(ref)
@@ -116,12 +84,12 @@ class GitClient(object):
         refs, server_capabilities = self.read_refs()
         refs, server_capabilities = self.read_refs()
         changed_refs = [] # FIXME
         changed_refs = [] # FIXME
         if not changed_refs:
         if not changed_refs:
-            self.write_pkt_line(None)
+            self.proto.write_pkt_line(None)
             return
             return
-        self.write_pkt_line("%s %s %s\0%s" % (changed_refs[0][0], changed_refs[0][1], changed_refs[0][2], self.capabilities()))
+        self.proto.write_pkt_line("%s %s %s\0%s" % (changed_refs[0][0], changed_refs[0][1], changed_refs[0][2], self.capabilities()))
         for changed_ref in changed_refs[:]:
         for changed_ref in changed_refs[:]:
-            self.write_pkt_line("%s %s %s" % changed_refs)
+            self.proto.write_pkt_line("%s %s %s" % changed_refs)
-        self.write_pkt_line(None)
+        self.proto.write_pkt_line(None)
         # FIXME: Send pack
         # FIXME: Send pack
 
 
     def fetch_pack(self, path, determine_wants, graph_walker, pack_data, progress):
     def fetch_pack(self, path, determine_wants, graph_walker, pack_data, progress):
@@ -138,32 +106,32 @@ class GitClient(object):
        
        
         wants = determine_wants(refs)
         wants = determine_wants(refs)
         if not wants:
         if not wants:
-            self.write_pkt_line(None)
+            self.proto.write_pkt_line(None)
             return
             return
-        self.write_pkt_line("want %s %s\n" % (wants[0], self.capabilities()))
+        self.proto.write_pkt_line("want %s %s\n" % (wants[0], self.capabilities()))
         for want in wants[1:]:
         for want in wants[1:]:
-            self.write_pkt_line("want %s\n" % want)
+            self.proto.write_pkt_line("want %s\n" % want)
-        self.write_pkt_line(None)
+        self.proto.write_pkt_line(None)
         have = graph_walker.next()
         have = graph_walker.next()
         while have:
         while have:
-            self.write_pkt_line("have %s\n" % have)
+            self.proto.write_pkt_line("have %s\n" % have)
             if len(select.select([self.fileno], [], [], 0)[0]) > 0:
             if len(select.select([self.fileno], [], [], 0)[0]) > 0:
-                pkt = self.read_pkt_line()
+                pkt = self.proto.read_pkt_line()
                 parts = pkt.rstrip("\n").split(" ")
                 parts = pkt.rstrip("\n").split(" ")
                 if parts[0] == "ACK":
                 if parts[0] == "ACK":
                     graph_walker.ack(parts[1])
                     graph_walker.ack(parts[1])
                     assert parts[2] == "continue"
                     assert parts[2] == "continue"
             have = graph_walker.next()
             have = graph_walker.next()
-        self.write_pkt_line("done\n")
+        self.proto.write_pkt_line("done\n")
-        pkt = self.read_pkt_line()
+        pkt = self.proto.read_pkt_line()
         while pkt:
         while pkt:
             parts = pkt.rstrip("\n").split(" ")
             parts = pkt.rstrip("\n").split(" ")
             if parts[0] == "ACK":
             if parts[0] == "ACK":
                 graph_walker.ack(pkt.split(" ")[1])
                 graph_walker.ack(pkt.split(" ")[1])
             if len(parts) < 3 or parts[2] != "continue":
             if len(parts) < 3 or parts[2] != "continue":
                 break
                 break
-            pkt = self.read_pkt_line()
+            pkt = self.proto.read_pkt_line()
-        for pkt in self.read_pkt_seq():
+        for pkt in self.proto.read_pkt_seq():
             channel = ord(pkt[0])
             channel = ord(pkt[0])
             pkt = pkt[1:]
             pkt = pkt[1:]
             if channel == 1:
             if channel == 1:

+ 71 - 0
dulwich/protocol.py

@@ -0,0 +1,71 @@
+# protocol.py -- Shared parts of the git protocols
+# Copryight (C) 2008 John Carr <john.carr@unrouted.co.uk>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; version 2
+# of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA  02110-1301, USA.
+
+
+class Protocol(object):
+
+    def __init__(self, read, write):
+        self.read = read
+        self.write = write
+
+    def read_pkt_line(self):
+        """
+        Reads a 'pkt line' from the remote git process
+
+        :return: The next string from the stream
+        """
+        sizestr = self.read(4)
+        if not sizestr:
+            return None
+        size = int(sizestr, 16)
+        if size == 0:
+            return None
+        return self.read(size-4)
+
+    def read_pkt_seq(self):
+        pkt = self.read_pkt_line()
+        while pkt:
+            yield pkt
+            pkt = self.read_pkt_line()
+
+    def write_pkt_line(self, line):
+        """
+        Sends a 'pkt line' to the remote git process
+
+        :param line: A string containing the data to send
+        """
+        if line is None:
+            self.write("0000")
+        else:
+            self.write("%04x%s" % (len(line)+4, line))
+
+    def write_sideband(self, channel, blob):
+        """
+        Write data to the sideband (a git multiplexing method)
+
+        :param channel: int specifying which channel to write to
+        :param blob: a blob of data (as a string) to send on this channel
+        """
+        # a pktline can be a max of 65535. a sideband line can therefore be
+        # 65535-5 = 65530
+        # WTF: Why have the len in ASCII, but the channel in binary.
+        while blob:
+            self.write_pkt_line("%s%s" % (chr(channel), blob[:65530]))
+            blob = blob[65530:]
+
+

+ 121 - 69
dulwich/server.py

@@ -17,6 +17,7 @@
 # MA  02110-1301, USA.
 # MA  02110-1301, USA.
 
 
 import SocketServer
 import SocketServer
+from dulwich.protocol import Protocol
 
 
 class Backend(object):
 class Backend(object):
 
 
@@ -55,53 +56,104 @@ class Backend(object):
         """
         """
         raise NotImplementedError
         raise NotImplementedError
 
 
+from dulwich.repo import Repo
+from dulwich.pack import PackData, Pack
+import sha, tempfile, os
+from dulwich.pack import write_pack_data
 
 
-class Handler(object):
+class GitBackend(Backend):
 
 
-    def __init__(self, backend, read, write):
+    def __init__(self, gitdir=None):
-        self.backend = backend
+        self.gitdir = gitdir
-        self.read = read
-        self.write = write
 
 
-    def read_pkt_line(self):
+        if not self.gitdir:
-        """
+            self.gitdir = tempfile.mkdtemp()
-        Reads a 'pkt line' from the remote git process
+            Repo.create(self.gitdir)
 
 
-        :return: The next string from the stream
+        self.repo = Repo(self.gitdir)
-        """
-        sizestr = self.read(4)
-        if not sizestr:
-            return None
-        size = int(sizestr, 16)
-        if size == 0:
-            return None
-        return self.read(size-4)
-
-    def write_pkt_line(self, line):
-        """
-        Sends a 'pkt line' to the remote git process
 
 
-        :param line: A string containing the data to send
+    def get_refs(self):
-        """
+        refs = []
-        self.write("%04x%s" % (len(line)+4, line))
+        if self.repo.head():
+            refs.append(('HEAD', self.repo.head()))
+        for ref, sha in self.repo.heads().items():
+            refs.append(('refs/heads/'+ref,sha))
+        return refs
 
 
-    def write_sideband(self, channel, blob):
+    def has_revision(self, sha):
-        """
+        return self.repo.get_object(sha) != None
-        Write data to the sideband (a git multiplexing method)
 
 
-        :param channel: int specifying which channel to write to
+    def apply_pack(self, refs, read):
-        :param blob: a blob of data (as a string) to send on this channel
+        # store the incoming pack in the repository
-        """
+        fd, name = tempfile.mkstemp(suffix='.pack', prefix='', dir=self.repo.pack_dir())
-        # a pktline can be a max of 65535. a sideband line can therefore be
+        os.write(fd, read())
-        # 65535-5 = 65530
+        os.close(fd)
-        # WTF: Why have the len in ASCII, but the channel in binary.
+
-        while blob:
+        # strip '.pack' off our filename
-            self.write_pkt_line("%s%s" % (chr(channel), blob[:65530]))
+        basename = name[:-5]
-            blob = blob[65530:]
+
+        # generate an index for it
+        pd = PackData(name)
+        pd.create_index_v2(basename+".idx")
+
+        for oldsha, sha, ref in refs:
+            if ref == "0" * 40:
+                self.repo.remove_ref(ref)
+            else:
+                self.repo.set_ref(ref, sha)
+
+        print "pack applied"
+
+    def generate_pack(self, want, have, write, progress):
+        progress("dul-daemon says what\n")
+
+        sha_queue = []
+
+        commits_to_send = want[:]
+        for sha in commits_to_send:
+            if sha in sha_queue:
+                continue
+
+            sha_queue.append((1,sha))
+
+            c = self.repo.commit(sha)
+            for p in c.parents():
+                if not p in commits_to_send:
+                    commits_to_send.append(p)
+
+            def parse_tree(tree, sha_queue):
+                for mode, name, x in tree.entries():
+                    if not x in sha_queue:
+                        try:
+                            t = self.repo.get_tree(x)
+                            sha_queue.append((2, x))
+                            parse_tree(t, sha_queue)
+                        except:
+                            sha_queue.append((3, x))
+
+            treesha = c.tree()
+            if treesha not in sha_queue:
+                sha_queue.append((2, treesha))
+                t = self.repo.get_tree(treesha)
+                parse_tree(t, sha_queue)
+
+            progress("counting objects: %d\r" % len(sha_queue))
+
+        progress("counting objects: %d, done.\n" % len(sha_queue))
+
+        write_pack_data(write, (self.repo.get_object(sha).as_raw_string() for sha in sha_queue))
+
+        progress("how was that, then?\n")
+
+
+class Handler(object):
+
+    def __init__(self, backend, read, write):
+        self.backend = backend
+        self.proto = Protocol(read, write)
 
 
     def capabilities(self):
     def capabilities(self):
-        # FIXME: Capabilities are different for pushing...
+        return " ".join(self.default_capabilities())
-        return "multi_ack side-band-64k thin-pack ofs-delta"
 
 
     def handshake(self, blob):
     def handshake(self, blob):
         """
         """
@@ -118,30 +170,27 @@ class Handler(object):
 
 
         return blob
         return blob
 
 
-    def handle(self):
-        """
-        Deal with the request
-        """
-        raise NotImplementedError
-
 
 
 class UploadPackHandler(Handler):
 class UploadPackHandler(Handler):
 
 
+    def default_capabilities(self):
+        return ("multi_ack", "side-band-64k", "thin-pack", "ofs-delta")
+
     def handle(self):
     def handle(self):
         refs = self.backend.get_refs()
         refs = self.backend.get_refs()
 
 
         if refs:
         if refs:
-            self.write_pkt_line("%s %s\x00%s\n" % (refs[0][1], refs[0][0], self.capabilities()))
+            self.proto.write_pkt_line("%s %s\x00%s\n" % (refs[0][1], refs[0][0], self.capabilities()))
             for i in range(1, len(refs)):
             for i in range(1, len(refs)):
                 ref = refs[i]
                 ref = refs[i]
-                self.write_pkt_line("%s %s\n" % (ref[1], ref[0]))
+                self.proto.write_pkt_line("%s %s\n" % (ref[1], ref[0]))
 
 
-        # i'm done...
+        # i'm done..
-        self.write("0000")
+        self.proto.write("0000")
 
 
         # Now client will either send "0000", meaning that it doesnt want to pull.
         # Now client will either send "0000", meaning that it doesnt want to pull.
         # or it will start sending want want want commands
         # or it will start sending want want want commands
-        want = self.read_pkt_line()
+        want = self.proto.read_pkt_line()
         if want == None:
         if want == None:
             return
             return
 
 
@@ -154,54 +203,57 @@ class UploadPackHandler(Handler):
             # FIXME: This check probably isnt needed?
             # FIXME: This check probably isnt needed?
             if self.backend.has_revision(want_rev):
             if self.backend.has_revision(want_rev):
                want_revs.append(want_rev)
                want_revs.append(want_rev)
-            want = self.read_pkt_line()
+            want = self.proto.read_pkt_line()
         
         
         # Client will now tell us which commits it already has - if we have them we ACK them
         # Client will now tell us which commits it already has - if we have them we ACK them
         # this allows client to stop looking at that commits parents (main reason why git pull is fast)
         # this allows client to stop looking at that commits parents (main reason why git pull is fast)
         last_sha = None
         last_sha = None
         have_revs = []
         have_revs = []
-        have = self.read_pkt_line()
+        have = self.proto.read_pkt_line()
         while have and have[:4] == 'have':
         while have and have[:4] == 'have':
             have_ref = have[6:46]
             have_ref = have[6:46]
-            if self.backend.has_revision(hav_rev):
+            if self.backend.has_revision(have_ref):
-                self.write_pkt_line("ACK %s continue\n" % sha)
+                self.proto.write_pkt_line("ACK %s continue\n" % have_ref)
-                last_sha = sha
+                last_sha = have_ref
-                have_revs.append(rev_id)
+                have_revs.append(have_ref)
-            have = self.read_pkt_line()
+            have = self.proto.read_pkt_line()
 
 
         # At some point client will stop sending commits and will tell us it is done
         # At some point client will stop sending commits and will tell us it is done
         assert(have[:4] == "done")
         assert(have[:4] == "done")
 
 
         # Oddness: Git seems to resend the last ACK, without the "continue" statement
         # Oddness: Git seems to resend the last ACK, without the "continue" statement
         if last_sha:
         if last_sha:
-            self.write_pkt_line("ACK %s\n" % last_sha)
+            self.proto.write_pkt_line("ACK %s\n" % last_sha)
 
 
         # The exchange finishes with a NAK
         # The exchange finishes with a NAK
-        self.write_pkt_line("NAK\n")
+        self.proto.write_pkt_line("NAK\n")
       
       
-        self.backend.generate_pack(want_revs, have_revs, lambda x: self.write_sideband(1, x), lambda x: self.write_sideband(2, x))
+        self.backend.generate_pack(want_revs, have_revs, lambda x: self.proto.write_sideband(1, x), lambda x: self.proto.write_sideband(2, x))
 
 
         # we are done
         # we are done
-        self.write("0000")
+        self.proto.write("0000")
 
 
 
 
 class ReceivePackHandler(Handler):
 class ReceivePackHandler(Handler):
 
 
+    def default_capabilities(self):
+        return ("report-status", "delete-refs")
+
     def handle(self):
     def handle(self):
         refs = self.backend.get_refs()
         refs = self.backend.get_refs()
 
 
         if refs:
         if refs:
-            self.write_pkt_line("%s %s\x00%s\n" % (refs[0][1], refs[0][0], self.capabilities()))
+            self.proto.write_pkt_line("%s %s\x00%s\n" % (refs[0][1], refs[0][0], self.capabilities()))
             for i in range(1, len(refs)):
             for i in range(1, len(refs)):
                 ref = refs[i]
                 ref = refs[i]
-                self.write_pkt_line("%s %s\n" % (ref[1], ref[0]))
+                self.proto.write_pkt_line("%s %s\n" % (ref[1], ref[0]))
         else:
         else:
-            self.write_pkt_line("0000000000000000000000000000000000000000 capabilities^{} %s" % self.capabilities())
+            self.proto.write_pkt_line("0000000000000000000000000000000000000000 capabilities^{} %s" % self.capabilities())
 
 
-        self.write("0000")
+        self.proto.write("0000")
 
 
         client_refs = []
         client_refs = []
-        ref = self.read_pkt_line()
+        ref = self.proto.read_pkt_line()
 
 
         # if ref is none then client doesnt want to send us anything..
         # if ref is none then client doesnt want to send us anything..
         if ref is None:
         if ref is None:
@@ -212,10 +264,10 @@ class ReceivePackHandler(Handler):
         # client will now send us a list of (oldsha, newsha, ref)
         # client will now send us a list of (oldsha, newsha, ref)
         while ref:
         while ref:
             client_refs.append(ref.split())
             client_refs.append(ref.split())
-            ref = self.read_pkt_line()
+            ref = self.proto.read_pkt_line()
 
 
         # backend can now deal with this refs and read a pack using self.read
         # backend can now deal with this refs and read a pack using self.read
-        self.backend.apply_pack(client_refs, self.read)
+        self.backend.apply_pack(client_refs, self.proto.read)
 
 
         # when we have read all the pack from the client, it assumes everything worked OK
         # when we have read all the pack from the client, it assumes everything worked OK
         # there is NO ack from the server before it reports victory.
         # there is NO ack from the server before it reports victory.
@@ -231,7 +283,7 @@ class TCPGitRequestHandler(SocketServer.StreamRequestHandler, Handler):
         #so we can't call this in a sane place??
         #so we can't call this in a sane place??
         Handler.__init__(self, self.server.backend, self.rfile.read, self.wfile.write)
         Handler.__init__(self, self.server.backend, self.rfile.read, self.wfile.write)
 
 
-        request = self.read_pkt_line()
+        request = self.proto.read_pkt_line()
 
 
         # up until the space is the command to run, everything after is parameters
         # up until the space is the command to run, everything after is parameters
         splice_point = request.find(' ')
         splice_point = request.find(' ')
@@ -248,7 +300,7 @@ class TCPGitRequestHandler(SocketServer.StreamRequestHandler, Handler):
         else:
         else:
             return
             return
 
 
-        h = cls(self.backend, self.read, self.write)
+        h = cls(self.backend, self.proto.read, self.proto.write)
         h.handle()
         h.handle()