objectspec.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. # objectspec.py -- Object specification
  2. # Copyright (C) 2014 Jelmer Vernooij <jelmer@jelmer.uk>
  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. """Object specification."""
  21. from typing import Union, List, Tuple
  22. def to_bytes(text):
  23. if getattr(text, "encode", None) is not None:
  24. text = text.encode("ascii")
  25. return text
  26. def parse_object(repo, objectish):
  27. """Parse a string referring to an object.
  28. Args:
  29. repo: A `Repo` object
  30. objectish: A string referring to an object
  31. Returns: A git object
  32. Raises:
  33. KeyError: If the object can not be found
  34. """
  35. objectish = to_bytes(objectish)
  36. return repo[objectish]
  37. def parse_tree(repo, treeish):
  38. """Parse a string referring to a tree.
  39. Args:
  40. repo: A `Repo` object
  41. treeish: A string referring to a tree
  42. Returns: A git object
  43. Raises:
  44. KeyError: If the object can not be found
  45. """
  46. treeish = to_bytes(treeish)
  47. o = repo[treeish]
  48. if o.type_name == b"commit":
  49. return repo[o.tree]
  50. return o
  51. def parse_ref(container, refspec):
  52. """Parse a string referring to a reference.
  53. Args:
  54. container: A RefsContainer object
  55. refspec: A string referring to a ref
  56. Returns: A ref
  57. Raises:
  58. KeyError: If the ref can not be found
  59. """
  60. refspec = to_bytes(refspec)
  61. possible_refs = [
  62. refspec,
  63. b"refs/" + refspec,
  64. b"refs/tags/" + refspec,
  65. b"refs/heads/" + refspec,
  66. b"refs/remotes/" + refspec,
  67. b"refs/remotes/" + refspec + b"/HEAD",
  68. ]
  69. for ref in possible_refs:
  70. if ref in container:
  71. return ref
  72. raise KeyError(refspec)
  73. def parse_reftuple(lh_container, rh_container, refspec, force=False):
  74. """Parse a reftuple spec.
  75. Args:
  76. lh_container: A RefsContainer object
  77. hh_container: A RefsContainer object
  78. refspec: A string
  79. Returns: A tuple with left and right ref
  80. Raises:
  81. KeyError: If one of the refs can not be found
  82. """
  83. refspec = to_bytes(refspec)
  84. if refspec.startswith(b"+"):
  85. force = True
  86. refspec = refspec[1:]
  87. if b":" in refspec:
  88. (lh, rh) = refspec.split(b":")
  89. else:
  90. lh = rh = refspec
  91. if lh == b"":
  92. lh = None
  93. else:
  94. lh = parse_ref(lh_container, lh)
  95. if rh == b"":
  96. rh = None
  97. else:
  98. try:
  99. rh = parse_ref(rh_container, rh)
  100. except KeyError:
  101. # TODO: check force?
  102. if b"/" not in rh:
  103. rh = b"refs/heads/" + rh
  104. return (lh, rh, force)
  105. def parse_reftuples(
  106. lh_container, rh_container,
  107. refspecs: Union[bytes, List[bytes], List[Tuple[bytes, bytes]]],
  108. force: bool = False):
  109. """Parse a list of reftuple specs to a list of reftuples.
  110. Args:
  111. lh_container: A RefsContainer object
  112. hh_container: A RefsContainer object
  113. refspecs: A list of refspecs or a string
  114. force: Force overwriting for all reftuples
  115. Returns: A list of refs
  116. Raises:
  117. KeyError: If one of the refs can not be found
  118. """
  119. if not isinstance(refspecs, list):
  120. refspecs = [refspecs]
  121. ret = []
  122. # TODO: Support * in refspecs
  123. for refspec in refspecs:
  124. ret.append(parse_reftuple(lh_container, rh_container, refspec, force=force))
  125. return ret
  126. def parse_refs(container, refspecs):
  127. """Parse a list of refspecs to a list of refs.
  128. Args:
  129. container: A RefsContainer object
  130. refspecs: A list of refspecs or a string
  131. Returns: A list of refs
  132. Raises:
  133. KeyError: If one of the refs can not be found
  134. """
  135. # TODO: Support * in refspecs
  136. if not isinstance(refspecs, list):
  137. refspecs = [refspecs]
  138. ret = []
  139. for refspec in refspecs:
  140. ret.append(parse_ref(container, refspec))
  141. return ret
  142. def parse_commit_range(repo, committishs):
  143. """Parse a string referring to a range of commits.
  144. Args:
  145. repo: A `Repo` object
  146. committishs: A string referring to a range of commits.
  147. Returns: An iterator over `Commit` objects
  148. Raises:
  149. KeyError: When the reference commits can not be found
  150. ValueError: If the range can not be parsed
  151. """
  152. committishs = to_bytes(committishs)
  153. # TODO(jelmer): Support more than a single commit..
  154. return iter([parse_commit(repo, committishs)])
  155. class AmbiguousShortId(Exception):
  156. """The short id is ambiguous."""
  157. def __init__(self, prefix, options):
  158. self.prefix = prefix
  159. self.options = options
  160. def scan_for_short_id(object_store, prefix):
  161. """Scan an object store for a short id."""
  162. # TODO(jelmer): This could short-circuit looking for objects
  163. # starting with a certain prefix.
  164. ret = []
  165. for object_id in object_store:
  166. if object_id.startswith(prefix):
  167. ret.append(object_store[object_id])
  168. if not ret:
  169. raise KeyError(prefix)
  170. if len(ret) == 1:
  171. return ret[0]
  172. raise AmbiguousShortId(prefix, ret)
  173. def parse_commit(repo, committish):
  174. """Parse a string referring to a single commit.
  175. Args:
  176. repo: A` Repo` object
  177. commitish: A string referring to a single commit.
  178. Returns: A Commit object
  179. Raises:
  180. KeyError: When the reference commits can not be found
  181. ValueError: If the range can not be parsed
  182. """
  183. committish = to_bytes(committish)
  184. try:
  185. return repo[committish]
  186. except KeyError:
  187. pass
  188. try:
  189. return repo[parse_ref(repo, committish)]
  190. except KeyError:
  191. pass
  192. if len(committish) >= 4 and len(committish) < 40:
  193. try:
  194. int(committish, 16)
  195. except ValueError:
  196. pass
  197. else:
  198. try:
  199. return scan_for_short_id(repo.object_store, committish)
  200. except KeyError:
  201. pass
  202. raise KeyError(committish)
  203. # TODO: parse_path_in_tree(), which handles e.g. v1.0:Documentation