diff options
Diffstat (limited to 'git/objects/commit.py')
-rw-r--r-- | git/objects/commit.py | 82 |
1 files changed, 51 insertions, 31 deletions
diff --git a/git/objects/commit.py b/git/objects/commit.py index 7d3ea4fa..0461f0e5 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -3,6 +3,8 @@ # # 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, @@ -37,9 +39,9 @@ import logging # typing ------------------------------------------------------------------ -from typing import Any, Iterator, List, Sequence, Tuple, Union, TYPE_CHECKING +from typing import Any, IO, Iterator, List, Sequence, Tuple, Union, TYPE_CHECKING -from git.types import PathLike +from git.types import PathLike, TypeGuard if TYPE_CHECKING: from git.repo import Repo @@ -78,11 +80,17 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): "message", "parents", "encoding", "gpgsig") _id_attribute_ = "hexsha" - def __init__(self, repo, binsha, tree=None, author: Union[Actor, None] = 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, 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. @@ -164,7 +172,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, 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 @@ -183,7 +191,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, 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) @@ -193,19 +201,19 @@ class Commit(base.Object, TraversableIterableObj, Diffable, 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) -> str: """:return: First line of the commit message""" return self.message.split('\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: @@ -223,7 +231,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, 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. @@ -231,7 +239,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): return self.repo.git.name_rev(self) @classmethod - def iter_items(cls, repo: 'Repo', rev, # type: ignore + 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. @@ -254,7 +262,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): # use -- in any case, to prevent possibility of ambiguous arguments # see https://github.com/gitpython-developers/GitPython/issues/264 - args_list: List[Union[PathLike, Sequence[PathLike]]] = ['--'] + args_list: List[PathLike] = ['--'] if paths: paths_tup: Tuple[PathLike, ...] @@ -286,7 +294,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): return self.iter_items(self.repo, self, paths, **kwargs) @ property - def stats(self): + 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. @@ -303,16 +311,25 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): return Stats._list_from_string(self.repo, text) @ classmethod - def _iter_from_process_or_stream(cls, repo, proc_or_stream): + 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: @@ -330,19 +347,21 @@ class Commit(base.Object, TraversableIterableObj, Diffable, 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): + 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 @@ -476,7 +495,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, 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')) @@ -526,7 +545,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, 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 @@ -555,7 +574,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, 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, @@ -571,11 +590,12 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): # a stream from our data simply gives us the plain message # The end of our message stream is marked with a newline that we strip - self.message = stream.read() + self.message_bytes = stream.read() try: - self.message = self.message.decode(self.encoding, 'replace') + self.message = self.message_bytes.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_bytes, self.encoding, exc_info=True) # END exception handling return self |