Browse Source

porcelain: add clone(branch=...) option

- equivalent to 'git clone --branch' for checking out specific branch or
  tag name after clone
- porcelain.clone now uses the remote's default branch name if it
  differs from dulwich DEFAULT_REF
Peter Rowlands 4 years ago
parent
commit
5e193cc92b
1 changed files with 44 additions and 8 deletions
  1. 44 8
      dulwich/porcelain.py

+ 44 - 8
dulwich/porcelain.py

@@ -144,7 +144,7 @@ from dulwich.refs import (
     strip_peeled_refs,
     strip_peeled_refs,
     RefsContainer,
     RefsContainer,
 )
 )
-from dulwich.repo import BaseRepo, Repo
+from dulwich.repo import DEFAULT_REF, BaseRepo, Repo
 from dulwich.server import (
 from dulwich.server import (
     FileSystemBackend,
     FileSystemBackend,
     TCPGitServer,
     TCPGitServer,
@@ -404,6 +404,7 @@ def clone(
     outstream=None,
     outstream=None,
     origin=b"origin",
     origin=b"origin",
     depth=None,
     depth=None,
+    branch=None,
     **kwargs
     **kwargs
 ):
 ):
     """Clone a local or remote git repository.
     """Clone a local or remote git repository.
@@ -466,14 +467,21 @@ def clone(
             depth=depth,
             depth=depth,
             **kwargs
             **kwargs
         )
         )
-        for key, target in fetch_result.symrefs.items():
-            r.refs.set_symbolic_ref(key, target)
-        try:
-            head = r[fetch_result.refs[b"HEAD"]]
-        except KeyError:
-            head = None
+        for key, target_ref in fetch_result.symrefs.items():
+            r.refs.set_symbolic_ref(key, target_ref)
+
+        head_ref = b"HEAD" if b"HEAD" in fetch_result.refs else None
+        if branch:
+            for ref in (_make_branch_ref(branch), _make_tag_ref(branch)):
+                if ref in fetch_result.refs:
+                    head_ref = ref
+                    break
+
+        if head_ref:
+            head = _clone_update_head(r, head_ref, fetch_result)
         else:
         else:
-            r[b"HEAD"] = head.id
+            head = None
+
         if checkout and not bare and head is not None:
         if checkout and not bare and head is not None:
             errstream.write(b"Checking out " + head.id + b"\n")
             errstream.write(b"Checking out " + head.id + b"\n")
             r.reset_index(head.tree)
             r.reset_index(head.tree)
@@ -485,6 +493,34 @@ def clone(
     return r
     return r
 
 
 
 
+def _clone_update_head(r, new_ref, fetch_result):
+    ref_message = b"clone: from " + source
+    if new_ref.startswith(LOCAL_TAG_PREFIX):
+        # ref is a tag, detach HEAD at remote tag
+        _cls, head_sha = r[fetch_result.refs[new_ref]].object
+        head = r[head_sha]
+        del r.refs[b"HEAD"]
+        r.refs.set_if_equals(
+            b"HEAD", None, head_sha, message=ref_message
+        )
+    else:
+        head = r[fetch_result.refs[new_ref]]
+        if new_ref == b"HEAD":
+            # set HEAD to default remote branch if it differs from DEFAULT_REF
+            default_ref = fetch_result.symrefs.get(b"HEAD")
+            if default_ref and default_ref != DEFAULT_REF:
+                del r.refs[DEFAULT_REF]
+                r.refs.set_symbolic_ref(b"HEAD", default_ref)
+        else:
+            # set HEAD to specific remote branch
+            del r.refs[DEFAULT_REF]
+            r.refs.set_symbolic_ref(b"HEAD", new_ref)
+        r.refs.set_if_equals(
+            b"HEAD", None, head.id, message=ref_message
+        )
+    return head
+
+
 def add(repo=".", paths=None):
 def add(repo=".", paths=None):
     """Add files to the staging area.
     """Add files to the staging area.