server: Correct short-circuiting operation for no-op fetches.
authorDave Borowitz <dborowitz@google.com>
Tue, 25 May 2010 18:48:09 +0000 (11:48 -0700)
committerDave Borowitz <dborowitz@google.com>
Tue, 5 Oct 2010 18:00:47 +0000 (11:00 -0700)
There are two distinct cases where determine_wants might return
"nothing":
 - The client may have finished its request, in which case it is
   expecting a 0-object pack.
 - We may be in the middle of a stateless RPC packfile negotiation
   request, in which case we should close the connection without writing
   a packfile.

This change distinguishes between these two cases by allowing
determine_wants to return either None, indicating that no pack should
be sent, or an empty iterator, indicating that an empty pack should be
sent.

Added a compat test for the no-op case that failed before.

Change-Id: Ia24f86cf8aa30d040eaf2f06a58e1d610cdeb32f

NEWS
dulwich/repo.py
dulwich/server.py
dulwich/tests/compat/server_utils.py

diff --git a/NEWS b/NEWS
index ca728621a68b33777fb1dfb6096f5895029c169a..eb51859cb9937d65d7121dc7eb2f42c7d5459244 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,9 @@
 
   * Provide strnlen() on mingw32 which doesn't have it. (Hans Kolek)
 
+  * Correct short-circuiting operation for no-op fetches in the server.
+    (Dave Borowitz)
+
  FEATURES
 
   * Use slots for core objects to save up on memory. (Jelmer Vernooij)
index 54a554684aea08b8d7c4753ff427445cb2ee0cf5..40c18bfc135535535a4d707ab074a04b26b038ad 100644 (file)
@@ -833,8 +833,10 @@ class BaseRepo(object):
         :return: iterator over objects, with __len__ implemented
         """
         wants = determine_wants(self.get_refs())
-        if not wants:
-            return []
+        if wants is None:
+            # TODO(dborowitz): find a way to short-circuit that doesn't change
+            # this interface.
+            return None
         haves = self.object_store.find_common_revisions(graph_walker)
         return self.object_store.iter_shas(
           self.object_store.find_missing_objects(haves, wants, progress,
index 7a97e64bc303a03ff9d56a497c992afbedb69e74..a7346282453b84241335178740159ff009984204 100644 (file)
@@ -264,8 +264,9 @@ class UploadPackHandler(Handler):
           graph_walker.determine_wants, graph_walker, self.progress,
           get_tagged=self.get_tagged)
 
-        # Do they want any objects?
-        if len(objects_iter) == 0:
+        # Did the process short-circuit (e.g. in a stateless RPC call)? Note
+        # that the client still expects a 0-object pack in most cases.
+        if objects_iter is None:
             return
 
         self.progress("dul-daemon says what\n")
@@ -367,7 +368,7 @@ class ProtocolGraphWalker(object):
             self.proto.write_pkt_line(None)
 
             if self.advertise_refs:
-                return []
+                return None
 
         # Now client will sending want want want commands
         want = self.proto.read_pkt_line()
@@ -388,6 +389,13 @@ class ProtocolGraphWalker(object):
             command, sha = self.read_proto_line(allowed)
 
         self.set_wants(want_revs)
+
+        if self.stateless_rpc and self.proto.eof():
+            # The client may close the socket at this point, expecting a
+            # flush-pkt from the server. We might be ready to send a packfile at
+            # this point, so we need to explicitly short-circuit in this case.
+            return None
+
         return want_revs
 
     def ack(self, have_ref):
index 0c5dce398e3eaea44c698ebe759bece1d139915d..4a9da06d03a7ba95163e6529e0d8179aa7b86fb4 100644 (file)
@@ -88,6 +88,18 @@ class ServerTests(object):
         self._old_repo.object_store._pack_cache = None
         self.assertReposEqual(self._old_repo, self._new_repo)
 
+    def test_fetch_from_dulwich_no_op(self):
+        self._old_repo = import_repo('server_old.export')
+        self._new_repo = import_repo('server_old.export')
+        self.assertReposEqual(self._old_repo, self._new_repo)
+        port = self._start_server(self._new_repo)
+
+        run_git_or_fail(['fetch', self.url(port)] + self.branch_args(),
+                        cwd=self._old_repo.path)
+        # flush the pack cache so any new packs are picked up
+        self._old_repo.object_store._pack_cache = None
+        self.assertReposEqual(self._old_repo, self._new_repo)
+
 
 class ShutdownServerMixIn:
     """Mixin that allows serve_forever to be shut down.