2
0

objectspec.py 6.4 KB

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