瀏覽代碼

Send an empty pack to clients if they requested objects, even if they
already have those objects. Fixes #781

Thanks to Martijn Pieters for the detailed bug report.

Jelmer Vernooij 4 年之前
父節點
當前提交
252bcae867
共有 5 個文件被更改,包括 61 次插入4 次删除
  1. 4 0
      NEWS
  2. 9 3
      dulwich/server.py
  3. 16 0
      dulwich/tests/compat/test_client.py
  4. 1 1
      dulwich/tests/compat/test_server.py
  5. 31 0
      dulwich/tests/test_server.py

+ 4 - 0
NEWS

@@ -3,6 +3,10 @@
  * Print a clearer exception when setup.py is executed on Python < 3.5.
  * Print a clearer exception when setup.py is executed on Python < 3.5.
    (Jelmer Vernooij, #783)
    (Jelmer Vernooij, #783)
 
 
+ * Send an empty pack to clients if they requested objects, even if they
+   already have those objects. Thanks to Martijn Pieters for
+   the detailed bug report. (Jelmer Vernooij, #781)
+
 0.20.3	2020-06-14
 0.20.3	2020-06-14
 
 
  * Add support for remembering remote refs after push/pull.
  * Add support for remembering remote refs after push/pull.

+ 9 - 3
dulwich/server.py

@@ -355,8 +355,14 @@ class UploadPackHandler(PackHandler):
         graph_walker = _ProtocolGraphWalker(
         graph_walker = _ProtocolGraphWalker(
                 self, self.repo.object_store, self.repo.get_peeled,
                 self, self.repo.object_store, self.repo.get_peeled,
                 self.repo.refs.get_symrefs)
                 self.repo.refs.get_symrefs)
+        wants = []
+
+        def wants_wrapper(refs):
+            wants.extend(graph_walker.determine_wants(refs))
+            return wants
+
         objects_iter = self.repo.fetch_objects(
         objects_iter = self.repo.fetch_objects(
-            graph_walker.determine_wants, graph_walker, self.progress,
+            wants_wrapper, graph_walker, self.progress,
             get_tagged=self.get_tagged)
             get_tagged=self.get_tagged)
 
 
         # Note the fact that client is only processing responses related
         # Note the fact that client is only processing responses related
@@ -370,7 +376,7 @@ class UploadPackHandler(PackHandler):
         # with a graph walker with an implementation that talks over the
         # with a graph walker with an implementation that talks over the
         # wire (which is this instance of this class) this will actually
         # wire (which is this instance of this class) this will actually
         # iterate through everything and write things out to the wire.
         # iterate through everything and write things out to the wire.
-        if len(objects_iter) == 0:
+        if len(wants) == 0:
             return
             return
 
 
         # The provided haves are processed, and it is safe to send side-
         # The provided haves are processed, and it is safe to send side-
@@ -548,7 +554,7 @@ class _ProtocolGraphWalker(object):
         """Determine the wants for a set of heads.
         """Determine the wants for a set of heads.
 
 
         The given heads are advertised to the client, who then specifies which
         The given heads are advertised to the client, who then specifies which
-        refs he wants using 'want' lines. This portion of the protocol is the
+        refs they want using 'want' lines. This portion of the protocol is the
         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.
 
 

+ 16 - 0
dulwich/tests/compat/test_client.py

@@ -262,6 +262,22 @@ class DulwichClientTestBase(object):
                 dest.refs.set_if_equals(r[0], None, r[1])
                 dest.refs.set_if_equals(r[0], None, r[1])
             self.assertDestEqualsSrc()
             self.assertDestEqualsSrc()
 
 
+    def test_fetch_empty_pack(self):
+        c = self._client()
+        with repo.Repo(os.path.join(self.gitroot, 'dest')) as dest:
+            result = c.fetch(self._build_path('/server_new.export'), dest)
+            for r in result.refs.items():
+                dest.refs.set_if_equals(r[0], None, r[1])
+            self.assertDestEqualsSrc()
+            def dw(refs):
+                return list(refs.values())
+            result = c.fetch(
+                self._build_path('/server_new.export'), dest,
+                determine_wants=dw)
+            for r in result.refs.items():
+                dest.refs.set_if_equals(r[0], None, r[1])
+            self.assertDestEqualsSrc()
+
     def test_incremental_fetch_pack(self):
     def test_incremental_fetch_pack(self):
         self.test_fetch_pack()
         self.test_fetch_pack()
         dest, dummy = self.disable_ff_and_make_dummy_commit()
         dest, dummy = self.disable_ff_and_make_dummy_commit()

+ 1 - 1
dulwich/tests/compat/test_server.py

@@ -85,7 +85,7 @@ class GitServerSideBand64kTestCase(GitServerTestCase):
 
 
     def setUp(self):
     def setUp(self):
         super(GitServerSideBand64kTestCase, self).setUp()
         super(GitServerSideBand64kTestCase, self).setUp()
-        # side-band-64k is broken in the widows client.
+        # side-band-64k is broken in the windows client.
         # https://github.com/msysgit/git/issues/101
         # https://github.com/msysgit/git/issues/101
         # Fix has landed for the 1.9.3 release.
         # Fix has landed for the 1.9.3 release.
         if os.name == 'nt':
         if os.name == 'nt':

+ 31 - 0
dulwich/tests/test_server.py

@@ -33,6 +33,7 @@ from dulwich.errors import (
     UnexpectedCommandError,
     UnexpectedCommandError,
     HangupException,
     HangupException,
     )
     )
+from dulwich.objects import Tree
 from dulwich.object_store import (
 from dulwich.object_store import (
     MemoryObjectStore,
     MemoryObjectStore,
     )
     )
@@ -215,6 +216,35 @@ class UploadPackHandlerTestCase(TestCase):
         self._handler.set_client_capabilities(caps)
         self._handler.set_client_capabilities(caps)
         self.assertEqual({}, self._handler.get_tagged(refs, repo=self._repo))
         self.assertEqual({}, self._handler.get_tagged(refs, repo=self._repo))
 
 
+    def test_nothing_to_do_but_wants(self):
+        # Just the fact that the client claims to want an object is enough
+        # for sending a pack. Even if there turns out to be nothing.
+        refs = {b'refs/tags/tag1': ONE}
+        tree = Tree()
+        self._repo.object_store.add_object(tree)
+        self._repo.object_store.add_object(make_commit(id=ONE, tree=tree))
+        self._repo.refs._update(refs)
+        self._handler.proto.set_output(
+            [b'want ' + ONE + b' side-band-64k thin-pack ofs-delta',
+             None, b'have ' + ONE, b'done', None])
+        self._handler.handle()
+        # The server should always send a pack, even if it's empty.
+        self.assertTrue(
+            self._handler.proto.get_received_line(1).startswith(b'PACK'))
+
+    def test_nothing_to_do_no_wants(self):
+        # Don't send a pack if the client didn't ask for anything.
+        refs = {b'refs/tags/tag1': ONE}
+        tree = Tree()
+        self._repo.object_store.add_object(tree)
+        self._repo.object_store.add_object(make_commit(id=ONE, tree=tree))
+        self._repo.refs._update(refs)
+        self._handler.proto.set_output([None])
+        self._handler.handle()
+        # The server should not send a pack, since the client didn't ask for
+        # anything.
+        self.assertEqual([], self._handler.proto._received[1])
+
 
 
 class FindShallowTests(TestCase):
 class FindShallowTests(TestCase):
 
 
@@ -320,6 +350,7 @@ class ReceivePackHandlerTestCase(TestCase):
 
 
 
 
 class ProtocolGraphWalkerEmptyTestCase(TestCase):
 class ProtocolGraphWalkerEmptyTestCase(TestCase):
+
     def setUp(self):
     def setUp(self):
         super(ProtocolGraphWalkerEmptyTestCase, self).setUp()
         super(ProtocolGraphWalkerEmptyTestCase, self).setUp()
         self._repo = MemoryRepo.init_bare([], {})
         self._repo = MemoryRepo.init_bare([], {})