objectspec.py 6.4 KB

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