clone.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. # clone.py
  2. # Copyright (C) 2021 Jelmer Vernooij <jelmer@samba.org>
  3. #
  4. # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
  5. # General Public License as public by the Free Software Foundation; version 2.0
  6. # or (at your option) any later version. You can redistribute it and/or
  7. # modify it under the terms of either of these two licenses.
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. #
  15. # You should have received a copy of the licenses; if not, see
  16. # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
  17. # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
  18. # License, Version 2.0.
  19. #
  20. """Repository clone handling."""
  21. import os
  22. from typing import TYPE_CHECKING, Callable, Tuple
  23. from dulwich.objects import (
  24. Tag,
  25. )
  26. from dulwich.refs import (
  27. LOCAL_BRANCH_PREFIX,
  28. LOCAL_TAG_PREFIX,
  29. )
  30. if TYPE_CHECKING:
  31. from dulwich.repo import Repo
  32. def do_clone(
  33. source_path,
  34. target_path,
  35. clone_refs: Callable[["Repo", bytes], Tuple[bytes, bytes]] = None,
  36. mkdir=True,
  37. bare=False,
  38. origin=b"origin",
  39. checkout=None,
  40. errstream=None,
  41. branch=None,
  42. ):
  43. """Clone a repository.
  44. Args:
  45. source_path: Source repository path
  46. target_path: Target repository path
  47. clone_refs: Callback to handle setting up cloned remote refs in
  48. the target repo
  49. mkdir: Create the target directory
  50. bare: Whether to create a bare repository
  51. checkout: Whether or not to check-out HEAD after cloning
  52. origin: Base name for refs in target repository
  53. cloned from this repository
  54. branch: Optional branch or tag to be used as HEAD in the new repository
  55. instead of the source repository's HEAD.
  56. Returns: Created repository as `Repo`
  57. """
  58. from dulwich.repo import Repo
  59. if not clone_refs:
  60. raise ValueError("clone_refs callback is required")
  61. if not bare:
  62. target = Repo.init(target_path, mkdir=mkdir)
  63. if checkout is None:
  64. checkout = True
  65. else:
  66. if checkout:
  67. raise ValueError("checkout and bare are incompatible")
  68. target = Repo.init_bare(target_path, mkdir=mkdir)
  69. try:
  70. target_config = target.get_config()
  71. target_config.set((b"remote", origin), b"url", source_path)
  72. target_config.set(
  73. (b"remote", origin),
  74. b"fetch",
  75. b"+refs/heads/*:refs/remotes/" + origin + b"/*",
  76. )
  77. target_config.write_to_path()
  78. ref_message = b"clone: from " + source_path
  79. origin_head, origin_sha = clone_refs(target, ref_message)
  80. if origin_sha and not origin_head:
  81. # set detached HEAD
  82. target.refs[b"HEAD"] = origin_sha
  83. _set_origin_head(target, origin, origin_head)
  84. head_ref = _set_default_branch(
  85. target, origin, origin_head, branch, ref_message
  86. )
  87. # Update target head
  88. if head_ref:
  89. head = _set_head(target, head_ref, ref_message)
  90. else:
  91. head = None
  92. if checkout and head is not None:
  93. if errstream:
  94. errstream.write(b"Checking out " + head + b"\n")
  95. target.reset_index()
  96. except BaseException:
  97. target.close()
  98. raise
  99. return target
  100. def _set_origin_head(r, origin, origin_head):
  101. # set refs/remotes/origin/HEAD
  102. origin_base = b"refs/remotes/" + origin + b"/"
  103. if origin_head and origin_head.startswith(LOCAL_BRANCH_PREFIX):
  104. origin_ref = origin_base + b"HEAD"
  105. target_ref = origin_base + origin_head[len(LOCAL_BRANCH_PREFIX) :]
  106. if target_ref in r.refs:
  107. r.refs.set_symbolic_ref(origin_ref, target_ref)
  108. def _set_default_branch(r, origin, origin_head, branch, ref_message):
  109. origin_base = b"refs/remotes/" + origin + b"/"
  110. if branch:
  111. origin_ref = origin_base + branch
  112. if origin_ref in r.refs:
  113. local_ref = LOCAL_BRANCH_PREFIX + branch
  114. r.refs.add_if_new(
  115. local_ref, r.refs[origin_ref], ref_message
  116. )
  117. head_ref = local_ref
  118. elif LOCAL_TAG_PREFIX + branch in r.refs:
  119. head_ref = LOCAL_TAG_PREFIX + branch
  120. else:
  121. raise ValueError(
  122. "%s is not a valid branch or tag" % os.fsencode(branch)
  123. )
  124. elif origin_head:
  125. head_ref = origin_head
  126. if origin_head.startswith(LOCAL_BRANCH_PREFIX):
  127. origin_ref = origin_base + origin_head[len(LOCAL_BRANCH_PREFIX) :]
  128. else:
  129. origin_ref = origin_head
  130. try:
  131. r.refs.add_if_new(
  132. head_ref, r.refs[origin_ref], ref_message
  133. )
  134. except KeyError:
  135. pass
  136. return head_ref
  137. def _set_head(r, head_ref, ref_message):
  138. if head_ref.startswith(LOCAL_TAG_PREFIX):
  139. # detach HEAD at specified tag
  140. head = r.refs[head_ref]
  141. if isinstance(head, Tag):
  142. _cls, obj = head.object
  143. head = obj.get_object(obj).id
  144. del r.refs[b"HEAD"]
  145. r.refs.set_if_equals(
  146. b"HEAD", None, head, message=ref_message
  147. )
  148. else:
  149. # set HEAD to specific branch
  150. try:
  151. head = r.refs[head_ref]
  152. r.refs.set_symbolic_ref(b"HEAD", head_ref)
  153. r.refs.set_if_equals(
  154. b"HEAD", None, head, message=ref_message
  155. )
  156. except KeyError:
  157. head = None
  158. return head