diff options
author | Sebastian Thiel <sebastian.thiel@icloud.com> | 2021-07-02 07:54:45 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-07-02 07:54:45 +0800 |
commit | 18c777bf815357663226fd6834c8b1bcfe9b7b62 (patch) | |
tree | 6e60335c9c631c38d0e88cf0ac11623985b8bd36 /git/objects/commit.py | |
parent | 8ad4f5923e7df65a4ad30a527ab10abc88f74f64 (diff) | |
parent | d4a9eab9ddc64a18b33ac04a4224f347ccdc78de (diff) | |
download | gitpython-18c777bf815357663226fd6834c8b1bcfe9b7b62.tar.gz |
Merge pull request #1282 from Yobmod/main
Start adding types to Submodule, add py.typed to manifest
Diffstat (limited to 'git/objects/commit.py')
-rw-r--r-- | git/objects/commit.py | 117 |
1 files changed, 78 insertions, 39 deletions
diff --git a/git/objects/commit.py b/git/objects/commit.py index 0b707450..81978ae8 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -3,12 +3,12 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php - +import datetime +from subprocess import Popen from gitdb import IStream from git.util import ( hex_to_bin, Actor, - IterableObj, Stats, finalize_process ) @@ -17,8 +17,8 @@ from git.diff import Diffable from .tree import Tree from . import base from .util import ( - Traversable, Serializable, + TraversableIterableObj, parse_date, altz_to_utctz_str, parse_actor_and_date, @@ -36,18 +36,25 @@ import os from io import BytesIO import logging -from typing import List, Tuple, Union, TYPE_CHECKING + +# typing ------------------------------------------------------------------ + +from typing import Any, IO, Iterator, List, Sequence, Tuple, Union, TYPE_CHECKING + +from git.types import PathLike, TypeGuard if TYPE_CHECKING: from git.repo import Repo +# ------------------------------------------------------------------------ + log = logging.getLogger('git.objects.commit') log.addHandler(logging.NullHandler()) __all__ = ('Commit', ) -class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): +class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): """Wraps a git Commit object. @@ -73,10 +80,17 @@ class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): "message", "parents", "encoding", "gpgsig") _id_attribute_ = "hexsha" - def __init__(self, repo, binsha, tree=None, author=None, authored_date=None, author_tz_offset=None, - committer=None, committed_date=None, committer_tz_offset=None, - message=None, parents: Union[Tuple['Commit', ...], List['Commit'], None] = None, - encoding=None, gpgsig=None): + def __init__(self, repo: 'Repo', binsha: bytes, tree: 'Tree' = None, + author: Union[Actor, None] = None, + authored_date: Union[int, None] = None, + author_tz_offset: Union[None, float] = None, + committer: Union[Actor, None] = None, + committed_date: Union[int, None] = None, + committer_tz_offset: Union[None, float] = None, + message: Union[str, bytes, None] = None, + parents: Union[Sequence['Commit'], None] = None, + encoding: Union[str, None] = None, + gpgsig: Union[str, None] = None) -> None: """Instantiate a new Commit. All keyword arguments taking None as default will be implicitly set on first query. @@ -139,7 +153,7 @@ class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): self.gpgsig = gpgsig @classmethod - def _get_intermediate_items(cls, commit: 'Commit') -> Tuple['Commit', ...]: # type: ignore ## cos overriding super + def _get_intermediate_items(cls, commit: 'Commit') -> Tuple['Commit', ...]: return tuple(commit.parents) @classmethod @@ -158,7 +172,7 @@ class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): istream = repo.odb.store(IStream(cls.type, streamlen, stream)) return istream.binsha - def replace(self, **kwargs): + def replace(self, **kwargs: Any) -> 'Commit': '''Create new commit object from existing commit object. Any values provided as keyword arguments will replace the @@ -177,7 +191,7 @@ class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): return new_commit - def _set_cache_(self, attr): + def _set_cache_(self, attr: str) -> None: if attr in Commit.__slots__: # read the data in a chunk, its faster - then provide a file wrapper _binsha, _typename, self.size, stream = self.repo.odb.stream(self.binsha) @@ -187,19 +201,22 @@ class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): # END handle attrs @property - def authored_datetime(self): + def authored_datetime(self) -> 'datetime.datetime': return from_timestamp(self.authored_date, self.author_tz_offset) @property - def committed_datetime(self): + def committed_datetime(self) -> 'datetime.datetime': return from_timestamp(self.committed_date, self.committer_tz_offset) @property - def summary(self): + def summary(self) -> Union[str, bytes]: """:return: First line of the commit message""" - return self.message.split('\n', 1)[0] + if isinstance(self.message, str): + return self.message.split('\n', 1)[0] + else: + return self.message.split(b'\n', 1)[0] - def count(self, paths='', **kwargs): + def count(self, paths: Union[PathLike, Sequence[PathLike]] = '', **kwargs: Any) -> int: """Count the number of commits reachable from this commit :param paths: @@ -217,7 +234,7 @@ class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): return len(self.repo.git.rev_list(self.hexsha, **kwargs).splitlines()) @property - def name_rev(self): + def name_rev(self) -> str: """ :return: String describing the commits hex sha based on the closest Reference. @@ -225,7 +242,9 @@ class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): return self.repo.git.name_rev(self) @classmethod - def iter_items(cls, repo, rev, paths='', **kwargs): + def iter_items(cls, repo: 'Repo', rev: str, # type: ignore + paths: Union[PathLike, Sequence[PathLike]] = '', **kwargs: Any + ) -> Iterator['Commit']: """Find all commits matching the given criteria. :param repo: is the Repo @@ -245,15 +264,23 @@ class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): # use -- in any case, to prevent possibility of ambiguous arguments # see https://github.com/gitpython-developers/GitPython/issues/264 - args = ['--'] + + args_list: List[PathLike] = ['--'] + if paths: - args.extend((paths, )) + paths_tup: Tuple[PathLike, ...] + if isinstance(paths, (str, os.PathLike)): + paths_tup = (paths, ) + else: + paths_tup = tuple(paths) + + args_list.extend(paths_tup) # END if paths - proc = repo.git.rev_list(rev, args, as_process=True, **kwargs) + proc = repo.git.rev_list(rev, args_list, as_process=True, **kwargs) return cls._iter_from_process_or_stream(repo, proc) - def iter_parents(self, paths='', **kwargs): + def iter_parents(self, paths: Union[PathLike, Sequence[PathLike]] = '', **kwargs) -> Iterator['Commit']: """Iterate _all_ parents of this commit. :param paths: @@ -269,8 +296,8 @@ class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): return self.iter_items(self.repo, self, paths, **kwargs) - @property - def stats(self): + @ property + def stats(self) -> Stats: """Create a git stat from changes between this commit and its first parent or from all changes done if this is the very first commit. @@ -286,17 +313,26 @@ class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): text = self.repo.git.diff(self.parents[0].hexsha, self.hexsha, '--', numstat=True) return Stats._list_from_string(self.repo, text) - @classmethod - def _iter_from_process_or_stream(cls, repo, proc_or_stream): + @ classmethod + def _iter_from_process_or_stream(cls, repo: 'Repo', proc_or_stream: Union[Popen, IO]) -> Iterator['Commit']: """Parse out commit information into a list of Commit objects We expect one-line per commit, and parse the actual commit information directly from our lighting fast object database :param proc: git-rev-list process instance - one sha per line :return: iterator returning Commit objects""" - stream = proc_or_stream - if not hasattr(stream, 'readline'): - stream = proc_or_stream.stdout + + def is_proc(inp) -> TypeGuard[Popen]: + return hasattr(proc_or_stream, 'wait') and not hasattr(proc_or_stream, 'readline') + + def is_stream(inp) -> TypeGuard[IO]: + return hasattr(proc_or_stream, 'readline') + + if is_proc(proc_or_stream): + if proc_or_stream.stdout is not None: + stream = proc_or_stream.stdout + elif is_stream(proc_or_stream): + stream = proc_or_stream readline = stream.readline while True: @@ -314,19 +350,21 @@ class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): # END for each line in stream # TODO: Review this - it seems process handling got a bit out of control # due to many developers trying to fix the open file handles issue - if hasattr(proc_or_stream, 'wait'): + if is_proc(proc_or_stream): finalize_process(proc_or_stream) - @classmethod - def create_from_tree(cls, repo, tree, message, parent_commits=None, head=False, author=None, committer=None, - author_date=None, commit_date=None): + @ classmethod + def create_from_tree(cls, repo: 'Repo', tree: Union['Tree', str], message: str, + parent_commits: Union[None, List['Commit']] = None, head: bool = False, + author: Union[None, Actor] = None, committer: Union[None, Actor] = None, + author_date: Union[None, str] = None, commit_date: Union[None, str] = None): """Commit the given tree, creating a commit object. :param repo: Repo object the commit should be part of :param tree: Tree object or hex or bin sha the tree of the new commit :param message: Commit message. It may be an empty string if no message is provided. - It will be converted to a string in any case. + It will be converted to a string , in any case. :param parent_commits: Optional Commit objects to use as parents for the new commit. If empty list, the commit will have no parents at all and become @@ -460,7 +498,7 @@ class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): write(("encoding %s\n" % self.encoding).encode('ascii')) try: - if self.__getattribute__('gpgsig') is not None: + if self.__getattribute__('gpgsig'): write(b"gpgsig") for sigline in self.gpgsig.rstrip("\n").split("\n"): write((" " + sigline + "\n").encode('ascii')) @@ -510,7 +548,7 @@ class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): # now we can have the encoding line, or an empty line followed by the optional # message. self.encoding = self.default_encoding - self.gpgsig = None + self.gpgsig = "" # read headers enc = next_line @@ -539,7 +577,7 @@ class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): # decode the authors name try: - self.author, self.authored_date, self.author_tz_offset = \ + (self.author, self.authored_date, self.author_tz_offset) = \ parse_actor_and_date(author_line.decode(self.encoding, 'replace')) except UnicodeDecodeError: log.error("Failed to decode author line '%s' using encoding %s", author_line, self.encoding, @@ -559,7 +597,8 @@ class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): try: self.message = self.message.decode(self.encoding, 'replace') except UnicodeDecodeError: - log.error("Failed to decode message '%s' using encoding %s", self.message, self.encoding, exc_info=True) + log.error("Failed to decode message '%s' using encoding %s", + self.message, self.encoding, exc_info=True) # END exception handling return self |