瀏覽代碼

Merge branch 'fix-remote-porcelain'

Jelmer Vernooij 3 年之前
父節點
當前提交
87ad39afa7
共有 5 個文件被更改,包括 99 次插入12 次删除
  1. 2 0
      NEWS
  2. 69 0
      dulwich/client.py
  3. 13 11
      dulwich/porcelain.py
  4. 3 1
      dulwich/repo.py
  5. 12 0
      dulwich/tests/test_client.py

+ 2 - 0
NEWS

@@ -1,5 +1,7 @@
 0.20.31	UNRELEASED
 
+ * Add GitClient.clone(). (Jelmer Vernooij)
+
 0.20.30	2022-01-08
 
 0.20.29	2022-01-08

+ 69 - 0
dulwich/client.py

@@ -110,6 +110,7 @@ from dulwich.pack import (
 from dulwich.refs import (
     read_info_refs,
     ANNOTATED_TAG_SUFFIX,
+    _import_remote_refs,
 )
 
 
@@ -495,6 +496,74 @@ class GitClient(object):
         """
         raise NotImplementedError(self.send_pack)
 
+    def clone(self, path, target_path, mkdir: bool = True, bare=False, origin="origin",
+              checkout=None, branch=None, depth=None):
+        """Clone a repository."""
+        from .refs import _set_origin_head, _set_default_branch, _set_head
+        from .repo import Repo
+
+        if mkdir:
+            os.mkdir(target_path)
+
+        try:
+            target = None
+            if not bare:
+                target = Repo.init(target_path)
+                if checkout is None:
+                    checkout = True
+            else:
+                if checkout:
+                    raise ValueError("checkout and bare are incompatible")
+                target = Repo.init_bare(target_path)
+
+            # TODO(jelmer): abstract method for get_location?
+            if isinstance(self, (LocalGitClient, SubprocessGitClient)):
+                encoded_path = path.encode('utf-8')
+            else:
+                encoded_path = self.get_url(path).encode('utf-8')
+
+            target_config = target.get_config()
+            target_config.set((b"remote", origin.encode('utf-8')), b"url", encoded_path)
+            target_config.set(
+                (b"remote", origin.encode('utf-8')),
+                b"fetch",
+                b"+refs/heads/*:refs/remotes/" + origin.encode('utf-8') + b"/*",
+            )
+            target_config.write_to_path()
+
+            ref_message = b"clone: from " + encoded_path
+            result = self.fetch(path, target, depth=depth)
+            _import_remote_refs(
+                target.refs, origin, result.refs, message=ref_message)
+
+            origin_head = result.symrefs.get(b"HEAD")
+            origin_sha = result.refs.get(b'HEAD')
+            if origin_sha and not origin_head:
+                # set detached HEAD
+                target.refs[b"HEAD"] = origin_sha
+
+            _set_origin_head(target.refs, origin.encode('utf-8'), origin_head)
+            head_ref = _set_default_branch(
+                target.refs, origin.encode('utf-8'), origin_head, branch, ref_message
+            )
+
+            # Update target head
+            if head_ref:
+                head = _set_head(target.refs, head_ref, ref_message)
+            else:
+                head = None
+
+            if checkout and head is not None:
+                target.reset_index()
+        except BaseException:
+            if target is not None:
+                target.close()
+            if mkdir:
+                import shutil
+                shutil.rmtree(target_path)
+            raise
+        return target
+
     def fetch(self, path, target, determine_wants=None, progress=None, depth=None):
         """Fetch into a target repository.
 

+ 13 - 11
dulwich/porcelain.py

@@ -74,7 +74,6 @@ import stat
 import sys
 import time
 from typing import (
-    Dict,
     Optional,
     Tuple,
     Union,
@@ -402,7 +401,7 @@ def clone(
     checkout=None,
     errstream=default_bytes_err_stream,
     outstream=None,
-    origin=b"origin",
+    origin="origin",
     depth=None,
     branch=None,
     **kwargs
@@ -442,15 +441,18 @@ def clone(
 
     mkdir = not os.path.exists(target)
 
-    with open_repo_closing(source) as r:
-        return r.clone(
-            target,
-            mkdir=mkdir,
-            bare=bare,
-            origin=origin,
-            checkout=checkout,
-            branch=branch,
-        )
+    (client, path) = get_transport_and_path(source)
+
+    return client.clone(
+        path,
+        target,
+        mkdir=mkdir,
+        bare=bare,
+        origin=origin,
+        checkout=checkout,
+        branch=branch,
+        depth=depth,
+    )
 
 
 def add(repo=".", paths=None):

+ 3 - 1
dulwich/repo.py

@@ -1389,6 +1389,7 @@ class Repo(BaseRepo):
         origin=b"origin",
         checkout=None,
         branch=None,
+        depth=None,
     ):
         """Clone this repository.
 
@@ -1401,6 +1402,7 @@ class Repo(BaseRepo):
             cloned from this repository
           branch: Optional branch or tag to be used as HEAD in the new repository
             instead of this repository's HEAD.
+          depth: Depth at which to fetch
         Returns: Created repository as `Repo`
         """
 
@@ -1432,7 +1434,7 @@ class Repo(BaseRepo):
             target_config.write_to_path()
 
             ref_message = b"clone: from " + encoded_path
-            self.fetch(target)
+            self.fetch(target, depth=depth)
             target.refs.import_refs(
                 b"refs/remotes/" + origin,
                 self.refs.as_dict(b"refs/heads"),

+ 12 - 0
dulwich/tests/test_client.py

@@ -841,6 +841,18 @@ class LocalGitClientTests(TestCase):
         self.addCleanup(tear_down_repo, s)
         self.assertEqual(s.get_refs(), c.fetch(s.path, t).refs)
 
+    def test_clone(self):
+        c = LocalGitClient()
+        s = open_repo("a.git")
+        self.addCleanup(tear_down_repo, s)
+        target = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, target)
+        result_repo = c.clone(s.path, target, mkdir=False)
+        expected = dict(s.get_refs())
+        expected[b'refs/remotes/origin/HEAD'] = expected[b'HEAD']
+        expected[b'refs/remotes/origin/master'] = expected[b'refs/heads/master']
+        self.assertEqual(expected, result_repo.get_refs())
+
     def test_fetch_empty(self):
         c = LocalGitClient()
         s = open_repo("a.git")