| | |
| | |
| |
|
| | """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 |
| |
|
| | |
| |
|
| | 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__ = () |
| |
|
| | |
| | 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" |
| |
|
| | |
| | |
| | if paths: |
| | mode = None |
| | |
| | |
| |
|
| | if working_tree: |
| | mode = "--hard" |
| | if not index: |
| | raise ValueError("Cannot reset the working tree if the index is not reset as well") |
| |
|
| | |
| |
|
| | try: |
| | self.repo.git.reset(mode, commit, "--", paths, **kwargs) |
| | except GitCommandError as e: |
| | |
| | |
| | if e.status != 1: |
| | raise |
| | |
| |
|
| | 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" |
| |
|
| | @classmethod |
| | 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) |
| | |
| |
|
| | 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) |
| | |
| |
|
| | |
| | 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 |
| |
|
| | |
| | def _config_parser(self, read_only: bool) -> SectionConstraint[GitConfigParser]: |
| | if read_only: |
| | parser = self.repo.config_reader() |
| | else: |
| | parser = self.repo.config_writer() |
| | |
| |
|
| | 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) |
| |
|
| | |
| |
|