33#
44# This module is part of GitPython and is released under
55# the BSD License: http://www.opensource.org/licenses/bsd-license.php
6+ import datetime
7+ from subprocess import Popen
68from gitdb import IStream
79from git .util import (
810 hex_to_bin ,
3739
3840# typing ------------------------------------------------------------------
3941
40- from typing import Any , Iterator , List , Sequence , Tuple , Union , TYPE_CHECKING
42+ from typing import Any , IO , Iterator , List , Sequence , Tuple , Union , TYPE_CHECKING
4143
42- from git .types import PathLike
44+ from git .types import PathLike , TypeGuard
4345
4446if TYPE_CHECKING :
4547 from git .repo import Repo
@@ -78,11 +80,17 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
7880 "message" , "parents" , "encoding" , "gpgsig" )
7981 _id_attribute_ = "hexsha"
8082
81- def __init__ (self , repo , binsha , tree = None , author : Union [Actor , None ] = None ,
82- authored_date = None , author_tz_offset = None ,
83- committer = None , committed_date = None , committer_tz_offset = None ,
84- message = None , parents : Union [Tuple ['Commit' , ...], List ['Commit' ], None ] = None ,
85- encoding = None , gpgsig = None ):
83+ def __init__ (self , repo : 'Repo' , binsha : bytes , tree : 'Tree' = None ,
84+ author : Union [Actor , None ] = None ,
85+ authored_date : Union [int , None ] = None ,
86+ author_tz_offset : Union [None , float ] = None ,
87+ committer : Union [Actor , None ] = None ,
88+ committed_date : Union [int , None ] = None ,
89+ committer_tz_offset : Union [None , float ] = None ,
90+ message : Union [str , None ] = None ,
91+ parents : Union [Sequence ['Commit' ], None ] = None ,
92+ encoding : Union [str , None ] = None ,
93+ gpgsig : Union [str , None ] = None ) -> None :
8694 """Instantiate a new Commit. All keyword arguments taking None as default will
8795 be implicitly set on first query.
8896
@@ -164,7 +172,7 @@ def _calculate_sha_(cls, repo: 'Repo', commit: 'Commit') -> bytes:
164172 istream = repo .odb .store (IStream (cls .type , streamlen , stream ))
165173 return istream .binsha
166174
167- def replace (self , ** kwargs ) :
175+ def replace (self , ** kwargs : Any ) -> 'Commit' :
168176 '''Create new commit object from existing commit object.
169177
170178 Any values provided as keyword arguments will replace the
@@ -183,7 +191,7 @@ def replace(self, **kwargs):
183191
184192 return new_commit
185193
186- def _set_cache_ (self , attr ) :
194+ def _set_cache_ (self , attr : str ) -> None :
187195 if attr in Commit .__slots__ :
188196 # read the data in a chunk, its faster - then provide a file wrapper
189197 _binsha , _typename , self .size , stream = self .repo .odb .stream (self .binsha )
@@ -193,19 +201,19 @@ def _set_cache_(self, attr):
193201 # END handle attrs
194202
195203 @property
196- def authored_datetime (self ):
204+ def authored_datetime (self ) -> 'datetime.datetime' :
197205 return from_timestamp (self .authored_date , self .author_tz_offset )
198206
199207 @property
200- def committed_datetime (self ):
208+ def committed_datetime (self ) -> 'datetime.datetime' :
201209 return from_timestamp (self .committed_date , self .committer_tz_offset )
202210
203211 @property
204- def summary (self ):
212+ def summary (self ) -> str :
205213 """:return: First line of the commit message"""
206214 return self .message .split ('\n ' , 1 )[0 ]
207215
208- def count (self , paths = '' , ** kwargs ) :
216+ def count (self , paths : Union [ PathLike , Sequence [ PathLike ]] = '' , ** kwargs : Any ) -> int :
209217 """Count the number of commits reachable from this commit
210218
211219 :param paths:
@@ -223,15 +231,15 @@ def count(self, paths='', **kwargs):
223231 return len (self .repo .git .rev_list (self .hexsha , ** kwargs ).splitlines ())
224232
225233 @property
226- def name_rev (self ):
234+ def name_rev (self ) -> str :
227235 """
228236 :return:
229237 String describing the commits hex sha based on the closest Reference.
230238 Mostly useful for UI purposes"""
231239 return self .repo .git .name_rev (self )
232240
233241 @classmethod
234- def iter_items (cls , repo : 'Repo' , rev , # type: ignore
242+ def iter_items (cls , repo : 'Repo' , rev : str , # type: ignore
235243 paths : Union [PathLike , Sequence [PathLike ]] = '' , ** kwargs : Any
236244 ) -> Iterator ['Commit' ]:
237245 """Find all commits matching the given criteria.
@@ -254,7 +262,7 @@ def iter_items(cls, repo: 'Repo', rev,
254262 # use -- in any case, to prevent possibility of ambiguous arguments
255263 # see https://github.com/gitpython-developers/GitPython/issues/264
256264
257- args_list : List [Union [ PathLike , Sequence [ PathLike ]] ] = ['--' ]
265+ args_list : List [PathLike ] = ['--' ]
258266
259267 if paths :
260268 paths_tup : Tuple [PathLike , ...]
@@ -286,7 +294,7 @@ def iter_parents(self, paths: Union[PathLike, Sequence[PathLike]] = '', **kwargs
286294 return self .iter_items (self .repo , self , paths , ** kwargs )
287295
288296 @ property
289- def stats (self ):
297+ def stats (self ) -> Stats :
290298 """Create a git stat from changes between this commit and its first parent
291299 or from all changes done if this is the very first commit.
292300
@@ -303,16 +311,25 @@ def stats(self):
303311 return Stats ._list_from_string (self .repo , text )
304312
305313 @ classmethod
306- def _iter_from_process_or_stream (cls , repo , proc_or_stream ) :
314+ def _iter_from_process_or_stream (cls , repo : 'Repo' , proc_or_stream : Union [ Popen , IO ]) -> Iterator [ 'Commit' ] :
307315 """Parse out commit information into a list of Commit objects
308316 We expect one-line per commit, and parse the actual commit information directly
309317 from our lighting fast object database
310318
311319 :param proc: git-rev-list process instance - one sha per line
312320 :return: iterator returning Commit objects"""
313- stream = proc_or_stream
314- if not hasattr (stream , 'readline' ):
315- stream = proc_or_stream .stdout
321+
322+ def is_proc (inp ) -> TypeGuard [Popen ]:
323+ return hasattr (proc_or_stream , 'wait' ) and not hasattr (proc_or_stream , 'readline' )
324+
325+ def is_stream (inp ) -> TypeGuard [IO ]:
326+ return hasattr (proc_or_stream , 'readline' )
327+
328+ if is_proc (proc_or_stream ):
329+ if proc_or_stream .stdout is not None :
330+ stream = proc_or_stream .stdout
331+ elif is_stream (proc_or_stream ):
332+ stream = proc_or_stream
316333
317334 readline = stream .readline
318335 while True :
@@ -330,19 +347,21 @@ def _iter_from_process_or_stream(cls, repo, proc_or_stream):
330347 # END for each line in stream
331348 # TODO: Review this - it seems process handling got a bit out of control
332349 # due to many developers trying to fix the open file handles issue
333- if hasattr (proc_or_stream , 'wait' ):
350+ if is_proc (proc_or_stream ):
334351 finalize_process (proc_or_stream )
335352
336353 @ classmethod
337- def create_from_tree (cls , repo , tree , message , parent_commits = None , head = False , author = None , committer = None ,
338- author_date = None , commit_date = None ):
354+ def create_from_tree (cls , repo : 'Repo' , tree : Union ['Tree' , str ], message : str ,
355+ parent_commits : Union [None , List ['Commit' ]] = None , head : bool = False ,
356+ author : Union [None , Actor ] = None , committer : Union [None , Actor ] = None ,
357+ author_date : Union [None , str ] = None , commit_date : Union [None , str ] = None ):
339358 """Commit the given tree, creating a commit object.
340359
341360 :param repo: Repo object the commit should be part of
342361 :param tree: Tree object or hex or bin sha
343362 the tree of the new commit
344363 :param message: Commit message. It may be an empty string if no message is provided.
345- It will be converted to a string in any case.
364+ It will be converted to a string , in any case.
346365 :param parent_commits:
347366 Optional Commit objects to use as parents for the new commit.
348367 If empty list, the commit will have no parents at all and become
@@ -476,7 +495,7 @@ def _serialize(self, stream: BytesIO) -> 'Commit':
476495 write (("encoding %s\n " % self .encoding ).encode ('ascii' ))
477496
478497 try :
479- if self .__getattribute__ ('gpgsig' ) is not None :
498+ if self .__getattribute__ ('gpgsig' ):
480499 write (b"gpgsig" )
481500 for sigline in self .gpgsig .rstrip ("\n " ).split ("\n " ):
482501 write ((" " + sigline + "\n " ).encode ('ascii' ))
@@ -526,7 +545,7 @@ def _deserialize(self, stream: BytesIO) -> 'Commit':
526545 # now we can have the encoding line, or an empty line followed by the optional
527546 # message.
528547 self .encoding = self .default_encoding
529- self .gpgsig = None
548+ self .gpgsig = ""
530549
531550 # read headers
532551 enc = next_line
@@ -555,7 +574,7 @@ def _deserialize(self, stream: BytesIO) -> 'Commit':
555574 # decode the authors name
556575
557576 try :
558- self .author , self .authored_date , self .author_tz_offset = \
577+ ( self .author , self .authored_date , self .author_tz_offset ) = \
559578 parse_actor_and_date (author_line .decode (self .encoding , 'replace' ))
560579 except UnicodeDecodeError :
561580 log .error ("Failed to decode author line '%s' using encoding %s" , author_line , self .encoding ,
@@ -571,11 +590,12 @@ def _deserialize(self, stream: BytesIO) -> 'Commit':
571590
572591 # a stream from our data simply gives us the plain message
573592 # The end of our message stream is marked with a newline that we strip
574- self .message = stream .read ()
593+ self .message_bytes = stream .read ()
575594 try :
576- self .message = self .message .decode (self .encoding , 'replace' )
595+ self .message = self .message_bytes .decode (self .encoding , 'replace' )
577596 except UnicodeDecodeError :
578- log .error ("Failed to decode message '%s' using encoding %s" , self .message , self .encoding , exc_info = True )
597+ log .error ("Failed to decode message '%s' using encoding %s" ,
598+ self .message_bytes , self .encoding , exc_info = True )
579599 # END exception handling
580600
581601 return self
0 commit comments