Spaces:
Running
Running
# This module is part of GitPython and is released under the | |
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ | |
"""Some ref-based objects. | |
Note the distinction between the :class:`HEAD` and :class:`Head` classes. | |
""" | |
__all__ = ["HEAD", "Head"] | |
from git.config import GitConfigParser, SectionConstraint | |
from git.exc import GitCommandError | |
from git.util import join_path | |
from .reference import Reference | |
from .symbolic import SymbolicReference | |
# typing --------------------------------------------------- | |
from typing import Any, Sequence, TYPE_CHECKING, Union | |
from git.types import Commit_ish, PathLike | |
if TYPE_CHECKING: | |
from git.objects import Commit | |
from git.refs import RemoteReference | |
from git.repo import Repo | |
# ------------------------------------------------------------------- | |
def strip_quotes(string: str) -> str: | |
if string.startswith('"') and string.endswith('"'): | |
return string[1:-1] | |
return string | |
class HEAD(SymbolicReference): | |
"""Special case of a :class:`~git.refs.symbolic.SymbolicReference` representing the | |
repository's HEAD reference.""" | |
_HEAD_NAME = "HEAD" | |
_ORIG_HEAD_NAME = "ORIG_HEAD" | |
__slots__ = () | |
# TODO: This can be removed once SymbolicReference.commit has static type hints. | |
commit: "Commit" | |
def __init__(self, repo: "Repo", path: PathLike = _HEAD_NAME) -> None: | |
if path != self._HEAD_NAME: | |
raise ValueError("HEAD instance must point to %r, got %r" % (self._HEAD_NAME, path)) | |
super().__init__(repo, path) | |
def orig_head(self) -> SymbolicReference: | |
""" | |
:return: | |
:class:`~git.refs.symbolic.SymbolicReference` pointing at the ORIG_HEAD, | |
which is maintained to contain the previous value of HEAD. | |
""" | |
return SymbolicReference(self.repo, self._ORIG_HEAD_NAME) | |
def reset( | |
self, | |
commit: Union[Commit_ish, SymbolicReference, str] = "HEAD", | |
index: bool = True, | |
working_tree: bool = False, | |
paths: Union[PathLike, Sequence[PathLike], None] = None, | |
**kwargs: Any, | |
) -> "HEAD": | |
"""Reset our HEAD to the given commit optionally synchronizing the index and | |
working tree. The reference we refer to will be set to commit as well. | |
:param commit: | |
:class:`~git.objects.commit.Commit`, :class:`~git.refs.reference.Reference`, | |
or string identifying a revision we should reset HEAD to. | |
:param index: | |
If ``True``, the index will be set to match the given commit. | |
Otherwise it will not be touched. | |
:param working_tree: | |
If ``True``, the working tree will be forcefully adjusted to match the given | |
commit, possibly overwriting uncommitted changes without warning. | |
If `working_tree` is ``True``, `index` must be ``True`` as well. | |
:param paths: | |
Single path or list of paths relative to the git root directory | |
that are to be reset. This allows to partially reset individual files. | |
:param kwargs: | |
Additional arguments passed to :manpage:`git-reset(1)`. | |
:return: | |
self | |
""" | |
mode: Union[str, None] | |
mode = "--soft" | |
if index: | |
mode = "--mixed" | |
# It appears some git versions declare mixed and paths deprecated. | |
# See http://github.com/Byron/GitPython/issues#issue/2. | |
if paths: | |
mode = None | |
# END special case | |
# END handle index | |
if working_tree: | |
mode = "--hard" | |
if not index: | |
raise ValueError("Cannot reset the working tree if the index is not reset as well") | |
# END working tree handling | |
try: | |
self.repo.git.reset(mode, commit, "--", paths, **kwargs) | |
except GitCommandError as e: | |
# git nowadays may use 1 as status to indicate there are still unstaged | |
# modifications after the reset. | |
if e.status != 1: | |
raise | |
# END handle exception | |
return self | |
class Head(Reference): | |
"""A Head is a named reference to a :class:`~git.objects.commit.Commit`. Every Head | |
instance contains a name and a :class:`~git.objects.commit.Commit` object. | |
Examples:: | |
>>> repo = Repo("/path/to/repo") | |
>>> head = repo.heads[0] | |
>>> head.name | |
'master' | |
>>> head.commit | |
<git.Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455"> | |
>>> head.commit.hexsha | |
'1c09f116cbc2cb4100fb6935bb162daa4723f455' | |
""" | |
_common_path_default = "refs/heads" | |
k_config_remote = "remote" | |
k_config_remote_ref = "merge" # Branch to merge from remote. | |
def delete(cls, repo: "Repo", *heads: "Union[Head, str]", force: bool = False, **kwargs: Any) -> None: | |
"""Delete the given heads. | |
:param force: | |
If ``True``, the heads will be deleted even if they are not yet merged into | |
the main development stream. Default ``False``. | |
""" | |
flag = "-d" | |
if force: | |
flag = "-D" | |
repo.git.branch(flag, *heads) | |
def set_tracking_branch(self, remote_reference: Union["RemoteReference", None]) -> "Head": | |
"""Configure this branch to track the given remote reference. This will | |
alter this branch's configuration accordingly. | |
:param remote_reference: | |
The remote reference to track or None to untrack any references. | |
:return: | |
self | |
""" | |
from .remote import RemoteReference | |
if remote_reference is not None and not isinstance(remote_reference, RemoteReference): | |
raise ValueError("Incorrect parameter type: %r" % remote_reference) | |
# END handle type | |
with self.config_writer() as writer: | |
if remote_reference is None: | |
writer.remove_option(self.k_config_remote) | |
writer.remove_option(self.k_config_remote_ref) | |
if len(writer.options()) == 0: | |
writer.remove_section() | |
else: | |
writer.set_value(self.k_config_remote, remote_reference.remote_name) | |
writer.set_value( | |
self.k_config_remote_ref, | |
Head.to_full_path(remote_reference.remote_head), | |
) | |
return self | |
def tracking_branch(self) -> Union["RemoteReference", None]: | |
""" | |
:return: | |
The remote reference we are tracking, or ``None`` if we are not a tracking | |
branch. | |
""" | |
from .remote import RemoteReference | |
reader = self.config_reader() | |
if reader.has_option(self.k_config_remote) and reader.has_option(self.k_config_remote_ref): | |
ref = Head( | |
self.repo, | |
Head.to_full_path(strip_quotes(reader.get_value(self.k_config_remote_ref))), | |
) | |
remote_refpath = RemoteReference.to_full_path(join_path(reader.get_value(self.k_config_remote), ref.name)) | |
return RemoteReference(self.repo, remote_refpath) | |
# END handle have tracking branch | |
# We are not a tracking branch. | |
return None | |
def rename(self, new_path: PathLike, force: bool = False) -> "Head": | |
"""Rename self to a new path. | |
:param new_path: | |
Either a simple name or a path, e.g. ``new_name`` or ``features/new_name``. | |
The prefix ``refs/heads`` is implied. | |
:param force: | |
If ``True``, the rename will succeed even if a head with the target name | |
already exists. | |
:return: | |
self | |
:note: | |
Respects the ref log, as git commands are used. | |
""" | |
flag = "-m" | |
if force: | |
flag = "-M" | |
self.repo.git.branch(flag, self, new_path) | |
self.path = "%s/%s" % (self._common_path_default, new_path) | |
return self | |
def checkout(self, force: bool = False, **kwargs: Any) -> Union["HEAD", "Head"]: | |
"""Check out this head by setting the HEAD to this reference, by updating the | |
index to reflect the tree we point to and by updating the working tree to | |
reflect the latest index. | |
The command will fail if changed working tree files would be overwritten. | |
:param force: | |
If ``True``, changes to the index and the working tree will be discarded. | |
If ``False``, :exc:`~git.exc.GitCommandError` will be raised in that | |
situation. | |
:param kwargs: | |
Additional keyword arguments to be passed to git checkout, e.g. | |
``b="new_branch"`` to create a new branch at the given spot. | |
:return: | |
The active branch after the checkout operation, usually self unless a new | |
branch has been created. | |
If there is no active branch, as the HEAD is now detached, the HEAD | |
reference will be returned instead. | |
:note: | |
By default it is only allowed to checkout heads - everything else will leave | |
the HEAD detached which is allowed and possible, but remains a special state | |
that some tools might not be able to handle. | |
""" | |
kwargs["f"] = force | |
if kwargs["f"] is False: | |
kwargs.pop("f") | |
self.repo.git.checkout(self, **kwargs) | |
if self.repo.head.is_detached: | |
return self.repo.head | |
else: | |
return self.repo.active_branch | |
# { Configuration | |
def _config_parser(self, read_only: bool) -> SectionConstraint[GitConfigParser]: | |
if read_only: | |
parser = self.repo.config_reader() | |
else: | |
parser = self.repo.config_writer() | |
# END handle parser instance | |
return SectionConstraint(parser, 'branch "%s"' % self.name) | |
def config_reader(self) -> SectionConstraint[GitConfigParser]: | |
""" | |
:return: | |
A configuration parser instance constrained to only read this instance's | |
values. | |
""" | |
return self._config_parser(read_only=True) | |
def config_writer(self) -> SectionConstraint[GitConfigParser]: | |
""" | |
:return: | |
A configuration writer instance with read-and write access to options of | |
this head. | |
""" | |
return self._config_parser(read_only=False) | |
# } END configuration | |