Browse Source

Add shallow support to ProtocolGraphWalker

Dave Borowitz 15 years ago
parent
commit
392d3280de
1 changed files with 49 additions and 7 deletions
  1. 49 7
      dulwich/server.py

+ 49 - 7
dulwich/server.py

@@ -227,7 +227,7 @@ class UploadPackHandler(Handler):
     @classmethod
     @classmethod
     def capabilities(cls):
     def capabilities(cls):
         return ("multi_ack_detailed", "multi_ack", "side-band-64k", "thin-pack",
         return ("multi_ack_detailed", "multi_ack", "side-band-64k", "thin-pack",
-                "ofs-delta", "no-progress", "include-tag")
+                "ofs-delta", "no-progress", "include-tag", "shallow")
 
 
     @classmethod
     @classmethod
     def required_capabilities(cls):
     def required_capabilities(cls):
@@ -316,9 +316,12 @@ def _split_proto_line(line, allowed):
     try:
     try:
         if len(fields) == 1 and command in ('done', None):
         if len(fields) == 1 and command in ('done', None):
             return (command, None)
             return (command, None)
-        elif len(fields) == 2 and command in ('want', 'have'):
+        elif len(fields) == 2:
-            hex_to_sha(fields[1])
+            if command in ('want', 'have', 'shallow', 'unshallow'):
-            return tuple(fields)
+                hex_to_sha(fields[1])
+                return tuple(fields)
+            elif command == 'deepen':
+                return command, int(fields[1])
     except (TypeError, AssertionError), e:
     except (TypeError, AssertionError), e:
         raise GitProtocolError(e)
         raise GitProtocolError(e)
     raise GitProtocolError('Received invalid line from client: %s' % line)
     raise GitProtocolError('Received invalid line from client: %s' % line)
@@ -383,6 +386,9 @@ class ProtocolGraphWalker(object):
         self.http_req = handler.http_req
         self.http_req = handler.http_req
         self.advertise_refs = handler.advertise_refs
         self.advertise_refs = handler.advertise_refs
         self._wants = []
         self._wants = []
+        self.shallow = set()
+        self.client_shallow = set()
+        self.unshallow = set()
         self._cached = False
         self._cached = False
         self._cache = []
         self._cache = []
         self._cache_index = 0
         self._cache_index = 0
@@ -396,6 +402,12 @@ class ProtocolGraphWalker(object):
         same regardless of ack type, and in fact is used to set the ack type of
         same regardless of ack type, and in fact is used to set the ack type of
         the ProtocolGraphWalker.
         the ProtocolGraphWalker.
 
 
+        If the client has the 'shallow' capability, this method also reads and
+        responds to the 'shallow' and 'deepen' lines from the client. These are
+        not part of the wants per se, but they set up necessary state for
+        walking the graph. Additionally, later code depends on this method
+        consuming everything up to the first 'have' line.
+
         :param heads: a dict of refname->SHA1 to advertise
         :param heads: a dict of refname->SHA1 to advertise
         :return: a list of SHA1s requested by the client
         :return: a list of SHA1s requested by the client
         """
         """
@@ -428,11 +440,11 @@ class ProtocolGraphWalker(object):
         line, caps = extract_want_line_capabilities(want)
         line, caps = extract_want_line_capabilities(want)
         self.handler.set_client_capabilities(caps)
         self.handler.set_client_capabilities(caps)
         self.set_ack_type(ack_type(caps))
         self.set_ack_type(ack_type(caps))
-        allowed = ('want', None)
+        allowed = ('want', 'shallow', 'deepen', None)
         command, sha = _split_proto_line(line, allowed)
         command, sha = _split_proto_line(line, allowed)
 
 
         want_revs = []
         want_revs = []
-        while command != None:
+        while command == 'want':
             if sha not in values:
             if sha not in values:
                 raise GitProtocolError(
                 raise GitProtocolError(
                   'Client wants invalid object %s' % sha)
                   'Client wants invalid object %s' % sha)
@@ -440,6 +452,9 @@ class ProtocolGraphWalker(object):
             command, sha = self.read_proto_line(allowed)
             command, sha = self.read_proto_line(allowed)
 
 
         self.set_wants(want_revs)
         self.set_wants(want_revs)
+        if command in ('shallow', 'deepen'):
+            self.unread_proto_line(command, sha)
+            self._handle_shallow_request(want_revs)
 
 
         if self.http_req and self.proto.eof():
         if self.http_req and self.proto.eof():
             # The client may close the socket at this point, expecting a
             # The client may close the socket at this point, expecting a
@@ -449,6 +464,9 @@ class ProtocolGraphWalker(object):
 
 
         return want_revs
         return want_revs
 
 
+    def unread_proto_line(self, command, value):
+        self.proto.unread_pkt_line('%s %s' % (command, value))
+
     def ack(self, have_ref):
     def ack(self, have_ref):
         return self._impl.ack(have_ref)
         return self._impl.ack(have_ref)
 
 
@@ -471,10 +489,34 @@ class ProtocolGraphWalker(object):
 
 
         :param allowed: An iterable of command names that should be allowed.
         :param allowed: An iterable of command names that should be allowed.
         :return: A tuple of (command, value); see _split_proto_line.
         :return: A tuple of (command, value); see _split_proto_line.
-        :raise GitProtocolError: If an error occurred reading the line.
+        :raise UnexpectedCommandError: If an error occurred reading the line.
         """
         """
         return _split_proto_line(self.proto.read_pkt_line(), allowed)
         return _split_proto_line(self.proto.read_pkt_line(), allowed)
 
 
+    def _handle_shallow_request(self, wants):
+        while True:
+            command, val = self.read_proto_line(('deepen', 'shallow'))
+            if command == 'deepen':
+                depth = val
+                break
+            self.client_shallow.add(val)
+        self.read_proto_line((None,))  # consume client's flush-pkt
+
+        shallow, not_shallow = _find_shallow(self.store, wants, depth)
+
+        # Update self.shallow instead of reassigning it since we passed a
+        # reference to it before this method was called.
+        self.shallow.update(shallow - not_shallow)
+        new_shallow = self.shallow - self.client_shallow
+        unshallow = self.unshallow = not_shallow & self.client_shallow
+
+        for sha in sorted(new_shallow):
+            self.proto.write_pkt_line('shallow %s' % sha)
+        for sha in sorted(unshallow):
+            self.proto.write_pkt_line('unshallow %s' % sha)
+
+        self.proto.write_pkt_line(None)
+
     def send_ack(self, sha, ack_type=''):
     def send_ack(self, sha, ack_type=''):
         if ack_type:
         if ack_type:
             ack_type = ' %s' % ack_type
             ack_type = ' %s' % ack_type