From 82b131cf2afebbed3723df5b5dfd5cd820716f97 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Wed, 30 Jun 2021 18:41:06 +0100 Subject: Type Traversable.traverse() better, start types of submodule --- git/objects/commit.py | 46 +++++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 15 deletions(-) (limited to 'git/objects/commit.py') diff --git a/git/objects/commit.py b/git/objects/commit.py index 0b707450..7d3ea4fa 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -3,12 +3,10 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php - from gitdb import IStream from git.util import ( hex_to_bin, Actor, - IterableObj, Stats, finalize_process ) @@ -17,8 +15,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 +34,25 @@ import os from io import BytesIO import logging -from typing import List, Tuple, Union, TYPE_CHECKING + +# typing ------------------------------------------------------------------ + +from typing import Any, Iterator, List, Sequence, Tuple, Union, TYPE_CHECKING + +from git.types import PathLike 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,7 +78,8 @@ 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, + 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): @@ -139,7 +145,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 @@ -225,7 +231,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, # 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 +253,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[Union[PathLike, Sequence[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,7 +285,7 @@ class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): return self.iter_items(self.repo, self, paths, **kwargs) - @property + @ property def stats(self): """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,7 +302,7 @@ 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 + @ classmethod def _iter_from_process_or_stream(cls, repo, proc_or_stream): """Parse out commit information into a list of Commit objects We expect one-line per commit, and parse the actual commit information directly @@ -317,7 +333,7 @@ class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): if hasattr(proc_or_stream, 'wait'): finalize_process(proc_or_stream) - @classmethod + @ classmethod def create_from_tree(cls, repo, tree, message, parent_commits=None, head=False, author=None, committer=None, author_date=None, commit_date=None): """Commit the given tree, creating a commit object. -- cgit v1.2.1 From 02b8ef0f163ca353e27f6b4a8c2120444739fde5 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 1 Jul 2021 10:35:11 +0100 Subject: Add missed types to Commit, uncomment to_native_path_linux() --- git/objects/commit.py | 82 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 31 deletions(-) (limited to 'git/objects/commit.py') 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 -- cgit v1.2.1 From d4a9eab9ddc64a18b33ac04a4224f347ccdc78de Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 1 Jul 2021 10:49:06 +0100 Subject: Make Commit.message bytes | str --- git/objects/commit.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'git/objects/commit.py') diff --git a/git/objects/commit.py b/git/objects/commit.py index 0461f0e5..81978ae8 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -87,7 +87,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): committer: Union[Actor, None] = None, committed_date: Union[int, None] = None, committer_tz_offset: Union[None, float] = None, - message: Union[str, None] = None, + message: Union[str, bytes, None] = None, parents: Union[Sequence['Commit'], None] = None, encoding: Union[str, None] = None, gpgsig: Union[str, None] = None) -> None: @@ -209,9 +209,12 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): return from_timestamp(self.committed_date, self.committer_tz_offset) @property - def summary(self) -> str: + 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: Union[PathLike, Sequence[PathLike]] = '', **kwargs: Any) -> int: """Count the number of commits reachable from this commit @@ -590,12 +593,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_bytes = stream.read() + self.message = stream.read() try: - self.message = self.message_bytes.decode(self.encoding, 'replace') + self.message = self.message.decode(self.encoding, 'replace') except UnicodeDecodeError: log.error("Failed to decode message '%s' using encoding %s", - self.message_bytes, self.encoding, exc_info=True) + self.message, self.encoding, exc_info=True) # END exception handling return self -- cgit v1.2.1