Browse Source

Provide symrefs in server. Fixes #485

Jelmer Vernooij 7 years ago
parent
commit
713d9e312b

+ 3 - 0
NEWS

@@ -14,6 +14,9 @@
   * ``GitClient.fetch_pack`` now returns symrefs.
     (Jelmer Vernooij, #485)
 
+  * The server now supports providing symrefs.
+    (Jelmer Vernooij, #485)
+
 0.18.2	2017-08-01
 
  TEST FIXES

+ 4 - 0
dulwich/protocol.py

@@ -115,6 +115,10 @@ def parse_capability(capability):
     return tuple(parts)
 
 
+def symref_capabilities(symrefs):
+    return [capability_symref(*k) for k in symrefs]
+
+
 COMMAND_DEEPEN = b'deepen'
 COMMAND_SHALLOW = b'shallow'
 COMMAND_UNSHALLOW = b'unshallow'

+ 20 - 12
dulwich/server.py

@@ -39,6 +39,7 @@ Currently supported capabilities:
  * report-status
  * delete-refs
  * shallow
+ * symref
 """
 
 import collections
@@ -104,6 +105,7 @@ from dulwich.protocol import (  # noqa: F401
     ack_type,
     extract_capabilities,
     extract_want_line_capabilities,
+    symref_capabilities,
     )
 from dulwich.refs import (
     ANNOTATED_TAG_SUFFIX,
@@ -230,6 +232,7 @@ class PackHandler(Handler):
 
     @classmethod
     def capability_line(cls, capabilities):
+        logger.info('Sending capabilities: %s', capabilities)
         return b"".join([b" " + c for c in capabilities])
 
     @classmethod
@@ -238,9 +241,9 @@ class PackHandler(Handler):
 
     @classmethod
     def innocuous_capabilities(cls):
-        return (CAPABILITY_INCLUDE_TAG, CAPABILITY_THIN_PACK,
+        return [CAPABILITY_INCLUDE_TAG, CAPABILITY_THIN_PACK,
                 CAPABILITY_NO_PROGRESS, CAPABILITY_OFS_DELTA,
-                capability_agent())
+                capability_agent()]
 
     @classmethod
     def required_capabilities(cls):
@@ -288,10 +291,10 @@ class UploadPackHandler(PackHandler):
 
     @classmethod
     def capabilities(cls):
-        return (CAPABILITY_MULTI_ACK_DETAILED, CAPABILITY_MULTI_ACK,
+        return [CAPABILITY_MULTI_ACK_DETAILED, CAPABILITY_MULTI_ACK,
                 CAPABILITY_SIDE_BAND_64K, CAPABILITY_THIN_PACK,
                 CAPABILITY_OFS_DELTA, CAPABILITY_NO_PROGRESS,
-                CAPABILITY_INCLUDE_TAG, CAPABILITY_SHALLOW, CAPABILITY_NO_DONE)
+                CAPABILITY_INCLUDE_TAG, CAPABILITY_SHALLOW, CAPABILITY_NO_DONE]
 
     @classmethod
     def required_capabilities(cls):
@@ -337,8 +340,9 @@ class UploadPackHandler(PackHandler):
         def write(x):
             return self.proto.write_sideband(SIDE_BAND_CHANNEL_DATA, x)
 
-        graph_walker = ProtocolGraphWalker(
-                self, self.repo.object_store, self.repo.get_peeled)
+        graph_walker = _ProtocolGraphWalker(
+                self, self.repo.object_store, self.repo.get_peeled,
+                self.repo.refs.get_symrefs)
         objects_iter = self.repo.fetch_objects(
             graph_walker.determine_wants, graph_walker, self.progress,
             get_tagged=self.get_tagged)
@@ -496,7 +500,7 @@ def _all_wants_satisfied(store, haves, wants):
     return True
 
 
-class ProtocolGraphWalker(object):
+class _ProtocolGraphWalker(object):
     """A graph walker that knows the git protocol.
 
     As a graph walker, this class implements ack(), next(), and reset(). It
@@ -509,10 +513,11 @@ class ProtocolGraphWalker(object):
     call to set_ack_type() is required to set up the implementation, before
     any calls to next() or ack() are made.
     """
-    def __init__(self, handler, object_store, get_peeled):
+    def __init__(self, handler, object_store, get_peeled, get_symrefs):
         self.handler = handler
         self.store = object_store
         self.get_peeled = get_peeled
+        self.get_symrefs = get_symrefs
         self.proto = handler.proto
         self.http_req = handler.http_req
         self.advertise_refs = handler.advertise_refs
@@ -549,7 +554,8 @@ class ProtocolGraphWalker(object):
                 if not i:
                     line += (b'\x00' +
                              self.handler.capability_line(
-                                 self.handler.capabilities()))
+                                 self.handler.capabilities() +
+                                 symref_capabilities(self.get_symrefs().items())))
                 self.proto.write_pkt_line(line + b'\n')
                 peeled_sha = self.get_peeled(ref)
                 if peeled_sha != sha:
@@ -874,9 +880,9 @@ class ReceivePackHandler(PackHandler):
 
     @classmethod
     def capabilities(cls):
-        return (CAPABILITY_REPORT_STATUS, CAPABILITY_DELETE_REFS,
+        return [CAPABILITY_REPORT_STATUS, CAPABILITY_DELETE_REFS,
                 CAPABILITY_QUIET, CAPABILITY_OFS_DELTA,
-                CAPABILITY_SIDE_BAND_64K, CAPABILITY_NO_DONE)
+                CAPABILITY_SIDE_BAND_64K, CAPABILITY_NO_DONE]
 
     def _apply_pack(self, refs):
         all_exceptions = (IOError, OSError, ChecksumMismatch, ApplyDeltaError,
@@ -956,12 +962,14 @@ class ReceivePackHandler(PackHandler):
     def handle(self):
         if self.advertise_refs or not self.http_req:
             refs = sorted(self.repo.get_refs().items())
+            symrefs = sorted(self.repo.refs.get_symrefs().items())
 
             if not refs:
                 refs = [(CAPABILITIES_REF, ZERO_SHA)]
             self.proto.write_pkt_line(
               refs[0][1] + b' ' + refs[0][0] + b'\0' +
-              self.capability_line(self.capabilities()) + b'\n')
+              self.capability_line(
+                  self.capabilities() + symref_capabilities(symrefs)) + b'\n')
             for i in range(1, len(refs)):
                 ref = refs[i]
                 self.proto.write_pkt_line(ref[1] + b' ' + ref[0] + b'\n')

+ 5 - 2
dulwich/tests/compat/server_utils.py

@@ -28,6 +28,9 @@ import tempfile
 
 from dulwich.repo import Repo
 from dulwich.objects import hex_to_sha
+from dulwich.protocol import (
+    CAPABILITY_SIDE_BAND_64K,
+    )
 from dulwich.server import (
     ReceivePackHandler,
     )
@@ -300,8 +303,8 @@ class NoSideBand64kReceivePackHandler(ReceivePackHandler):
 
     @classmethod
     def capabilities(cls):
-        return tuple(c for c in ReceivePackHandler.capabilities()
-                     if c != b'side-band-64k')
+        return [c for c in ReceivePackHandler.capabilities()
+                if c != CAPABILITY_SIDE_BAND_64K]
 
 
 def ignore_error(error):

+ 2 - 2
dulwich/tests/compat/test_web.py

@@ -111,8 +111,8 @@ def patch_capabilities(handler, caps_removed):
     # Patch a handler's capabilities by specifying a list of them to be
     # removed, and return the original classmethod for restoration.
     original_capabilities = handler.capabilities
-    filtered_capabilities = tuple(
-        i for i in original_capabilities() if i not in caps_removed)
+    filtered_capabilities = [
+        i for i in original_capabilities() if i not in caps_removed]
 
     def capabilities(cls):
         return filtered_capabilities

+ 2 - 2
dulwich/tests/test_porcelain.py

@@ -950,8 +950,8 @@ class ReceivePackTests(PorcelainTestCase):
                 self.repo.path, BytesIO(b"0000"), outf)
         outlines = outf.getvalue().splitlines()
         self.assertEqual([
-            b'00739e65bdcf4a22cdd4f3700604a275cd2aaf146b23 HEAD\x00 report-status '  # noqa: E501
-            b'delete-refs quiet ofs-delta side-band-64k no-done',
+            b'00919e65bdcf4a22cdd4f3700604a275cd2aaf146b23 HEAD\x00 report-status '  # noqa: E501
+            b'delete-refs quiet ofs-delta side-band-64k no-done symref=HEAD:refs/heads/master',
             b'003f9e65bdcf4a22cdd4f3700604a275cd2aaf146b23 refs/heads/master',
             b'0000'], outlines)
         self.assertEqual(0, exitcode)

+ 11 - 8
dulwich/tests/test_server.py

@@ -50,7 +50,7 @@ from dulwich.server import (
     _split_proto_line,
     serve_command,
     _find_shallow,
-    ProtocolGraphWalker,
+    _ProtocolGraphWalker,
     ReceivePackHandler,
     SingleAckGraphWalkerImpl,
     UploadPackHandler,
@@ -111,11 +111,11 @@ class TestGenericPackHandler(PackHandler):
 
     @classmethod
     def capabilities(cls):
-        return (b'cap1', b'cap2', b'cap3')
+        return [b'cap1', b'cap2', b'cap3']
 
     @classmethod
     def required_capabilities(cls):
-        return (b'cap2',)
+        return [b'cap2']
 
 
 class HandlerTestCase(TestCase):
@@ -290,9 +290,10 @@ class FindShallowTests(TestCase):
 
 
 class TestUploadPackHandler(UploadPackHandler):
+
     @classmethod
     def required_capabilities(self):
-        return ()
+        return []
 
 
 class ReceivePackHandlerTestCase(TestCase):
@@ -323,10 +324,11 @@ class ProtocolGraphWalkerEmptyTestCase(TestCase):
         super(ProtocolGraphWalkerEmptyTestCase, self).setUp()
         self._repo = MemoryRepo.init_bare([], {})
         backend = DictBackend({b'/': self._repo})
-        self._walker = ProtocolGraphWalker(
+        self._walker = _ProtocolGraphWalker(
                 TestUploadPackHandler(backend, [b'/', b'host=lolcats'],
                                       TestProto()),
-                self._repo.object_store, self._repo.get_peeled)
+                self._repo.object_store, self._repo.get_peeled,
+                self._repo.refs.get_symrefs)
 
     def test_empty_repository(self):
         # The server should wait for a flush packet.
@@ -356,10 +358,11 @@ class ProtocolGraphWalkerTestCase(TestCase):
           ]
         self._repo = MemoryRepo.init_bare(commits, {})
         backend = DictBackend({b'/': self._repo})
-        self._walker = ProtocolGraphWalker(
+        self._walker = _ProtocolGraphWalker(
                 TestUploadPackHandler(backend, [b'/', b'host=lolcats'],
                                       TestProto()),
-                self._repo.object_store, self._repo.get_peeled)
+                self._repo.object_store, self._repo.get_peeled,
+                self._repo.refs.get_symrefs)
 
     def test_all_wants_satisfied_no_haves(self):
         self._walker.set_wants([ONE])