head.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. # This module is part of GitPython and is released under the
  2. # 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
  3. """Some ref-based objects.
  4. Note the distinction between the :class:`HEAD` and :class:`Head` classes.
  5. """
  6. __all__ = ["HEAD", "Head"]
  7. from git.config import GitConfigParser, SectionConstraint
  8. from git.exc import GitCommandError
  9. from git.util import join_path
  10. from .reference import Reference
  11. from .symbolic import SymbolicReference
  12. # typing ---------------------------------------------------
  13. from typing import Any, Sequence, TYPE_CHECKING, Union
  14. from git.types import Commit_ish, PathLike
  15. if TYPE_CHECKING:
  16. from git.refs import RemoteReference
  17. from git.repo import Repo
  18. # -------------------------------------------------------------------
  19. def strip_quotes(string: str) -> str:
  20. if string.startswith('"') and string.endswith('"'):
  21. return string[1:-1]
  22. return string
  23. class HEAD(SymbolicReference):
  24. """Special case of a :class:`~git.refs.symbolic.SymbolicReference` representing the
  25. repository's HEAD reference."""
  26. _HEAD_NAME = "HEAD"
  27. _ORIG_HEAD_NAME = "ORIG_HEAD"
  28. __slots__ = ()
  29. def __init__(self, repo: "Repo", path: PathLike = _HEAD_NAME) -> None:
  30. if path != self._HEAD_NAME:
  31. raise ValueError("HEAD instance must point to %r, got %r" % (self._HEAD_NAME, path))
  32. super().__init__(repo, path)
  33. def orig_head(self) -> SymbolicReference:
  34. """
  35. :return:
  36. :class:`~git.refs.symbolic.SymbolicReference` pointing at the ORIG_HEAD,
  37. which is maintained to contain the previous value of HEAD.
  38. """
  39. return SymbolicReference(self.repo, self._ORIG_HEAD_NAME)
  40. def reset(
  41. self,
  42. commit: Union[Commit_ish, SymbolicReference, str] = "HEAD",
  43. index: bool = True,
  44. working_tree: bool = False,
  45. paths: Union[PathLike, Sequence[PathLike], None] = None,
  46. **kwargs: Any,
  47. ) -> "HEAD":
  48. """Reset our HEAD to the given commit optionally synchronizing the index and
  49. working tree. The reference we refer to will be set to commit as well.
  50. :param commit:
  51. :class:`~git.objects.commit.Commit`, :class:`~git.refs.reference.Reference`,
  52. or string identifying a revision we should reset HEAD to.
  53. :param index:
  54. If ``True``, the index will be set to match the given commit.
  55. Otherwise it will not be touched.
  56. :param working_tree:
  57. If ``True``, the working tree will be forcefully adjusted to match the given
  58. commit, possibly overwriting uncommitted changes without warning.
  59. If `working_tree` is ``True``, `index` must be ``True`` as well.
  60. :param paths:
  61. Single path or list of paths relative to the git root directory
  62. that are to be reset. This allows to partially reset individual files.
  63. :param kwargs:
  64. Additional arguments passed to :manpage:`git-reset(1)`.
  65. :return:
  66. self
  67. """
  68. mode: Union[str, None]
  69. mode = "--soft"
  70. if index:
  71. mode = "--mixed"
  72. # Explicit "--mixed" when passing paths is deprecated since git 1.5.4.
  73. # See https://github.com/gitpython-developers/GitPython/discussions/1876.
  74. if paths:
  75. mode = None
  76. # END special case
  77. # END handle index
  78. if working_tree:
  79. mode = "--hard"
  80. if not index:
  81. raise ValueError("Cannot reset the working tree if the index is not reset as well")
  82. # END working tree handling
  83. try:
  84. self.repo.git.reset(mode, commit, "--", paths, **kwargs)
  85. except GitCommandError as e:
  86. # git nowadays may use 1 as status to indicate there are still unstaged
  87. # modifications after the reset.
  88. if e.status != 1:
  89. raise
  90. # END handle exception
  91. return self
  92. class Head(Reference):
  93. """A Head is a named reference to a :class:`~git.objects.commit.Commit`. Every Head
  94. instance contains a name and a :class:`~git.objects.commit.Commit` object.
  95. Examples::
  96. >>> repo = Repo("/path/to/repo")
  97. >>> head = repo.heads[0]
  98. >>> head.name
  99. 'master'
  100. >>> head.commit
  101. <git.Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">
  102. >>> head.commit.hexsha
  103. '1c09f116cbc2cb4100fb6935bb162daa4723f455'
  104. """
  105. _common_path_default = "refs/heads"
  106. k_config_remote = "remote"
  107. k_config_remote_ref = "merge" # Branch to merge from remote.
  108. @classmethod
  109. def delete(cls, repo: "Repo", *heads: "Union[Head, str]", force: bool = False, **kwargs: Any) -> None: # type: ignore[override]
  110. """Delete the given heads.
  111. :param force:
  112. If ``True``, the heads will be deleted even if they are not yet merged into
  113. the main development stream. Default ``False``.
  114. """
  115. flag = "-d"
  116. if force:
  117. flag = "-D"
  118. repo.git.branch(flag, *heads)
  119. def set_tracking_branch(self, remote_reference: Union["RemoteReference", None]) -> "Head":
  120. """Configure this branch to track the given remote reference. This will
  121. alter this branch's configuration accordingly.
  122. :param remote_reference:
  123. The remote reference to track or None to untrack any references.
  124. :return:
  125. self
  126. """
  127. from .remote import RemoteReference
  128. if remote_reference is not None and not isinstance(remote_reference, RemoteReference):
  129. raise ValueError("Incorrect parameter type: %r" % remote_reference)
  130. # END handle type
  131. with self.config_writer() as writer:
  132. if remote_reference is None:
  133. writer.remove_option(self.k_config_remote)
  134. writer.remove_option(self.k_config_remote_ref)
  135. if len(writer.options()) == 0:
  136. writer.remove_section()
  137. else:
  138. writer.set_value(self.k_config_remote, remote_reference.remote_name)
  139. writer.set_value(
  140. self.k_config_remote_ref,
  141. Head.to_full_path(remote_reference.remote_head),
  142. )
  143. return self
  144. def tracking_branch(self) -> Union["RemoteReference", None]:
  145. """
  146. :return:
  147. The remote reference we are tracking, or ``None`` if we are not a tracking
  148. branch.
  149. """
  150. from .remote import RemoteReference
  151. reader = self.config_reader()
  152. if reader.has_option(self.k_config_remote) and reader.has_option(self.k_config_remote_ref):
  153. ref = Head(
  154. self.repo,
  155. Head.to_full_path(strip_quotes(reader.get_value(self.k_config_remote_ref))),
  156. )
  157. remote_refpath = RemoteReference.to_full_path(join_path(reader.get_value(self.k_config_remote), ref.name))
  158. return RemoteReference(self.repo, remote_refpath)
  159. # END handle have tracking branch
  160. # We are not a tracking branch.
  161. return None
  162. def rename(self, new_path: PathLike, force: bool = False) -> "Head":
  163. """Rename self to a new path.
  164. :param new_path:
  165. Either a simple name or a path, e.g. ``new_name`` or ``features/new_name``.
  166. The prefix ``refs/heads`` is implied.
  167. :param force:
  168. If ``True``, the rename will succeed even if a head with the target name
  169. already exists.
  170. :return:
  171. self
  172. :note:
  173. Respects the ref log, as git commands are used.
  174. """
  175. flag = "-m"
  176. if force:
  177. flag = "-M"
  178. self.repo.git.branch(flag, self, new_path)
  179. self.path = "%s/%s" % (self._common_path_default, new_path)
  180. return self
  181. def checkout(self, force: bool = False, **kwargs: Any) -> Union["HEAD", "Head"]:
  182. """Check out this head by setting the HEAD to this reference, by updating the
  183. index to reflect the tree we point to and by updating the working tree to
  184. reflect the latest index.
  185. The command will fail if changed working tree files would be overwritten.
  186. :param force:
  187. If ``True``, changes to the index and the working tree will be discarded.
  188. If ``False``, :exc:`~git.exc.GitCommandError` will be raised in that
  189. situation.
  190. :param kwargs:
  191. Additional keyword arguments to be passed to git checkout, e.g.
  192. ``b="new_branch"`` to create a new branch at the given spot.
  193. :return:
  194. The active branch after the checkout operation, usually self unless a new
  195. branch has been created.
  196. If there is no active branch, as the HEAD is now detached, the HEAD
  197. reference will be returned instead.
  198. :note:
  199. By default it is only allowed to checkout heads - everything else will leave
  200. the HEAD detached which is allowed and possible, but remains a special state
  201. that some tools might not be able to handle.
  202. """
  203. kwargs["f"] = force
  204. if kwargs["f"] is False:
  205. kwargs.pop("f")
  206. self.repo.git.checkout(self, **kwargs)
  207. if self.repo.head.is_detached:
  208. return self.repo.head
  209. else:
  210. return self.repo.active_branch
  211. # { Configuration
  212. def _config_parser(self, read_only: bool) -> SectionConstraint[GitConfigParser]:
  213. if read_only:
  214. parser = self.repo.config_reader()
  215. else:
  216. parser = self.repo.config_writer()
  217. # END handle parser instance
  218. return SectionConstraint(parser, 'branch "%s"' % self.name)
  219. def config_reader(self) -> SectionConstraint[GitConfigParser]:
  220. """
  221. :return:
  222. A configuration parser instance constrained to only read this instance's
  223. values.
  224. """
  225. return self._config_parser(read_only=True)
  226. def config_writer(self) -> SectionConstraint[GitConfigParser]:
  227. """
  228. :return:
  229. A configuration writer instance with read-and write access to options of
  230. this head.
  231. """
  232. return self._config_parser(read_only=False)
  233. # } END configuration