| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- # 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.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__ = ()
- 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"
- # Explicit "--mixed" when passing paths is deprecated since git 1.5.4.
- # See https://github.com/gitpython-developers/GitPython/discussions/1876.
- 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.
- @classmethod
- def delete(cls, repo: "Repo", *heads: "Union[Head, str]", force: bool = False, **kwargs: Any) -> None: # type: ignore[override]
- """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
|