objectspec.py 6.7 KB

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