Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/git/refs/head.py: 34%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This module is part of GitPython and is released under the
2# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
4"""Some ref-based objects.
6Note the distinction between the :class:`HEAD` and :class:`Head` classes.
7"""
9__all__ = ["HEAD", "Head"]
11from git.config import GitConfigParser, SectionConstraint
12from git.exc import GitCommandError
13from git.util import join_path
15from .reference import Reference
16from .symbolic import SymbolicReference
18# typing ---------------------------------------------------
20from typing import Any, Sequence, TYPE_CHECKING, Union
22from git.cmd import Git
23from git.types import Commit_ish, PathLike
25if TYPE_CHECKING:
26 from git.refs import RemoteReference
27 from git.repo import Repo
29# -------------------------------------------------------------------
32def strip_quotes(string: str) -> str:
33 if string.startswith('"') and string.endswith('"'):
34 return string[1:-1]
35 return string
38class HEAD(SymbolicReference):
39 """Special case of a :class:`~git.refs.symbolic.SymbolicReference` representing the
40 repository's HEAD reference."""
42 _HEAD_NAME = "HEAD"
43 _ORIG_HEAD_NAME = "ORIG_HEAD"
45 __slots__ = ()
47 def __init__(self, repo: "Repo", path: PathLike = _HEAD_NAME) -> None:
48 if path != self._HEAD_NAME:
49 raise ValueError("HEAD instance must point to %r, got %r" % (self._HEAD_NAME, path))
50 super().__init__(repo, path)
52 def orig_head(self) -> SymbolicReference:
53 """
54 :return:
55 :class:`~git.refs.symbolic.SymbolicReference` pointing at the ORIG_HEAD,
56 which is maintained to contain the previous value of HEAD.
57 """
58 return SymbolicReference(self.repo, self._ORIG_HEAD_NAME)
60 def reset(
61 self,
62 commit: Union[Commit_ish, SymbolicReference, str] = "HEAD",
63 index: bool = True,
64 working_tree: bool = False,
65 paths: Union[PathLike, Sequence[PathLike], None] = None,
66 allow_unsafe_options: bool = False,
67 **kwargs: Any,
68 ) -> "HEAD":
69 """Reset our HEAD to the given commit optionally synchronizing the index and
70 working tree. The reference we refer to will be set to commit as well.
72 :param commit:
73 :class:`~git.objects.commit.Commit`, :class:`~git.refs.reference.Reference`,
74 or string identifying a revision we should reset HEAD to.
76 :param index:
77 If ``True``, the index will be set to match the given commit.
78 Otherwise it will not be touched.
80 :param working_tree:
81 If ``True``, the working tree will be forcefully adjusted to match the given
82 commit, possibly overwriting uncommitted changes without warning.
83 If `working_tree` is ``True``, `index` must be ``True`` as well.
85 :param paths:
86 Single path or list of paths relative to the git root directory
87 that are to be reset. This allows to partially reset individual files.
89 :param allow_unsafe_options:
90 Allow unsafe options such as ``--pathspec-from-file`` to be passed to
91 :manpage:`git-reset(1)`.
93 :param kwargs:
94 Additional arguments passed to :manpage:`git-reset(1)`.
96 :return:
97 self
98 """
99 if not allow_unsafe_options:
100 Git.check_unsafe_options(
101 options=Git._option_candidates([commit], kwargs),
102 unsafe_options=Git.unsafe_git_pathspec_from_file_options,
103 )
104 mode: Union[str, None]
105 mode = "--soft"
106 if index:
107 mode = "--mixed"
109 # Explicit "--mixed" when passing paths is deprecated since git 1.5.4.
110 # See https://github.com/gitpython-developers/GitPython/discussions/1876.
111 if paths:
112 mode = None
113 # END special case
114 # END handle index
116 if working_tree:
117 mode = "--hard"
118 if not index:
119 raise ValueError("Cannot reset the working tree if the index is not reset as well")
121 # END working tree handling
123 try:
124 self.repo.git.reset(mode, commit, "--", paths, **kwargs)
125 except GitCommandError as e:
126 # git nowadays may use 1 as status to indicate there are still unstaged
127 # modifications after the reset.
128 if e.status != 1:
129 raise
130 # END handle exception
132 return self
135class Head(Reference):
136 """A Head is a named reference to a :class:`~git.objects.commit.Commit`. Every Head
137 instance contains a name and a :class:`~git.objects.commit.Commit` object.
139 Examples::
141 >>> repo = Repo("/path/to/repo")
142 >>> head = repo.heads[0]
144 >>> head.name
145 'master'
147 >>> head.commit
148 <git.Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">
150 >>> head.commit.hexsha
151 '1c09f116cbc2cb4100fb6935bb162daa4723f455'
152 """
154 _common_path_default = "refs/heads"
155 k_config_remote = "remote"
156 k_config_remote_ref = "merge" # Branch to merge from remote.
158 @classmethod
159 def delete(cls, repo: "Repo", *heads: "Union[Head, str]", force: bool = False, **kwargs: Any) -> None: # type: ignore[override]
160 """Delete the given heads.
162 :param force:
163 If ``True``, the heads will be deleted even if they are not yet merged into
164 the main development stream. Default ``False``.
165 """
166 flag = "-d"
167 if force:
168 flag = "-D"
169 repo.git.branch(flag, *heads)
171 def set_tracking_branch(self, remote_reference: Union["RemoteReference", None]) -> "Head":
172 """Configure this branch to track the given remote reference. This will
173 alter this branch's configuration accordingly.
175 :param remote_reference:
176 The remote reference to track or None to untrack any references.
178 :return:
179 self
180 """
181 from .remote import RemoteReference
183 if remote_reference is not None and not isinstance(remote_reference, RemoteReference):
184 raise ValueError("Incorrect parameter type: %r" % remote_reference)
185 # END handle type
187 with self.config_writer() as writer:
188 if remote_reference is None:
189 writer.remove_option(self.k_config_remote)
190 writer.remove_option(self.k_config_remote_ref)
191 if len(writer.options()) == 0:
192 writer.remove_section()
193 else:
194 writer.set_value(self.k_config_remote, remote_reference.remote_name)
195 writer.set_value(
196 self.k_config_remote_ref,
197 Head.to_full_path(remote_reference.remote_head),
198 )
200 return self
202 def tracking_branch(self) -> Union["RemoteReference", None]:
203 """
204 :return:
205 The remote reference we are tracking, or ``None`` if we are not a tracking
206 branch.
207 """
208 from .remote import RemoteReference
210 reader = self.config_reader()
211 if reader.has_option(self.k_config_remote) and reader.has_option(self.k_config_remote_ref):
212 ref = Head(
213 self.repo,
214 Head.to_full_path(strip_quotes(reader.get_value(self.k_config_remote_ref))),
215 )
216 remote_refpath = RemoteReference.to_full_path(join_path(reader.get_value(self.k_config_remote), ref.name))
217 return RemoteReference(self.repo, remote_refpath)
218 # END handle have tracking branch
220 # We are not a tracking branch.
221 return None
223 def rename(self, new_path: PathLike, force: bool = False) -> "Head":
224 """Rename self to a new path.
226 :param new_path:
227 Either a simple name or a path, e.g. ``new_name`` or ``features/new_name``.
228 The prefix ``refs/heads`` is implied.
230 :param force:
231 If ``True``, the rename will succeed even if a head with the target name
232 already exists.
234 :return:
235 self
237 :note:
238 Respects the ref log, as git commands are used.
239 """
240 flag = "-m"
241 if force:
242 flag = "-M"
244 self.repo.git.branch(flag, self, new_path)
245 self.path = "%s/%s" % (self._common_path_default, new_path)
246 return self
248 def checkout(
249 self,
250 force: bool = False,
251 allow_unsafe_options: bool = False,
252 **kwargs: Any,
253 ) -> Union["HEAD", "Head"]:
254 """Check out this head by setting the HEAD to this reference, by updating the
255 index to reflect the tree we point to and by updating the working tree to
256 reflect the latest index.
258 The command will fail if changed working tree files would be overwritten.
260 :param force:
261 If ``True``, changes to the index and the working tree will be discarded.
262 If ``False``, :exc:`~git.exc.GitCommandError` will be raised in that
263 situation.
265 :param allow_unsafe_options:
266 Allow unsafe options such as ``--pathspec-from-file`` to be passed to
267 :manpage:`git-checkout(1)`.
269 :param kwargs:
270 Additional keyword arguments to be passed to git checkout, e.g.
271 ``b="new_branch"`` to create a new branch at the given spot.
273 :return:
274 The active branch after the checkout operation, usually self unless a new
275 branch has been created.
276 If there is no active branch, as the HEAD is now detached, the HEAD
277 reference will be returned instead.
279 :note:
280 By default it is only allowed to checkout heads - everything else will leave
281 the HEAD detached which is allowed and possible, but remains a special state
282 that some tools might not be able to handle.
283 """
284 if not allow_unsafe_options:
285 Git.check_unsafe_options(
286 options=Git._option_candidates([], kwargs),
287 unsafe_options=Git.unsafe_git_pathspec_from_file_options,
288 )
289 kwargs["f"] = force
290 if kwargs["f"] is False:
291 kwargs.pop("f")
293 self.repo.git.checkout(self, **kwargs)
294 if self.repo.head.is_detached:
295 return self.repo.head
296 else:
297 return self.repo.active_branch
299 # { Configuration
300 def _config_parser(self, read_only: bool) -> SectionConstraint[GitConfigParser]:
301 if read_only:
302 parser = self.repo.config_reader()
303 else:
304 parser = self.repo.config_writer()
305 # END handle parser instance
307 return SectionConstraint(parser, 'branch "%s"' % self.name)
309 def config_reader(self) -> SectionConstraint[GitConfigParser]:
310 """
311 :return:
312 A configuration parser instance constrained to only read this instance's
313 values.
314 """
315 return self._config_parser(read_only=True)
317 def config_writer(self) -> SectionConstraint[GitConfigParser]:
318 """
319 :return:
320 A configuration writer instance with read-and write access to options of
321 this head.
322 """
323 return self._config_parser(read_only=False)
325 # } END configuration