upload
This commit is contained in:
		@@ -0,0 +1,9 @@
 | 
			
		||||
# flake8: noqa
 | 
			
		||||
# import all modules in order, fix the names they require
 | 
			
		||||
from .symbolic import *
 | 
			
		||||
from .reference import *
 | 
			
		||||
from .head import *
 | 
			
		||||
from .tag import *
 | 
			
		||||
from .remote import *
 | 
			
		||||
 | 
			
		||||
from .log import *
 | 
			
		||||
							
								
								
									
										277
									
								
								zero-cost-nas/.eggs/GitPython-3.1.31-py3.8.egg/git/refs/head.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										277
									
								
								zero-cost-nas/.eggs/GitPython-3.1.31-py3.8.egg/git/refs/head.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,277 @@
 | 
			
		||||
from git.config import GitConfigParser, SectionConstraint
 | 
			
		||||
from git.util import join_path
 | 
			
		||||
from git.exc import GitCommandError
 | 
			
		||||
 | 
			
		||||
from .symbolic import SymbolicReference
 | 
			
		||||
from .reference import Reference
 | 
			
		||||
 | 
			
		||||
# typinng ---------------------------------------------------
 | 
			
		||||
 | 
			
		||||
from typing import Any, Sequence, Union, TYPE_CHECKING
 | 
			
		||||
 | 
			
		||||
from git.types import PathLike, Commit_ish
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
    from git.repo import Repo
 | 
			
		||||
    from git.objects import Commit
 | 
			
		||||
    from git.refs import RemoteReference
 | 
			
		||||
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
__all__ = ["HEAD", "Head"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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 Symbolic Reference as it represents the repository's
 | 
			
		||||
    HEAD reference."""
 | 
			
		||||
 | 
			
		||||
    _HEAD_NAME = "HEAD"
 | 
			
		||||
    _ORIG_HEAD_NAME = "ORIG_HEAD"
 | 
			
		||||
    __slots__ = ()
 | 
			
		||||
 | 
			
		||||
    def __init__(self, repo: "Repo", path: PathLike = _HEAD_NAME):
 | 
			
		||||
        if path != self._HEAD_NAME:
 | 
			
		||||
            raise ValueError("HEAD instance must point to %r, got %r" % (self._HEAD_NAME, path))
 | 
			
		||||
        super(HEAD, self).__init__(repo, path)
 | 
			
		||||
        self.commit: "Commit"
 | 
			
		||||
 | 
			
		||||
    def orig_head(self) -> SymbolicReference:
 | 
			
		||||
        """
 | 
			
		||||
        :return: 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:
 | 
			
		||||
            Commit object, Reference Object 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 git-reset.
 | 
			
		||||
 | 
			
		||||
        :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 Commit. Every Head instance contains a name
 | 
			
		||||
    and a 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:
 | 
			
		||||
        """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, i.e. 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"]:
 | 
			
		||||
        """Checkout 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, GitCommandError will be raised in that situation.
 | 
			
		||||
 | 
			
		||||
        :param kwargs:
 | 
			
		||||
            Additional keyword arguments to be passed to git checkout, i.e.
 | 
			
		||||
            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
 | 
			
		||||
							
								
								
									
										353
									
								
								zero-cost-nas/.eggs/GitPython-3.1.31-py3.8.egg/git/refs/log.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										353
									
								
								zero-cost-nas/.eggs/GitPython-3.1.31-py3.8.egg/git/refs/log.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,353 @@
 | 
			
		||||
from mmap import mmap
 | 
			
		||||
import re
 | 
			
		||||
import time as _time
 | 
			
		||||
 | 
			
		||||
from git.compat import defenc
 | 
			
		||||
from git.objects.util import (
 | 
			
		||||
    parse_date,
 | 
			
		||||
    Serializable,
 | 
			
		||||
    altz_to_utctz_str,
 | 
			
		||||
)
 | 
			
		||||
from git.util import (
 | 
			
		||||
    Actor,
 | 
			
		||||
    LockedFD,
 | 
			
		||||
    LockFile,
 | 
			
		||||
    assure_directory_exists,
 | 
			
		||||
    to_native_path,
 | 
			
		||||
    bin_to_hex,
 | 
			
		||||
    file_contents_ro_filepath,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
import os.path as osp
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# typing ------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
from typing import Iterator, List, Tuple, Union, TYPE_CHECKING
 | 
			
		||||
 | 
			
		||||
from git.types import PathLike
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
    from git.refs import SymbolicReference
 | 
			
		||||
    from io import BytesIO
 | 
			
		||||
    from git.config import GitConfigParser, SectionConstraint  # NOQA
 | 
			
		||||
 | 
			
		||||
# ------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
__all__ = ["RefLog", "RefLogEntry"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RefLogEntry(Tuple[str, str, Actor, Tuple[int, int], str]):
 | 
			
		||||
 | 
			
		||||
    """Named tuple allowing easy access to the revlog data fields"""
 | 
			
		||||
 | 
			
		||||
    _re_hexsha_only = re.compile("^[0-9A-Fa-f]{40}$")
 | 
			
		||||
    __slots__ = ()
 | 
			
		||||
 | 
			
		||||
    def __repr__(self) -> str:
 | 
			
		||||
        """Representation of ourselves in git reflog format"""
 | 
			
		||||
        return self.format()
 | 
			
		||||
 | 
			
		||||
    def format(self) -> str:
 | 
			
		||||
        """:return: a string suitable to be placed in a reflog file"""
 | 
			
		||||
        act = self.actor
 | 
			
		||||
        time = self.time
 | 
			
		||||
        return "{} {} {} <{}> {!s} {}\t{}\n".format(
 | 
			
		||||
            self.oldhexsha,
 | 
			
		||||
            self.newhexsha,
 | 
			
		||||
            act.name,
 | 
			
		||||
            act.email,
 | 
			
		||||
            time[0],
 | 
			
		||||
            altz_to_utctz_str(time[1]),
 | 
			
		||||
            self.message,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def oldhexsha(self) -> str:
 | 
			
		||||
        """The hexsha to the commit the ref pointed to before the change"""
 | 
			
		||||
        return self[0]
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def newhexsha(self) -> str:
 | 
			
		||||
        """The hexsha to the commit the ref now points to, after the change"""
 | 
			
		||||
        return self[1]
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def actor(self) -> Actor:
 | 
			
		||||
        """Actor instance, providing access"""
 | 
			
		||||
        return self[2]
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def time(self) -> Tuple[int, int]:
 | 
			
		||||
        """time as tuple:
 | 
			
		||||
 | 
			
		||||
        * [0] = int(time)
 | 
			
		||||
        * [1] = int(timezone_offset) in time.altzone format"""
 | 
			
		||||
        return self[3]
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def message(self) -> str:
 | 
			
		||||
        """Message describing the operation that acted on the reference"""
 | 
			
		||||
        return self[4]
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def new(
 | 
			
		||||
        cls,
 | 
			
		||||
        oldhexsha: str,
 | 
			
		||||
        newhexsha: str,
 | 
			
		||||
        actor: Actor,
 | 
			
		||||
        time: int,
 | 
			
		||||
        tz_offset: int,
 | 
			
		||||
        message: str,
 | 
			
		||||
    ) -> "RefLogEntry":  # skipcq: PYL-W0621
 | 
			
		||||
        """:return: New instance of a RefLogEntry"""
 | 
			
		||||
        if not isinstance(actor, Actor):
 | 
			
		||||
            raise ValueError("Need actor instance, got %s" % actor)
 | 
			
		||||
        # END check types
 | 
			
		||||
        return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), message))
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def from_line(cls, line: bytes) -> "RefLogEntry":
 | 
			
		||||
        """:return: New RefLogEntry instance from the given revlog line.
 | 
			
		||||
        :param line: line bytes without trailing newline
 | 
			
		||||
        :raise ValueError: If line could not be parsed"""
 | 
			
		||||
        line_str = line.decode(defenc)
 | 
			
		||||
        fields = line_str.split("\t", 1)
 | 
			
		||||
        if len(fields) == 1:
 | 
			
		||||
            info, msg = fields[0], None
 | 
			
		||||
        elif len(fields) == 2:
 | 
			
		||||
            info, msg = fields
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValueError("Line must have up to two TAB-separated fields." " Got %s" % repr(line_str))
 | 
			
		||||
        # END handle first split
 | 
			
		||||
 | 
			
		||||
        oldhexsha = info[:40]
 | 
			
		||||
        newhexsha = info[41:81]
 | 
			
		||||
        for hexsha in (oldhexsha, newhexsha):
 | 
			
		||||
            if not cls._re_hexsha_only.match(hexsha):
 | 
			
		||||
                raise ValueError("Invalid hexsha: %r" % (hexsha,))
 | 
			
		||||
            # END if hexsha re doesn't match
 | 
			
		||||
        # END for each hexsha
 | 
			
		||||
 | 
			
		||||
        email_end = info.find(">", 82)
 | 
			
		||||
        if email_end == -1:
 | 
			
		||||
            raise ValueError("Missing token: >")
 | 
			
		||||
        # END handle missing end brace
 | 
			
		||||
 | 
			
		||||
        actor = Actor._from_string(info[82 : email_end + 1])
 | 
			
		||||
        time, tz_offset = parse_date(info[email_end + 2 :])  # skipcq: PYL-W0621
 | 
			
		||||
 | 
			
		||||
        return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), msg))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RefLog(List[RefLogEntry], Serializable):
 | 
			
		||||
 | 
			
		||||
    """A reflog contains RefLogEntrys, each of which defines a certain state
 | 
			
		||||
    of the head in question. Custom query methods allow to retrieve log entries
 | 
			
		||||
    by date or by other criteria.
 | 
			
		||||
 | 
			
		||||
    Reflog entries are ordered, the first added entry is first in the list, the last
 | 
			
		||||
    entry, i.e. the last change of the head or reference, is last in the list."""
 | 
			
		||||
 | 
			
		||||
    __slots__ = ("_path",)
 | 
			
		||||
 | 
			
		||||
    def __new__(cls, filepath: Union[PathLike, None] = None) -> "RefLog":
 | 
			
		||||
        inst = super(RefLog, cls).__new__(cls)
 | 
			
		||||
        return inst
 | 
			
		||||
 | 
			
		||||
    def __init__(self, filepath: Union[PathLike, None] = None):
 | 
			
		||||
        """Initialize this instance with an optional filepath, from which we will
 | 
			
		||||
        initialize our data. The path is also used to write changes back using
 | 
			
		||||
        the write() method"""
 | 
			
		||||
        self._path = filepath
 | 
			
		||||
        if filepath is not None:
 | 
			
		||||
            self._read_from_file()
 | 
			
		||||
        # END handle filepath
 | 
			
		||||
 | 
			
		||||
    def _read_from_file(self) -> None:
 | 
			
		||||
        try:
 | 
			
		||||
            fmap = file_contents_ro_filepath(self._path, stream=True, allow_mmap=True)
 | 
			
		||||
        except OSError:
 | 
			
		||||
            # it is possible and allowed that the file doesn't exist !
 | 
			
		||||
            return
 | 
			
		||||
        # END handle invalid log
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._deserialize(fmap)
 | 
			
		||||
        finally:
 | 
			
		||||
            fmap.close()
 | 
			
		||||
        # END handle closing of handle
 | 
			
		||||
 | 
			
		||||
    # { Interface
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def from_file(cls, filepath: PathLike) -> "RefLog":
 | 
			
		||||
        """
 | 
			
		||||
        :return: a new RefLog instance containing all entries from the reflog
 | 
			
		||||
            at the given filepath
 | 
			
		||||
        :param filepath: path to reflog
 | 
			
		||||
        :raise ValueError: If the file could not be read or was corrupted in some way"""
 | 
			
		||||
        return cls(filepath)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def path(cls, ref: "SymbolicReference") -> str:
 | 
			
		||||
        """
 | 
			
		||||
        :return: string to absolute path at which the reflog of the given ref
 | 
			
		||||
            instance would be found. The path is not guaranteed to point to a valid
 | 
			
		||||
            file though.
 | 
			
		||||
        :param ref: SymbolicReference instance"""
 | 
			
		||||
        return osp.join(ref.repo.git_dir, "logs", to_native_path(ref.path))
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def iter_entries(cls, stream: Union[str, "BytesIO", mmap]) -> Iterator[RefLogEntry]:
 | 
			
		||||
        """
 | 
			
		||||
        :return: Iterator yielding RefLogEntry instances, one for each line read
 | 
			
		||||
            sfrom the given stream.
 | 
			
		||||
        :param stream: file-like object containing the revlog in its native format
 | 
			
		||||
            or string instance pointing to a file to read"""
 | 
			
		||||
        new_entry = RefLogEntry.from_line
 | 
			
		||||
        if isinstance(stream, str):
 | 
			
		||||
            # default args return mmap on py>3
 | 
			
		||||
            _stream = file_contents_ro_filepath(stream)
 | 
			
		||||
            assert isinstance(_stream, mmap)
 | 
			
		||||
        else:
 | 
			
		||||
            _stream = stream
 | 
			
		||||
        # END handle stream type
 | 
			
		||||
        while True:
 | 
			
		||||
            line = _stream.readline()
 | 
			
		||||
            if not line:
 | 
			
		||||
                return
 | 
			
		||||
            yield new_entry(line.strip())
 | 
			
		||||
        # END endless loop
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def entry_at(cls, filepath: PathLike, index: int) -> "RefLogEntry":
 | 
			
		||||
        """
 | 
			
		||||
        :return: RefLogEntry at the given index
 | 
			
		||||
 | 
			
		||||
        :param filepath: full path to the index file from which to read the entry
 | 
			
		||||
 | 
			
		||||
        :param index: python list compatible index, i.e. it may be negative to
 | 
			
		||||
            specify an entry counted from the end of the list
 | 
			
		||||
 | 
			
		||||
        :raise IndexError: If the entry didn't exist
 | 
			
		||||
 | 
			
		||||
        .. note:: This method is faster as it only parses the entry at index, skipping
 | 
			
		||||
            all other lines. Nonetheless, the whole file has to be read if
 | 
			
		||||
            the index is negative
 | 
			
		||||
        """
 | 
			
		||||
        with open(filepath, "rb") as fp:
 | 
			
		||||
            if index < 0:
 | 
			
		||||
                return RefLogEntry.from_line(fp.readlines()[index].strip())
 | 
			
		||||
            # read until index is reached
 | 
			
		||||
 | 
			
		||||
            for i in range(index + 1):
 | 
			
		||||
                line = fp.readline()
 | 
			
		||||
                if not line:
 | 
			
		||||
                    raise IndexError(f"Index file ended at line {i+1}, before given index was reached")
 | 
			
		||||
                # END abort on eof
 | 
			
		||||
            # END handle runup
 | 
			
		||||
 | 
			
		||||
            return RefLogEntry.from_line(line.strip())
 | 
			
		||||
        # END handle index
 | 
			
		||||
 | 
			
		||||
    def to_file(self, filepath: PathLike) -> None:
 | 
			
		||||
        """Write the contents of the reflog instance to a file at the given filepath.
 | 
			
		||||
 | 
			
		||||
        :param filepath: path to file, parent directories are assumed to exist"""
 | 
			
		||||
        lfd = LockedFD(filepath)
 | 
			
		||||
        assure_directory_exists(filepath, is_file=True)
 | 
			
		||||
 | 
			
		||||
        fp = lfd.open(write=True, stream=True)
 | 
			
		||||
        try:
 | 
			
		||||
            self._serialize(fp)
 | 
			
		||||
            lfd.commit()
 | 
			
		||||
        except Exception:
 | 
			
		||||
            # on failure it rolls back automatically, but we make it clear
 | 
			
		||||
            lfd.rollback()
 | 
			
		||||
            raise
 | 
			
		||||
        # END handle change
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def append_entry(
 | 
			
		||||
        cls,
 | 
			
		||||
        config_reader: Union[Actor, "GitConfigParser", "SectionConstraint", None],
 | 
			
		||||
        filepath: PathLike,
 | 
			
		||||
        oldbinsha: bytes,
 | 
			
		||||
        newbinsha: bytes,
 | 
			
		||||
        message: str,
 | 
			
		||||
        write: bool = True,
 | 
			
		||||
    ) -> "RefLogEntry":
 | 
			
		||||
        """Append a new log entry to the revlog at filepath.
 | 
			
		||||
 | 
			
		||||
        :param config_reader: configuration reader of the repository - used to obtain
 | 
			
		||||
            user information. May also be an Actor instance identifying the committer directly or None.
 | 
			
		||||
        :param filepath: full path to the log file
 | 
			
		||||
        :param oldbinsha: binary sha of the previous commit
 | 
			
		||||
        :param newbinsha: binary sha of the current commit
 | 
			
		||||
        :param message: message describing the change to the reference
 | 
			
		||||
        :param write: If True, the changes will be written right away. Otherwise
 | 
			
		||||
            the change will not be written
 | 
			
		||||
 | 
			
		||||
        :return: RefLogEntry objects which was appended to the log
 | 
			
		||||
 | 
			
		||||
        :note: As we are append-only, concurrent access is not a problem as we
 | 
			
		||||
            do not interfere with readers."""
 | 
			
		||||
 | 
			
		||||
        if len(oldbinsha) != 20 or len(newbinsha) != 20:
 | 
			
		||||
            raise ValueError("Shas need to be given in binary format")
 | 
			
		||||
        # END handle sha type
 | 
			
		||||
        assure_directory_exists(filepath, is_file=True)
 | 
			
		||||
        first_line = message.split("\n")[0]
 | 
			
		||||
        if isinstance(config_reader, Actor):
 | 
			
		||||
            committer = config_reader  # mypy thinks this is Actor | Gitconfigparser, but why?
 | 
			
		||||
        else:
 | 
			
		||||
            committer = Actor.committer(config_reader)
 | 
			
		||||
        entry = RefLogEntry(
 | 
			
		||||
            (
 | 
			
		||||
                bin_to_hex(oldbinsha).decode("ascii"),
 | 
			
		||||
                bin_to_hex(newbinsha).decode("ascii"),
 | 
			
		||||
                committer,
 | 
			
		||||
                (int(_time.time()), _time.altzone),
 | 
			
		||||
                first_line,
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if write:
 | 
			
		||||
            lf = LockFile(filepath)
 | 
			
		||||
            lf._obtain_lock_or_raise()
 | 
			
		||||
            fd = open(filepath, "ab")
 | 
			
		||||
            try:
 | 
			
		||||
                fd.write(entry.format().encode(defenc))
 | 
			
		||||
            finally:
 | 
			
		||||
                fd.close()
 | 
			
		||||
                lf._release_lock()
 | 
			
		||||
            # END handle write operation
 | 
			
		||||
        return entry
 | 
			
		||||
 | 
			
		||||
    def write(self) -> "RefLog":
 | 
			
		||||
        """Write this instance's data to the file we are originating from
 | 
			
		||||
 | 
			
		||||
        :return: self"""
 | 
			
		||||
        if self._path is None:
 | 
			
		||||
            raise ValueError("Instance was not initialized with a path, use to_file(...) instead")
 | 
			
		||||
        # END assert path
 | 
			
		||||
        self.to_file(self._path)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    # } END interface
 | 
			
		||||
 | 
			
		||||
    # { Serializable Interface
 | 
			
		||||
    def _serialize(self, stream: "BytesIO") -> "RefLog":
 | 
			
		||||
        write = stream.write
 | 
			
		||||
 | 
			
		||||
        # write all entries
 | 
			
		||||
        for e in self:
 | 
			
		||||
            write(e.format().encode(defenc))
 | 
			
		||||
        # END for each entry
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def _deserialize(self, stream: "BytesIO") -> "RefLog":
 | 
			
		||||
        self.extend(self.iter_entries(stream))
 | 
			
		||||
        # } END serializable interface
 | 
			
		||||
        return self
 | 
			
		||||
@@ -0,0 +1,154 @@
 | 
			
		||||
from git.util import (
 | 
			
		||||
    LazyMixin,
 | 
			
		||||
    IterableObj,
 | 
			
		||||
)
 | 
			
		||||
from .symbolic import SymbolicReference, T_References
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# typing ------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
from typing import Any, Callable, Iterator, Type, Union, TYPE_CHECKING  # NOQA
 | 
			
		||||
from git.types import Commit_ish, PathLike, _T  # NOQA
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
    from git.repo import Repo
 | 
			
		||||
 | 
			
		||||
# ------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__all__ = ["Reference"]
 | 
			
		||||
 | 
			
		||||
# { Utilities
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def require_remote_ref_path(func: Callable[..., _T]) -> Callable[..., _T]:
 | 
			
		||||
    """A decorator raising a TypeError if we are not a valid remote, based on the path"""
 | 
			
		||||
 | 
			
		||||
    def wrapper(self: T_References, *args: Any) -> _T:
 | 
			
		||||
        if not self.is_remote():
 | 
			
		||||
            raise ValueError("ref path does not point to a remote reference: %s" % self.path)
 | 
			
		||||
        return func(self, *args)
 | 
			
		||||
 | 
			
		||||
    # END wrapper
 | 
			
		||||
    wrapper.__name__ = func.__name__
 | 
			
		||||
    return wrapper
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# }END utilities
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Reference(SymbolicReference, LazyMixin, IterableObj):
 | 
			
		||||
 | 
			
		||||
    """Represents a named reference to any object. Subclasses may apply restrictions though,
 | 
			
		||||
    i.e. Heads can only point to commits."""
 | 
			
		||||
 | 
			
		||||
    __slots__ = ()
 | 
			
		||||
    _points_to_commits_only = False
 | 
			
		||||
    _resolve_ref_on_create = True
 | 
			
		||||
    _common_path_default = "refs"
 | 
			
		||||
 | 
			
		||||
    def __init__(self, repo: "Repo", path: PathLike, check_path: bool = True) -> None:
 | 
			
		||||
        """Initialize this instance
 | 
			
		||||
 | 
			
		||||
        :param repo: Our parent repository
 | 
			
		||||
        :param path:
 | 
			
		||||
            Path relative to the .git/ directory pointing to the ref in question, i.e.
 | 
			
		||||
            refs/heads/master
 | 
			
		||||
        :param check_path: if False, you can provide any path. Otherwise the path must start with the
 | 
			
		||||
            default path prefix of this type."""
 | 
			
		||||
        if check_path and not str(path).startswith(self._common_path_default + "/"):
 | 
			
		||||
            raise ValueError(f"Cannot instantiate {self.__class__.__name__!r} from path {path}")
 | 
			
		||||
        self.path: str  # SymbolicReference converts to string atm
 | 
			
		||||
        super(Reference, self).__init__(repo, path)
 | 
			
		||||
 | 
			
		||||
    def __str__(self) -> str:
 | 
			
		||||
        return self.name
 | 
			
		||||
 | 
			
		||||
    # { Interface
 | 
			
		||||
 | 
			
		||||
    # @ReservedAssignment
 | 
			
		||||
    def set_object(
 | 
			
		||||
        self,
 | 
			
		||||
        object: Union[Commit_ish, "SymbolicReference", str],
 | 
			
		||||
        logmsg: Union[str, None] = None,
 | 
			
		||||
    ) -> "Reference":
 | 
			
		||||
        """Special version which checks if the head-log needs an update as well
 | 
			
		||||
 | 
			
		||||
        :return: self"""
 | 
			
		||||
        oldbinsha = None
 | 
			
		||||
        if logmsg is not None:
 | 
			
		||||
            head = self.repo.head
 | 
			
		||||
            if not head.is_detached and head.ref == self:
 | 
			
		||||
                oldbinsha = self.commit.binsha
 | 
			
		||||
            # END handle commit retrieval
 | 
			
		||||
        # END handle message is set
 | 
			
		||||
 | 
			
		||||
        super(Reference, self).set_object(object, logmsg)
 | 
			
		||||
 | 
			
		||||
        if oldbinsha is not None:
 | 
			
		||||
            # /* from refs.c in git-source
 | 
			
		||||
            # * Special hack: If a branch is updated directly and HEAD
 | 
			
		||||
            # * points to it (may happen on the remote side of a push
 | 
			
		||||
            # * for example) then logically the HEAD reflog should be
 | 
			
		||||
            # * updated too.
 | 
			
		||||
            # * A generic solution implies reverse symref information,
 | 
			
		||||
            # * but finding all symrefs pointing to the given branch
 | 
			
		||||
            # * would be rather costly for this rare event (the direct
 | 
			
		||||
            # * update of a branch) to be worth it.  So let's cheat and
 | 
			
		||||
            # * check with HEAD only which should cover 99% of all usage
 | 
			
		||||
            # * scenarios (even 100% of the default ones).
 | 
			
		||||
            # */
 | 
			
		||||
            self.repo.head.log_append(oldbinsha, logmsg)
 | 
			
		||||
        # END check if the head
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    # NOTE: Don't have to overwrite properties as the will only work without a the log
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def name(self) -> str:
 | 
			
		||||
        """:return: (shortest) Name of this reference - it may contain path components"""
 | 
			
		||||
        # first two path tokens are can be removed as they are
 | 
			
		||||
        # refs/heads or refs/tags or refs/remotes
 | 
			
		||||
        tokens = self.path.split("/")
 | 
			
		||||
        if len(tokens) < 3:
 | 
			
		||||
            return self.path  # could be refs/HEAD
 | 
			
		||||
        return "/".join(tokens[2:])
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def iter_items(
 | 
			
		||||
        cls: Type[T_References],
 | 
			
		||||
        repo: "Repo",
 | 
			
		||||
        common_path: Union[PathLike, None] = None,
 | 
			
		||||
        *args: Any,
 | 
			
		||||
        **kwargs: Any,
 | 
			
		||||
    ) -> Iterator[T_References]:
 | 
			
		||||
        """Equivalent to SymbolicReference.iter_items, but will return non-detached
 | 
			
		||||
        references as well."""
 | 
			
		||||
        return cls._iter_items(repo, common_path)
 | 
			
		||||
 | 
			
		||||
    # }END interface
 | 
			
		||||
 | 
			
		||||
    # { Remote Interface
 | 
			
		||||
 | 
			
		||||
    @property  # type: ignore ## mypy cannot deal with properties with an extra decorator (2021-04-21)
 | 
			
		||||
    @require_remote_ref_path
 | 
			
		||||
    def remote_name(self) -> str:
 | 
			
		||||
        """
 | 
			
		||||
        :return:
 | 
			
		||||
            Name of the remote we are a reference of, such as 'origin' for a reference
 | 
			
		||||
            named 'origin/master'"""
 | 
			
		||||
        tokens = self.path.split("/")
 | 
			
		||||
        # /refs/remotes/<remote name>/<branch_name>
 | 
			
		||||
        return tokens[2]
 | 
			
		||||
 | 
			
		||||
    @property  # type: ignore ## mypy cannot deal with properties with an extra decorator (2021-04-21)
 | 
			
		||||
    @require_remote_ref_path
 | 
			
		||||
    def remote_head(self) -> str:
 | 
			
		||||
        """:return: Name of the remote head itself, i.e. master.
 | 
			
		||||
        :note: The returned name is usually not qualified enough to uniquely identify
 | 
			
		||||
            a branch"""
 | 
			
		||||
        tokens = self.path.split("/")
 | 
			
		||||
        return "/".join(tokens[3:])
 | 
			
		||||
 | 
			
		||||
    # } END remote interface
 | 
			
		||||
@@ -0,0 +1,75 @@
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from git.util import join_path
 | 
			
		||||
 | 
			
		||||
from .head import Head
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__all__ = ["RemoteReference"]
 | 
			
		||||
 | 
			
		||||
# typing ------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
from typing import Any, Iterator, NoReturn, Union, TYPE_CHECKING
 | 
			
		||||
from git.types import PathLike
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
    from git.repo import Repo
 | 
			
		||||
    from git import Remote
 | 
			
		||||
 | 
			
		||||
# ------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RemoteReference(Head):
 | 
			
		||||
 | 
			
		||||
    """Represents a reference pointing to a remote head."""
 | 
			
		||||
 | 
			
		||||
    _common_path_default = Head._remote_common_path_default
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def iter_items(
 | 
			
		||||
        cls,
 | 
			
		||||
        repo: "Repo",
 | 
			
		||||
        common_path: Union[PathLike, None] = None,
 | 
			
		||||
        remote: Union["Remote", None] = None,
 | 
			
		||||
        *args: Any,
 | 
			
		||||
        **kwargs: Any,
 | 
			
		||||
    ) -> Iterator["RemoteReference"]:
 | 
			
		||||
        """Iterate remote references, and if given, constrain them to the given remote"""
 | 
			
		||||
        common_path = common_path or cls._common_path_default
 | 
			
		||||
        if remote is not None:
 | 
			
		||||
            common_path = join_path(common_path, str(remote))
 | 
			
		||||
        # END handle remote constraint
 | 
			
		||||
        # super is Reference
 | 
			
		||||
        return super(RemoteReference, cls).iter_items(repo, common_path)
 | 
			
		||||
 | 
			
		||||
    # The Head implementation of delete also accepts strs, but this
 | 
			
		||||
    # implementation does not.  mypy doesn't have a way of representing
 | 
			
		||||
    # tightening the types of arguments in subclasses and recommends Any or
 | 
			
		||||
    # "type: ignore".  (See https://github.com/python/typing/issues/241)
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def delete(cls, repo: "Repo", *refs: "RemoteReference", **kwargs: Any) -> None:  # type: ignore
 | 
			
		||||
        """Delete the given remote references
 | 
			
		||||
 | 
			
		||||
        :note:
 | 
			
		||||
            kwargs are given for comparability with the base class method as we
 | 
			
		||||
            should not narrow the signature."""
 | 
			
		||||
        repo.git.branch("-d", "-r", *refs)
 | 
			
		||||
        # the official deletion method will ignore remote symbolic refs - these
 | 
			
		||||
        # are generally ignored in the refs/ folder. We don't though
 | 
			
		||||
        # and delete remainders manually
 | 
			
		||||
        for ref in refs:
 | 
			
		||||
            try:
 | 
			
		||||
                os.remove(os.path.join(repo.common_dir, ref.path))
 | 
			
		||||
            except OSError:
 | 
			
		||||
                pass
 | 
			
		||||
            try:
 | 
			
		||||
                os.remove(os.path.join(repo.git_dir, ref.path))
 | 
			
		||||
            except OSError:
 | 
			
		||||
                pass
 | 
			
		||||
        # END for each ref
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create(cls, *args: Any, **kwargs: Any) -> NoReturn:
 | 
			
		||||
        """Used to disable this method"""
 | 
			
		||||
        raise TypeError("Cannot explicitly create remote references")
 | 
			
		||||
@@ -0,0 +1,767 @@
 | 
			
		||||
from git.types import PathLike
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from git.compat import defenc
 | 
			
		||||
from git.objects import Object
 | 
			
		||||
from git.objects.commit import Commit
 | 
			
		||||
from git.util import (
 | 
			
		||||
    join_path,
 | 
			
		||||
    join_path_native,
 | 
			
		||||
    to_native_path_linux,
 | 
			
		||||
    assure_directory_exists,
 | 
			
		||||
    hex_to_bin,
 | 
			
		||||
    LockedFD,
 | 
			
		||||
)
 | 
			
		||||
from gitdb.exc import BadObject, BadName
 | 
			
		||||
 | 
			
		||||
from .log import RefLog
 | 
			
		||||
 | 
			
		||||
# typing ------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
from typing import (
 | 
			
		||||
    Any,
 | 
			
		||||
    Iterator,
 | 
			
		||||
    List,
 | 
			
		||||
    Tuple,
 | 
			
		||||
    Type,
 | 
			
		||||
    TypeVar,
 | 
			
		||||
    Union,
 | 
			
		||||
    TYPE_CHECKING,
 | 
			
		||||
    cast,
 | 
			
		||||
)  # NOQA
 | 
			
		||||
from git.types import Commit_ish, PathLike  # NOQA
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
    from git.repo import Repo
 | 
			
		||||
    from git.refs import Head, TagReference, RemoteReference, Reference
 | 
			
		||||
    from .log import RefLogEntry
 | 
			
		||||
    from git.config import GitConfigParser
 | 
			
		||||
    from git.objects.commit import Actor
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
T_References = TypeVar("T_References", bound="SymbolicReference")
 | 
			
		||||
 | 
			
		||||
# ------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__all__ = ["SymbolicReference"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _git_dir(repo: "Repo", path: Union[PathLike, None]) -> PathLike:
 | 
			
		||||
    """Find the git dir that's appropriate for the path"""
 | 
			
		||||
    name = f"{path}"
 | 
			
		||||
    if name in ["HEAD", "ORIG_HEAD", "FETCH_HEAD", "index", "logs"]:
 | 
			
		||||
        return repo.git_dir
 | 
			
		||||
    return repo.common_dir
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SymbolicReference(object):
 | 
			
		||||
 | 
			
		||||
    """Represents a special case of a reference such that this reference is symbolic.
 | 
			
		||||
    It does not point to a specific commit, but to another Head, which itself
 | 
			
		||||
    specifies a commit.
 | 
			
		||||
 | 
			
		||||
    A typical example for a symbolic reference is HEAD."""
 | 
			
		||||
 | 
			
		||||
    __slots__ = ("repo", "path")
 | 
			
		||||
    _resolve_ref_on_create = False
 | 
			
		||||
    _points_to_commits_only = True
 | 
			
		||||
    _common_path_default = ""
 | 
			
		||||
    _remote_common_path_default = "refs/remotes"
 | 
			
		||||
    _id_attribute_ = "name"
 | 
			
		||||
 | 
			
		||||
    def __init__(self, repo: "Repo", path: PathLike, check_path: bool = False):
 | 
			
		||||
        self.repo = repo
 | 
			
		||||
        self.path = path
 | 
			
		||||
 | 
			
		||||
    def __str__(self) -> str:
 | 
			
		||||
        return str(self.path)
 | 
			
		||||
 | 
			
		||||
    def __repr__(self) -> str:
 | 
			
		||||
        return '<git.%s "%s">' % (self.__class__.__name__, self.path)
 | 
			
		||||
 | 
			
		||||
    def __eq__(self, other: object) -> bool:
 | 
			
		||||
        if hasattr(other, "path"):
 | 
			
		||||
            other = cast(SymbolicReference, other)
 | 
			
		||||
            return self.path == other.path
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def __ne__(self, other: object) -> bool:
 | 
			
		||||
        return not (self == other)
 | 
			
		||||
 | 
			
		||||
    def __hash__(self) -> int:
 | 
			
		||||
        return hash(self.path)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def name(self) -> str:
 | 
			
		||||
        """
 | 
			
		||||
        :return:
 | 
			
		||||
            In case of symbolic references, the shortest assumable name
 | 
			
		||||
            is the path itself."""
 | 
			
		||||
        return str(self.path)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def abspath(self) -> PathLike:
 | 
			
		||||
        return join_path_native(_git_dir(self.repo, self.path), self.path)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def _get_packed_refs_path(cls, repo: "Repo") -> str:
 | 
			
		||||
        return os.path.join(repo.common_dir, "packed-refs")
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def _iter_packed_refs(cls, repo: "Repo") -> Iterator[Tuple[str, str]]:
 | 
			
		||||
        """Returns an iterator yielding pairs of sha1/path pairs (as strings) for the corresponding refs.
 | 
			
		||||
        :note: The packed refs file will be kept open as long as we iterate"""
 | 
			
		||||
        try:
 | 
			
		||||
            with open(cls._get_packed_refs_path(repo), "rt", encoding="UTF-8") as fp:
 | 
			
		||||
                for line in fp:
 | 
			
		||||
                    line = line.strip()
 | 
			
		||||
                    if not line:
 | 
			
		||||
                        continue
 | 
			
		||||
                    if line.startswith("#"):
 | 
			
		||||
                        # "# pack-refs with: peeled fully-peeled sorted"
 | 
			
		||||
                        # the git source code shows "peeled",
 | 
			
		||||
                        # "fully-peeled" and "sorted" as the keywords
 | 
			
		||||
                        # that can go on this line, as per comments in git file
 | 
			
		||||
                        # refs/packed-backend.c
 | 
			
		||||
                        # I looked at master on 2017-10-11,
 | 
			
		||||
                        # commit 111ef79afe, after tag v2.15.0-rc1
 | 
			
		||||
                        # from repo https://github.com/git/git.git
 | 
			
		||||
                        if line.startswith("# pack-refs with:") and "peeled" not in line:
 | 
			
		||||
                            raise TypeError("PackingType of packed-Refs not understood: %r" % line)
 | 
			
		||||
                        # END abort if we do not understand the packing scheme
 | 
			
		||||
                        continue
 | 
			
		||||
                    # END parse comment
 | 
			
		||||
 | 
			
		||||
                    # skip dereferenced tag object entries - previous line was actual
 | 
			
		||||
                    # tag reference for it
 | 
			
		||||
                    if line[0] == "^":
 | 
			
		||||
                        continue
 | 
			
		||||
 | 
			
		||||
                    yield cast(Tuple[str, str], tuple(line.split(" ", 1)))
 | 
			
		||||
                # END for each line
 | 
			
		||||
        except OSError:
 | 
			
		||||
            return None
 | 
			
		||||
        # END no packed-refs file handling
 | 
			
		||||
        # NOTE: Had try-finally block around here to close the fp,
 | 
			
		||||
        # but some python version wouldn't allow yields within that.
 | 
			
		||||
        # I believe files are closing themselves on destruction, so it is
 | 
			
		||||
        # alright.
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def dereference_recursive(cls, repo: "Repo", ref_path: Union[PathLike, None]) -> str:
 | 
			
		||||
        """
 | 
			
		||||
        :return: hexsha stored in the reference at the given ref_path, recursively dereferencing all
 | 
			
		||||
            intermediate references as required
 | 
			
		||||
        :param repo: the repository containing the reference at ref_path"""
 | 
			
		||||
 | 
			
		||||
        while True:
 | 
			
		||||
            hexsha, ref_path = cls._get_ref_info(repo, ref_path)
 | 
			
		||||
            if hexsha is not None:
 | 
			
		||||
                return hexsha
 | 
			
		||||
        # END recursive dereferencing
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def _get_ref_info_helper(
 | 
			
		||||
        cls, repo: "Repo", ref_path: Union[PathLike, None]
 | 
			
		||||
    ) -> Union[Tuple[str, None], Tuple[None, str]]:
 | 
			
		||||
        """Return: (str(sha), str(target_ref_path)) if available, the sha the file at
 | 
			
		||||
        rela_path points to, or None. target_ref_path is the reference we
 | 
			
		||||
        point to, or None"""
 | 
			
		||||
        tokens: Union[None, List[str], Tuple[str, str]] = None
 | 
			
		||||
        repodir = _git_dir(repo, ref_path)
 | 
			
		||||
        try:
 | 
			
		||||
            with open(os.path.join(repodir, str(ref_path)), "rt", encoding="UTF-8") as fp:
 | 
			
		||||
                value = fp.read().rstrip()
 | 
			
		||||
            # Don't only split on spaces, but on whitespace, which allows to parse lines like
 | 
			
		||||
            # 60b64ef992065e2600bfef6187a97f92398a9144                branch 'master' of git-server:/path/to/repo
 | 
			
		||||
            tokens = value.split()
 | 
			
		||||
            assert len(tokens) != 0
 | 
			
		||||
        except OSError:
 | 
			
		||||
            # Probably we are just packed, find our entry in the packed refs file
 | 
			
		||||
            # NOTE: We are not a symbolic ref if we are in a packed file, as these
 | 
			
		||||
            # are excluded explicitly
 | 
			
		||||
            for sha, path in cls._iter_packed_refs(repo):
 | 
			
		||||
                if path != ref_path:
 | 
			
		||||
                    continue
 | 
			
		||||
                # sha will be used
 | 
			
		||||
                tokens = sha, path
 | 
			
		||||
                break
 | 
			
		||||
            # END for each packed ref
 | 
			
		||||
        # END handle packed refs
 | 
			
		||||
        if tokens is None:
 | 
			
		||||
            raise ValueError("Reference at %r does not exist" % ref_path)
 | 
			
		||||
 | 
			
		||||
        # is it a reference ?
 | 
			
		||||
        if tokens[0] == "ref:":
 | 
			
		||||
            return (None, tokens[1])
 | 
			
		||||
 | 
			
		||||
        # its a commit
 | 
			
		||||
        if repo.re_hexsha_only.match(tokens[0]):
 | 
			
		||||
            return (tokens[0], None)
 | 
			
		||||
 | 
			
		||||
        raise ValueError("Failed to parse reference information from %r" % ref_path)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def _get_ref_info(cls, repo: "Repo", ref_path: Union[PathLike, None]) -> Union[Tuple[str, None], Tuple[None, str]]:
 | 
			
		||||
        """Return: (str(sha), str(target_ref_path)) if available, the sha the file at
 | 
			
		||||
        rela_path points to, or None. target_ref_path is the reference we
 | 
			
		||||
        point to, or None"""
 | 
			
		||||
        return cls._get_ref_info_helper(repo, ref_path)
 | 
			
		||||
 | 
			
		||||
    def _get_object(self) -> Commit_ish:
 | 
			
		||||
        """
 | 
			
		||||
        :return:
 | 
			
		||||
            The object our ref currently refers to. Refs can be cached, they will
 | 
			
		||||
            always point to the actual object as it gets re-created on each query"""
 | 
			
		||||
        # have to be dynamic here as we may be a tag which can point to anything
 | 
			
		||||
        # Our path will be resolved to the hexsha which will be used accordingly
 | 
			
		||||
        return Object.new_from_sha(self.repo, hex_to_bin(self.dereference_recursive(self.repo, self.path)))
 | 
			
		||||
 | 
			
		||||
    def _get_commit(self) -> "Commit":
 | 
			
		||||
        """
 | 
			
		||||
        :return:
 | 
			
		||||
            Commit object we point to, works for detached and non-detached
 | 
			
		||||
            SymbolicReferences. The symbolic reference will be dereferenced recursively."""
 | 
			
		||||
        obj = self._get_object()
 | 
			
		||||
        if obj.type == "tag":
 | 
			
		||||
            obj = obj.object
 | 
			
		||||
        # END dereference tag
 | 
			
		||||
 | 
			
		||||
        if obj.type != Commit.type:
 | 
			
		||||
            raise TypeError("Symbolic Reference pointed to object %r, commit was required" % obj)
 | 
			
		||||
        # END handle type
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
    def set_commit(
 | 
			
		||||
        self,
 | 
			
		||||
        commit: Union[Commit, "SymbolicReference", str],
 | 
			
		||||
        logmsg: Union[str, None] = None,
 | 
			
		||||
    ) -> "SymbolicReference":
 | 
			
		||||
        """As set_object, but restricts the type of object to be a Commit
 | 
			
		||||
 | 
			
		||||
        :raise ValueError: If commit is not a Commit object or doesn't point to
 | 
			
		||||
            a commit
 | 
			
		||||
        :return: self"""
 | 
			
		||||
        # check the type - assume the best if it is a base-string
 | 
			
		||||
        invalid_type = False
 | 
			
		||||
        if isinstance(commit, Object):
 | 
			
		||||
            invalid_type = commit.type != Commit.type
 | 
			
		||||
        elif isinstance(commit, SymbolicReference):
 | 
			
		||||
            invalid_type = commit.object.type != Commit.type
 | 
			
		||||
        else:
 | 
			
		||||
            try:
 | 
			
		||||
                invalid_type = self.repo.rev_parse(commit).type != Commit.type
 | 
			
		||||
            except (BadObject, BadName) as e:
 | 
			
		||||
                raise ValueError("Invalid object: %s" % commit) from e
 | 
			
		||||
            # END handle exception
 | 
			
		||||
        # END verify type
 | 
			
		||||
 | 
			
		||||
        if invalid_type:
 | 
			
		||||
            raise ValueError("Need commit, got %r" % commit)
 | 
			
		||||
        # END handle raise
 | 
			
		||||
 | 
			
		||||
        # we leave strings to the rev-parse method below
 | 
			
		||||
        self.set_object(commit, logmsg)
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def set_object(
 | 
			
		||||
        self,
 | 
			
		||||
        object: Union[Commit_ish, "SymbolicReference", str],
 | 
			
		||||
        logmsg: Union[str, None] = None,
 | 
			
		||||
    ) -> "SymbolicReference":
 | 
			
		||||
        """Set the object we point to, possibly dereference our symbolic reference first.
 | 
			
		||||
        If the reference does not exist, it will be created
 | 
			
		||||
 | 
			
		||||
        :param object: a refspec, a SymbolicReference or an Object instance. SymbolicReferences
 | 
			
		||||
            will be dereferenced beforehand to obtain the object they point to
 | 
			
		||||
        :param logmsg: If not None, the message will be used in the reflog entry to be
 | 
			
		||||
            written. Otherwise the reflog is not altered
 | 
			
		||||
        :note: plain SymbolicReferences may not actually point to objects by convention
 | 
			
		||||
        :return: self"""
 | 
			
		||||
        if isinstance(object, SymbolicReference):
 | 
			
		||||
            object = object.object  # @ReservedAssignment
 | 
			
		||||
        # END resolve references
 | 
			
		||||
 | 
			
		||||
        is_detached = True
 | 
			
		||||
        try:
 | 
			
		||||
            is_detached = self.is_detached
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            pass
 | 
			
		||||
        # END handle non-existing ones
 | 
			
		||||
 | 
			
		||||
        if is_detached:
 | 
			
		||||
            return self.set_reference(object, logmsg)
 | 
			
		||||
 | 
			
		||||
        # set the commit on our reference
 | 
			
		||||
        return self._get_reference().set_object(object, logmsg)
 | 
			
		||||
 | 
			
		||||
    commit = property(_get_commit, set_commit, doc="Query or set commits directly")  # type: ignore
 | 
			
		||||
    object = property(_get_object, set_object, doc="Return the object our ref currently refers to")  # type: ignore
 | 
			
		||||
 | 
			
		||||
    def _get_reference(self) -> "SymbolicReference":
 | 
			
		||||
        """:return: Reference Object we point to
 | 
			
		||||
        :raise TypeError: If this symbolic reference is detached, hence it doesn't point
 | 
			
		||||
            to a reference, but to a commit"""
 | 
			
		||||
        sha, target_ref_path = self._get_ref_info(self.repo, self.path)
 | 
			
		||||
        if target_ref_path is None:
 | 
			
		||||
            raise TypeError("%s is a detached symbolic reference as it points to %r" % (self, sha))
 | 
			
		||||
        return self.from_path(self.repo, target_ref_path)
 | 
			
		||||
 | 
			
		||||
    def set_reference(
 | 
			
		||||
        self,
 | 
			
		||||
        ref: Union[Commit_ish, "SymbolicReference", str],
 | 
			
		||||
        logmsg: Union[str, None] = None,
 | 
			
		||||
    ) -> "SymbolicReference":
 | 
			
		||||
        """Set ourselves to the given ref. It will stay a symbol if the ref is a Reference.
 | 
			
		||||
        Otherwise an Object, given as Object instance or refspec, is assumed and if valid,
 | 
			
		||||
        will be set which effectively detaches the reference if it was a purely
 | 
			
		||||
        symbolic one.
 | 
			
		||||
 | 
			
		||||
        :param ref: SymbolicReference instance, Object instance or refspec string
 | 
			
		||||
            Only if the ref is a SymbolicRef instance, we will point to it. Everything
 | 
			
		||||
            else is dereferenced to obtain the actual object.
 | 
			
		||||
        :param logmsg: If set to a string, the message will be used in the reflog.
 | 
			
		||||
            Otherwise, a reflog entry is not written for the changed reference.
 | 
			
		||||
            The previous commit of the entry will be the commit we point to now.
 | 
			
		||||
 | 
			
		||||
            See also: log_append()
 | 
			
		||||
 | 
			
		||||
        :return: self
 | 
			
		||||
        :note: This symbolic reference will not be dereferenced. For that, see
 | 
			
		||||
            ``set_object(...)``"""
 | 
			
		||||
        write_value = None
 | 
			
		||||
        obj = None
 | 
			
		||||
        if isinstance(ref, SymbolicReference):
 | 
			
		||||
            write_value = "ref: %s" % ref.path
 | 
			
		||||
        elif isinstance(ref, Object):
 | 
			
		||||
            obj = ref
 | 
			
		||||
            write_value = ref.hexsha
 | 
			
		||||
        elif isinstance(ref, str):
 | 
			
		||||
            try:
 | 
			
		||||
                obj = self.repo.rev_parse(ref + "^{}")  # optionally deref tags
 | 
			
		||||
                write_value = obj.hexsha
 | 
			
		||||
            except (BadObject, BadName) as e:
 | 
			
		||||
                raise ValueError("Could not extract object from %s" % ref) from e
 | 
			
		||||
            # END end try string
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValueError("Unrecognized Value: %r" % ref)
 | 
			
		||||
        # END try commit attribute
 | 
			
		||||
 | 
			
		||||
        # typecheck
 | 
			
		||||
        if obj is not None and self._points_to_commits_only and obj.type != Commit.type:
 | 
			
		||||
            raise TypeError("Require commit, got %r" % obj)
 | 
			
		||||
        # END verify type
 | 
			
		||||
 | 
			
		||||
        oldbinsha: bytes = b""
 | 
			
		||||
        if logmsg is not None:
 | 
			
		||||
            try:
 | 
			
		||||
                oldbinsha = self.commit.binsha
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                oldbinsha = Commit.NULL_BIN_SHA
 | 
			
		||||
            # END handle non-existing
 | 
			
		||||
        # END retrieve old hexsha
 | 
			
		||||
 | 
			
		||||
        fpath = self.abspath
 | 
			
		||||
        assure_directory_exists(fpath, is_file=True)
 | 
			
		||||
 | 
			
		||||
        lfd = LockedFD(fpath)
 | 
			
		||||
        fd = lfd.open(write=True, stream=True)
 | 
			
		||||
        ok = True
 | 
			
		||||
        try:
 | 
			
		||||
            fd.write(write_value.encode("utf-8") + b"\n")
 | 
			
		||||
            lfd.commit()
 | 
			
		||||
            ok = True
 | 
			
		||||
        finally:
 | 
			
		||||
            if not ok:
 | 
			
		||||
                lfd.rollback()
 | 
			
		||||
        # Adjust the reflog
 | 
			
		||||
        if logmsg is not None:
 | 
			
		||||
            self.log_append(oldbinsha, logmsg)
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    # aliased reference
 | 
			
		||||
    reference: Union["Head", "TagReference", "RemoteReference", "Reference"]
 | 
			
		||||
    reference = property(_get_reference, set_reference, doc="Returns the Reference we point to")  # type: ignore
 | 
			
		||||
    ref = reference
 | 
			
		||||
 | 
			
		||||
    def is_valid(self) -> bool:
 | 
			
		||||
        """
 | 
			
		||||
        :return:
 | 
			
		||||
            True if the reference is valid, hence it can be read and points to
 | 
			
		||||
            a valid object or reference."""
 | 
			
		||||
        try:
 | 
			
		||||
            self.object
 | 
			
		||||
        except (OSError, ValueError):
 | 
			
		||||
            return False
 | 
			
		||||
        else:
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def is_detached(self) -> bool:
 | 
			
		||||
        """
 | 
			
		||||
        :return:
 | 
			
		||||
            True if we are a detached reference, hence we point to a specific commit
 | 
			
		||||
            instead to another reference"""
 | 
			
		||||
        try:
 | 
			
		||||
            self.ref
 | 
			
		||||
            return False
 | 
			
		||||
        except TypeError:
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
    def log(self) -> "RefLog":
 | 
			
		||||
        """
 | 
			
		||||
        :return: RefLog for this reference. Its last entry reflects the latest change
 | 
			
		||||
            applied to this reference
 | 
			
		||||
 | 
			
		||||
        .. note:: As the log is parsed every time, its recommended to cache it for use
 | 
			
		||||
            instead of calling this method repeatedly. It should be considered read-only."""
 | 
			
		||||
        return RefLog.from_file(RefLog.path(self))
 | 
			
		||||
 | 
			
		||||
    def log_append(
 | 
			
		||||
        self,
 | 
			
		||||
        oldbinsha: bytes,
 | 
			
		||||
        message: Union[str, None],
 | 
			
		||||
        newbinsha: Union[bytes, None] = None,
 | 
			
		||||
    ) -> "RefLogEntry":
 | 
			
		||||
        """Append a logentry to the logfile of this ref
 | 
			
		||||
 | 
			
		||||
        :param oldbinsha: binary sha this ref used to point to
 | 
			
		||||
        :param message: A message describing the change
 | 
			
		||||
        :param newbinsha: The sha the ref points to now. If None, our current commit sha
 | 
			
		||||
            will be used
 | 
			
		||||
        :return: added RefLogEntry instance"""
 | 
			
		||||
        # NOTE: we use the committer of the currently active commit - this should be
 | 
			
		||||
        # correct to allow overriding the committer on a per-commit level.
 | 
			
		||||
        # See https://github.com/gitpython-developers/GitPython/pull/146
 | 
			
		||||
        try:
 | 
			
		||||
            committer_or_reader: Union["Actor", "GitConfigParser"] = self.commit.committer
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            committer_or_reader = self.repo.config_reader()
 | 
			
		||||
        # end handle newly cloned repositories
 | 
			
		||||
        if newbinsha is None:
 | 
			
		||||
            newbinsha = self.commit.binsha
 | 
			
		||||
 | 
			
		||||
        if message is None:
 | 
			
		||||
            message = ""
 | 
			
		||||
 | 
			
		||||
        return RefLog.append_entry(committer_or_reader, RefLog.path(self), oldbinsha, newbinsha, message)
 | 
			
		||||
 | 
			
		||||
    def log_entry(self, index: int) -> "RefLogEntry":
 | 
			
		||||
        """:return: RefLogEntry at the given index
 | 
			
		||||
        :param index: python list compatible positive or negative index
 | 
			
		||||
 | 
			
		||||
        .. note:: This method must read part of the reflog during execution, hence
 | 
			
		||||
            it should be used sparringly, or only if you need just one index.
 | 
			
		||||
            In that case, it will be faster than the ``log()`` method"""
 | 
			
		||||
        return RefLog.entry_at(RefLog.path(self), index)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def to_full_path(cls, path: Union[PathLike, "SymbolicReference"]) -> PathLike:
 | 
			
		||||
        """
 | 
			
		||||
        :return: string with a full repository-relative path which can be used to initialize
 | 
			
		||||
            a Reference instance, for instance by using ``Reference.from_path``"""
 | 
			
		||||
        if isinstance(path, SymbolicReference):
 | 
			
		||||
            path = path.path
 | 
			
		||||
        full_ref_path = path
 | 
			
		||||
        if not cls._common_path_default:
 | 
			
		||||
            return full_ref_path
 | 
			
		||||
        if not str(path).startswith(cls._common_path_default + "/"):
 | 
			
		||||
            full_ref_path = "%s/%s" % (cls._common_path_default, path)
 | 
			
		||||
        return full_ref_path
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def delete(cls, repo: "Repo", path: PathLike) -> None:
 | 
			
		||||
        """Delete the reference at the given path
 | 
			
		||||
 | 
			
		||||
        :param repo:
 | 
			
		||||
            Repository to delete the reference from
 | 
			
		||||
 | 
			
		||||
        :param path:
 | 
			
		||||
            Short or full path pointing to the reference, i.e. refs/myreference
 | 
			
		||||
            or just "myreference", hence 'refs/' is implied.
 | 
			
		||||
            Alternatively the symbolic reference to be deleted"""
 | 
			
		||||
        full_ref_path = cls.to_full_path(path)
 | 
			
		||||
        abs_path = os.path.join(repo.common_dir, full_ref_path)
 | 
			
		||||
        if os.path.exists(abs_path):
 | 
			
		||||
            os.remove(abs_path)
 | 
			
		||||
        else:
 | 
			
		||||
            # check packed refs
 | 
			
		||||
            pack_file_path = cls._get_packed_refs_path(repo)
 | 
			
		||||
            try:
 | 
			
		||||
                with open(pack_file_path, "rb") as reader:
 | 
			
		||||
                    new_lines = []
 | 
			
		||||
                    made_change = False
 | 
			
		||||
                    dropped_last_line = False
 | 
			
		||||
                    for line_bytes in reader:
 | 
			
		||||
                        line = line_bytes.decode(defenc)
 | 
			
		||||
                        _, _, line_ref = line.partition(" ")
 | 
			
		||||
                        line_ref = line_ref.strip()
 | 
			
		||||
                        # keep line if it is a comment or if the ref to delete is not
 | 
			
		||||
                        # in the line
 | 
			
		||||
                        # If we deleted the last line and this one is a tag-reference object,
 | 
			
		||||
                        # we drop it as well
 | 
			
		||||
                        if (line.startswith("#") or full_ref_path != line_ref) and (
 | 
			
		||||
                            not dropped_last_line or dropped_last_line and not line.startswith("^")
 | 
			
		||||
                        ):
 | 
			
		||||
                            new_lines.append(line)
 | 
			
		||||
                            dropped_last_line = False
 | 
			
		||||
                            continue
 | 
			
		||||
                        # END skip comments and lines without our path
 | 
			
		||||
 | 
			
		||||
                        # drop this line
 | 
			
		||||
                        made_change = True
 | 
			
		||||
                        dropped_last_line = True
 | 
			
		||||
 | 
			
		||||
                # write the new lines
 | 
			
		||||
                if made_change:
 | 
			
		||||
                    # write-binary is required, otherwise windows will
 | 
			
		||||
                    # open the file in text mode and change LF to CRLF !
 | 
			
		||||
                    with open(pack_file_path, "wb") as fd:
 | 
			
		||||
                        fd.writelines(line.encode(defenc) for line in new_lines)
 | 
			
		||||
 | 
			
		||||
            except OSError:
 | 
			
		||||
                pass  # it didn't exist at all
 | 
			
		||||
 | 
			
		||||
        # delete the reflog
 | 
			
		||||
        reflog_path = RefLog.path(cls(repo, full_ref_path))
 | 
			
		||||
        if os.path.isfile(reflog_path):
 | 
			
		||||
            os.remove(reflog_path)
 | 
			
		||||
        # END remove reflog
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def _create(
 | 
			
		||||
        cls: Type[T_References],
 | 
			
		||||
        repo: "Repo",
 | 
			
		||||
        path: PathLike,
 | 
			
		||||
        resolve: bool,
 | 
			
		||||
        reference: Union["SymbolicReference", str],
 | 
			
		||||
        force: bool,
 | 
			
		||||
        logmsg: Union[str, None] = None,
 | 
			
		||||
    ) -> T_References:
 | 
			
		||||
        """internal method used to create a new symbolic reference.
 | 
			
		||||
        If resolve is False, the reference will be taken as is, creating
 | 
			
		||||
        a proper symbolic reference. Otherwise it will be resolved to the
 | 
			
		||||
        corresponding object and a detached symbolic reference will be created
 | 
			
		||||
        instead"""
 | 
			
		||||
        git_dir = _git_dir(repo, path)
 | 
			
		||||
        full_ref_path = cls.to_full_path(path)
 | 
			
		||||
        abs_ref_path = os.path.join(git_dir, full_ref_path)
 | 
			
		||||
 | 
			
		||||
        # figure out target data
 | 
			
		||||
        target = reference
 | 
			
		||||
        if resolve:
 | 
			
		||||
            target = repo.rev_parse(str(reference))
 | 
			
		||||
 | 
			
		||||
        if not force and os.path.isfile(abs_ref_path):
 | 
			
		||||
            target_data = str(target)
 | 
			
		||||
            if isinstance(target, SymbolicReference):
 | 
			
		||||
                target_data = str(target.path)
 | 
			
		||||
            if not resolve:
 | 
			
		||||
                target_data = "ref: " + target_data
 | 
			
		||||
            with open(abs_ref_path, "rb") as fd:
 | 
			
		||||
                existing_data = fd.read().decode(defenc).strip()
 | 
			
		||||
            if existing_data != target_data:
 | 
			
		||||
                raise OSError(
 | 
			
		||||
                    "Reference at %r does already exist, pointing to %r, requested was %r"
 | 
			
		||||
                    % (full_ref_path, existing_data, target_data)
 | 
			
		||||
                )
 | 
			
		||||
        # END no force handling
 | 
			
		||||
 | 
			
		||||
        ref = cls(repo, full_ref_path)
 | 
			
		||||
        ref.set_reference(target, logmsg)
 | 
			
		||||
        return ref
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create(
 | 
			
		||||
        cls: Type[T_References],
 | 
			
		||||
        repo: "Repo",
 | 
			
		||||
        path: PathLike,
 | 
			
		||||
        reference: Union["SymbolicReference", str] = "HEAD",
 | 
			
		||||
        logmsg: Union[str, None] = None,
 | 
			
		||||
        force: bool = False,
 | 
			
		||||
        **kwargs: Any,
 | 
			
		||||
    ) -> T_References:
 | 
			
		||||
        """Create a new symbolic reference, hence a reference pointing , to another reference.
 | 
			
		||||
 | 
			
		||||
        :param repo:
 | 
			
		||||
            Repository to create the reference in
 | 
			
		||||
 | 
			
		||||
        :param path:
 | 
			
		||||
            full path at which the new symbolic reference is supposed to be
 | 
			
		||||
            created at, i.e. "NEW_HEAD" or "symrefs/my_new_symref"
 | 
			
		||||
 | 
			
		||||
        :param reference:
 | 
			
		||||
            The reference to which the new symbolic reference should point to.
 | 
			
		||||
            If it is a commit'ish, the symbolic ref will be detached.
 | 
			
		||||
 | 
			
		||||
        :param force:
 | 
			
		||||
            if True, force creation even if a symbolic reference with that name already exists.
 | 
			
		||||
            Raise OSError otherwise
 | 
			
		||||
 | 
			
		||||
        :param logmsg:
 | 
			
		||||
            If not None, the message to append to the reflog. Otherwise no reflog
 | 
			
		||||
            entry is written.
 | 
			
		||||
 | 
			
		||||
        :return: Newly created symbolic Reference
 | 
			
		||||
 | 
			
		||||
        :raise OSError:
 | 
			
		||||
            If a (Symbolic)Reference with the same name but different contents
 | 
			
		||||
            already exists.
 | 
			
		||||
 | 
			
		||||
        :note: This does not alter the current HEAD, index or Working Tree"""
 | 
			
		||||
        return cls._create(repo, path, cls._resolve_ref_on_create, reference, force, logmsg)
 | 
			
		||||
 | 
			
		||||
    def rename(self, new_path: PathLike, force: bool = False) -> "SymbolicReference":
 | 
			
		||||
        """Rename self to a new path
 | 
			
		||||
 | 
			
		||||
        :param new_path:
 | 
			
		||||
            Either a simple name or a full path, i.e. new_name or features/new_name.
 | 
			
		||||
            The prefix refs/ is implied for references and will be set as needed.
 | 
			
		||||
            In case this is a symbolic ref, there is no implied prefix
 | 
			
		||||
 | 
			
		||||
        :param force:
 | 
			
		||||
            If True, the rename will succeed even if a head with the target name
 | 
			
		||||
            already exists. It will be overwritten in that case
 | 
			
		||||
 | 
			
		||||
        :return: self
 | 
			
		||||
        :raise OSError: In case a file at path but a different contents already exists"""
 | 
			
		||||
        new_path = self.to_full_path(new_path)
 | 
			
		||||
        if self.path == new_path:
 | 
			
		||||
            return self
 | 
			
		||||
 | 
			
		||||
        new_abs_path = os.path.join(_git_dir(self.repo, new_path), new_path)
 | 
			
		||||
        cur_abs_path = os.path.join(_git_dir(self.repo, self.path), self.path)
 | 
			
		||||
        if os.path.isfile(new_abs_path):
 | 
			
		||||
            if not force:
 | 
			
		||||
                # if they point to the same file, its not an error
 | 
			
		||||
                with open(new_abs_path, "rb") as fd1:
 | 
			
		||||
                    f1 = fd1.read().strip()
 | 
			
		||||
                with open(cur_abs_path, "rb") as fd2:
 | 
			
		||||
                    f2 = fd2.read().strip()
 | 
			
		||||
                if f1 != f2:
 | 
			
		||||
                    raise OSError("File at path %r already exists" % new_abs_path)
 | 
			
		||||
                # else: we could remove ourselves and use the otherone, but
 | 
			
		||||
                # but clarity we just continue as usual
 | 
			
		||||
            # END not force handling
 | 
			
		||||
            os.remove(new_abs_path)
 | 
			
		||||
        # END handle existing target file
 | 
			
		||||
 | 
			
		||||
        dname = os.path.dirname(new_abs_path)
 | 
			
		||||
        if not os.path.isdir(dname):
 | 
			
		||||
            os.makedirs(dname)
 | 
			
		||||
        # END create directory
 | 
			
		||||
 | 
			
		||||
        os.rename(cur_abs_path, new_abs_path)
 | 
			
		||||
        self.path = new_path
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def _iter_items(
 | 
			
		||||
        cls: Type[T_References], repo: "Repo", common_path: Union[PathLike, None] = None
 | 
			
		||||
    ) -> Iterator[T_References]:
 | 
			
		||||
        if common_path is None:
 | 
			
		||||
            common_path = cls._common_path_default
 | 
			
		||||
        rela_paths = set()
 | 
			
		||||
 | 
			
		||||
        # walk loose refs
 | 
			
		||||
        # Currently we do not follow links
 | 
			
		||||
        for root, dirs, files in os.walk(join_path_native(repo.common_dir, common_path)):
 | 
			
		||||
            if "refs" not in root.split(os.sep):  # skip non-refs subfolders
 | 
			
		||||
                refs_id = [d for d in dirs if d == "refs"]
 | 
			
		||||
                if refs_id:
 | 
			
		||||
                    dirs[0:] = ["refs"]
 | 
			
		||||
            # END prune non-refs folders
 | 
			
		||||
 | 
			
		||||
            for f in files:
 | 
			
		||||
                if f == "packed-refs":
 | 
			
		||||
                    continue
 | 
			
		||||
                abs_path = to_native_path_linux(join_path(root, f))
 | 
			
		||||
                rela_paths.add(abs_path.replace(to_native_path_linux(repo.common_dir) + "/", ""))
 | 
			
		||||
            # END for each file in root directory
 | 
			
		||||
        # END for each directory to walk
 | 
			
		||||
 | 
			
		||||
        # read packed refs
 | 
			
		||||
        for _sha, rela_path in cls._iter_packed_refs(repo):
 | 
			
		||||
            if rela_path.startswith(str(common_path)):
 | 
			
		||||
                rela_paths.add(rela_path)
 | 
			
		||||
            # END relative path matches common path
 | 
			
		||||
        # END packed refs reading
 | 
			
		||||
 | 
			
		||||
        # return paths in sorted order
 | 
			
		||||
        for path in sorted(rela_paths):
 | 
			
		||||
            try:
 | 
			
		||||
                yield cls.from_path(repo, path)
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                continue
 | 
			
		||||
        # END for each sorted relative refpath
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def iter_items(
 | 
			
		||||
        cls: Type[T_References],
 | 
			
		||||
        repo: "Repo",
 | 
			
		||||
        common_path: Union[PathLike, None] = None,
 | 
			
		||||
        *args: Any,
 | 
			
		||||
        **kwargs: Any,
 | 
			
		||||
    ) -> Iterator[T_References]:
 | 
			
		||||
        """Find all refs in the repository
 | 
			
		||||
 | 
			
		||||
        :param repo: is the Repo
 | 
			
		||||
 | 
			
		||||
        :param common_path:
 | 
			
		||||
            Optional keyword argument to the path which is to be shared by all
 | 
			
		||||
            returned Ref objects.
 | 
			
		||||
            Defaults to class specific portion if None assuring that only
 | 
			
		||||
            refs suitable for the actual class are returned.
 | 
			
		||||
 | 
			
		||||
        :return:
 | 
			
		||||
            git.SymbolicReference[], each of them is guaranteed to be a symbolic
 | 
			
		||||
            ref which is not detached and pointing to a valid ref
 | 
			
		||||
 | 
			
		||||
            List is lexicographically sorted
 | 
			
		||||
            The returned objects represent actual subclasses, such as Head or TagReference"""
 | 
			
		||||
        return (r for r in cls._iter_items(repo, common_path) if r.__class__ == SymbolicReference or not r.is_detached)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def from_path(cls: Type[T_References], repo: "Repo", path: PathLike) -> T_References:
 | 
			
		||||
        """
 | 
			
		||||
        :param path: full .git-directory-relative path name to the Reference to instantiate
 | 
			
		||||
        :note: use to_full_path() if you only have a partial path of a known Reference Type
 | 
			
		||||
        :return:
 | 
			
		||||
            Instance of type Reference, Head, or Tag
 | 
			
		||||
            depending on the given path"""
 | 
			
		||||
        if not path:
 | 
			
		||||
            raise ValueError("Cannot create Reference from %r" % path)
 | 
			
		||||
 | 
			
		||||
        # Names like HEAD are inserted after the refs module is imported - we have an import dependency
 | 
			
		||||
        # cycle and don't want to import these names in-function
 | 
			
		||||
        from . import HEAD, Head, RemoteReference, TagReference, Reference
 | 
			
		||||
 | 
			
		||||
        for ref_type in (
 | 
			
		||||
            HEAD,
 | 
			
		||||
            Head,
 | 
			
		||||
            RemoteReference,
 | 
			
		||||
            TagReference,
 | 
			
		||||
            Reference,
 | 
			
		||||
            SymbolicReference,
 | 
			
		||||
        ):
 | 
			
		||||
            try:
 | 
			
		||||
                instance: T_References
 | 
			
		||||
                instance = ref_type(repo, path)
 | 
			
		||||
                if instance.__class__ == SymbolicReference and instance.is_detached:
 | 
			
		||||
                    raise ValueError("SymbolRef was detached, we drop it")
 | 
			
		||||
                else:
 | 
			
		||||
                    return instance
 | 
			
		||||
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                pass
 | 
			
		||||
            # END exception handling
 | 
			
		||||
        # END for each type to try
 | 
			
		||||
        raise ValueError("Could not find reference type suitable to handle path %r" % path)
 | 
			
		||||
 | 
			
		||||
    def is_remote(self) -> bool:
 | 
			
		||||
        """:return: True if this symbolic reference points to a remote branch"""
 | 
			
		||||
        return str(self.path).startswith(self._remote_common_path_default + "/")
 | 
			
		||||
							
								
								
									
										138
									
								
								zero-cost-nas/.eggs/GitPython-3.1.31-py3.8.egg/git/refs/tag.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								zero-cost-nas/.eggs/GitPython-3.1.31-py3.8.egg/git/refs/tag.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,138 @@
 | 
			
		||||
from .reference import Reference
 | 
			
		||||
 | 
			
		||||
__all__ = ["TagReference", "Tag"]
 | 
			
		||||
 | 
			
		||||
# typing ------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
from typing import Any, Type, Union, TYPE_CHECKING
 | 
			
		||||
from git.types import Commit_ish, PathLike
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
    from git.repo import Repo
 | 
			
		||||
    from git.objects import Commit
 | 
			
		||||
    from git.objects import TagObject
 | 
			
		||||
    from git.refs import SymbolicReference
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TagReference(Reference):
 | 
			
		||||
 | 
			
		||||
    """Class representing a lightweight tag reference which either points to a commit
 | 
			
		||||
    ,a tag object or any other object. In the latter case additional information,
 | 
			
		||||
    like the signature or the tag-creator, is available.
 | 
			
		||||
 | 
			
		||||
    This tag object will always point to a commit object, but may carry additional
 | 
			
		||||
    information in a tag object::
 | 
			
		||||
 | 
			
		||||
     tagref = TagReference.list_items(repo)[0]
 | 
			
		||||
     print(tagref.commit.message)
 | 
			
		||||
     if tagref.tag is not None:
 | 
			
		||||
        print(tagref.tag.message)"""
 | 
			
		||||
 | 
			
		||||
    __slots__ = ()
 | 
			
		||||
    _common_default = "tags"
 | 
			
		||||
    _common_path_default = Reference._common_path_default + "/" + _common_default
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def commit(self) -> "Commit":  # type: ignore[override]  # LazyMixin has unrelated commit method
 | 
			
		||||
        """:return: Commit object the tag ref points to
 | 
			
		||||
 | 
			
		||||
        :raise ValueError: if the tag points to a tree or blob"""
 | 
			
		||||
        obj = self.object
 | 
			
		||||
        while obj.type != "commit":
 | 
			
		||||
            if obj.type == "tag":
 | 
			
		||||
                # it is a tag object which carries the commit as an object - we can point to anything
 | 
			
		||||
                obj = obj.object
 | 
			
		||||
            else:
 | 
			
		||||
                raise ValueError(
 | 
			
		||||
                    (
 | 
			
		||||
                        "Cannot resolve commit as tag %s points to a %s object - "
 | 
			
		||||
                        + "use the `.object` property instead to access it"
 | 
			
		||||
                    )
 | 
			
		||||
                    % (self, obj.type)
 | 
			
		||||
                )
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def tag(self) -> Union["TagObject", None]:
 | 
			
		||||
        """
 | 
			
		||||
        :return: Tag object this tag ref points to or None in case
 | 
			
		||||
            we are a light weight tag"""
 | 
			
		||||
        obj = self.object
 | 
			
		||||
        if obj.type == "tag":
 | 
			
		||||
            return obj
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    # make object read-only
 | 
			
		||||
    # It should be reasonably hard to adjust an existing tag
 | 
			
		||||
 | 
			
		||||
    # object = property(Reference._get_object)
 | 
			
		||||
    @property
 | 
			
		||||
    def object(self) -> Commit_ish:  # type: ignore[override]
 | 
			
		||||
        return Reference._get_object(self)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create(
 | 
			
		||||
        cls: Type["TagReference"],
 | 
			
		||||
        repo: "Repo",
 | 
			
		||||
        path: PathLike,
 | 
			
		||||
        reference: Union[str, "SymbolicReference"] = "HEAD",
 | 
			
		||||
        logmsg: Union[str, None] = None,
 | 
			
		||||
        force: bool = False,
 | 
			
		||||
        **kwargs: Any,
 | 
			
		||||
    ) -> "TagReference":
 | 
			
		||||
        """Create a new tag reference.
 | 
			
		||||
 | 
			
		||||
        :param path:
 | 
			
		||||
            The name of the tag, i.e. 1.0 or releases/1.0.
 | 
			
		||||
            The prefix refs/tags is implied
 | 
			
		||||
 | 
			
		||||
        :param ref:
 | 
			
		||||
            A reference to the Object you want to tag. The Object can be a commit, tree or
 | 
			
		||||
            blob.
 | 
			
		||||
 | 
			
		||||
        :param logmsg:
 | 
			
		||||
            If not None, the message will be used in your tag object. This will also
 | 
			
		||||
            create an additional tag object that allows to obtain that information, i.e.::
 | 
			
		||||
 | 
			
		||||
                tagref.tag.message
 | 
			
		||||
 | 
			
		||||
        :param message:
 | 
			
		||||
            Synonym for :param logmsg:
 | 
			
		||||
            Included for backwards compatibility. :param logmsg is used in preference if both given.
 | 
			
		||||
 | 
			
		||||
        :param force:
 | 
			
		||||
            If True, to force creation of a tag even though that tag already exists.
 | 
			
		||||
 | 
			
		||||
        :param kwargs:
 | 
			
		||||
            Additional keyword arguments to be passed to git-tag
 | 
			
		||||
 | 
			
		||||
        :return: A new TagReference"""
 | 
			
		||||
        if "ref" in kwargs and kwargs["ref"]:
 | 
			
		||||
            reference = kwargs["ref"]
 | 
			
		||||
 | 
			
		||||
        if "message" in kwargs and kwargs["message"]:
 | 
			
		||||
            kwargs["m"] = kwargs["message"]
 | 
			
		||||
            del kwargs["message"]
 | 
			
		||||
 | 
			
		||||
        if logmsg:
 | 
			
		||||
            kwargs["m"] = logmsg
 | 
			
		||||
 | 
			
		||||
        if force:
 | 
			
		||||
            kwargs["f"] = True
 | 
			
		||||
 | 
			
		||||
        args = (path, reference)
 | 
			
		||||
 | 
			
		||||
        repo.git.tag(*args, **kwargs)
 | 
			
		||||
        return TagReference(repo, "%s/%s" % (cls._common_path_default, path))
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def delete(cls, repo: "Repo", *tags: "TagReference") -> None:  # type: ignore[override]
 | 
			
		||||
        """Delete the given existing tag or tags"""
 | 
			
		||||
        repo.git.tag("-d", *tags)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# provide an alias
 | 
			
		||||
Tag = TagReference
 | 
			
		||||
		Reference in New Issue
	
	Block a user