3
3
#
4
4
# This module is part of GitPython and is released under
5
5
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
6
+ import datetime
7
+ from subprocess import Popen
6
8
from gitdb import IStream
7
9
from git .util import (
8
10
hex_to_bin ,
37
39
38
40
# typing ------------------------------------------------------------------
39
41
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
41
43
42
- from git .types import PathLike
44
+ from git .types import PathLike , TypeGuard
43
45
44
46
if TYPE_CHECKING :
45
47
from git .repo import Repo
@@ -78,11 +80,17 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
78
80
"message" , "parents" , "encoding" , "gpgsig" )
79
81
_id_attribute_ = "hexsha"
80
82
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 :
86
94
"""Instantiate a new Commit. All keyword arguments taking None as default will
87
95
be implicitly set on first query.
88
96
@@ -164,7 +172,7 @@ def _calculate_sha_(cls, repo: 'Repo', commit: 'Commit') -> bytes:
164
172
istream = repo .odb .store (IStream (cls .type , streamlen , stream ))
165
173
return istream .binsha
166
174
167
- def replace (self , ** kwargs ) :
175
+ def replace (self , ** kwargs : Any ) -> 'Commit' :
168
176
'''Create new commit object from existing commit object.
169
177
170
178
Any values provided as keyword arguments will replace the
@@ -183,7 +191,7 @@ def replace(self, **kwargs):
183
191
184
192
return new_commit
185
193
186
- def _set_cache_ (self , attr ) :
194
+ def _set_cache_ (self , attr : str ) -> None :
187
195
if attr in Commit .__slots__ :
188
196
# read the data in a chunk, its faster - then provide a file wrapper
189
197
_binsha , _typename , self .size , stream = self .repo .odb .stream (self .binsha )
@@ -193,19 +201,19 @@ def _set_cache_(self, attr):
193
201
# END handle attrs
194
202
195
203
@property
196
- def authored_datetime (self ):
204
+ def authored_datetime (self ) -> 'datetime.datetime' :
197
205
return from_timestamp (self .authored_date , self .author_tz_offset )
198
206
199
207
@property
200
- def committed_datetime (self ):
208
+ def committed_datetime (self ) -> 'datetime.datetime' :
201
209
return from_timestamp (self .committed_date , self .committer_tz_offset )
202
210
203
211
@property
204
- def summary (self ):
212
+ def summary (self ) -> str :
205
213
""":return: First line of the commit message"""
206
214
return self .message .split ('\n ' , 1 )[0 ]
207
215
208
- def count (self , paths = '' , ** kwargs ) :
216
+ def count (self , paths : Union [ PathLike , Sequence [ PathLike ]] = '' , ** kwargs : Any ) -> int :
209
217
"""Count the number of commits reachable from this commit
210
218
211
219
:param paths:
@@ -223,15 +231,15 @@ def count(self, paths='', **kwargs):
223
231
return len (self .repo .git .rev_list (self .hexsha , ** kwargs ).splitlines ())
224
232
225
233
@property
226
- def name_rev (self ):
234
+ def name_rev (self ) -> str :
227
235
"""
228
236
:return:
229
237
String describing the commits hex sha based on the closest Reference.
230
238
Mostly useful for UI purposes"""
231
239
return self .repo .git .name_rev (self )
232
240
233
241
@classmethod
234
- def iter_items (cls , repo : 'Repo' , rev , # type: ignore
242
+ def iter_items (cls , repo : 'Repo' , rev : str , # type: ignore
235
243
paths : Union [PathLike , Sequence [PathLike ]] = '' , ** kwargs : Any
236
244
) -> Iterator ['Commit' ]:
237
245
"""Find all commits matching the given criteria.
@@ -254,7 +262,7 @@ def iter_items(cls, repo: 'Repo', rev,
254
262
# use -- in any case, to prevent possibility of ambiguous arguments
255
263
# see https://github.com/gitpython-developers/GitPython/issues/264
256
264
257
- args_list : List [Union [ PathLike , Sequence [ PathLike ]] ] = ['--' ]
265
+ args_list : List [PathLike ] = ['--' ]
258
266
259
267
if paths :
260
268
paths_tup : Tuple [PathLike , ...]
@@ -286,7 +294,7 @@ def iter_parents(self, paths: Union[PathLike, Sequence[PathLike]] = '', **kwargs
286
294
return self .iter_items (self .repo , self , paths , ** kwargs )
287
295
288
296
@ property
289
- def stats (self ):
297
+ def stats (self ) -> Stats :
290
298
"""Create a git stat from changes between this commit and its first parent
291
299
or from all changes done if this is the very first commit.
292
300
@@ -303,16 +311,25 @@ def stats(self):
303
311
return Stats ._list_from_string (self .repo , text )
304
312
305
313
@ 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' ] :
307
315
"""Parse out commit information into a list of Commit objects
308
316
We expect one-line per commit, and parse the actual commit information directly
309
317
from our lighting fast object database
310
318
311
319
:param proc: git-rev-list process instance - one sha per line
312
320
: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
316
333
317
334
readline = stream .readline
318
335
while True :
@@ -330,19 +347,21 @@ def _iter_from_process_or_stream(cls, repo, proc_or_stream):
330
347
# END for each line in stream
331
348
# TODO: Review this - it seems process handling got a bit out of control
332
349
# 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 ):
334
351
finalize_process (proc_or_stream )
335
352
336
353
@ 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 ):
339
358
"""Commit the given tree, creating a commit object.
340
359
341
360
:param repo: Repo object the commit should be part of
342
361
:param tree: Tree object or hex or bin sha
343
362
the tree of the new commit
344
363
: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.
346
365
:param parent_commits:
347
366
Optional Commit objects to use as parents for the new commit.
348
367
If empty list, the commit will have no parents at all and become
@@ -476,7 +495,7 @@ def _serialize(self, stream: BytesIO) -> 'Commit':
476
495
write (("encoding %s\n " % self .encoding ).encode ('ascii' ))
477
496
478
497
try :
479
- if self .__getattribute__ ('gpgsig' ) is not None :
498
+ if self .__getattribute__ ('gpgsig' ):
480
499
write (b"gpgsig" )
481
500
for sigline in self .gpgsig .rstrip ("\n " ).split ("\n " ):
482
501
write ((" " + sigline + "\n " ).encode ('ascii' ))
@@ -526,7 +545,7 @@ def _deserialize(self, stream: BytesIO) -> 'Commit':
526
545
# now we can have the encoding line, or an empty line followed by the optional
527
546
# message.
528
547
self .encoding = self .default_encoding
529
- self .gpgsig = None
548
+ self .gpgsig = ""
530
549
531
550
# read headers
532
551
enc = next_line
@@ -555,7 +574,7 @@ def _deserialize(self, stream: BytesIO) -> 'Commit':
555
574
# decode the authors name
556
575
557
576
try :
558
- self .author , self .authored_date , self .author_tz_offset = \
577
+ ( self .author , self .authored_date , self .author_tz_offset ) = \
559
578
parse_actor_and_date (author_line .decode (self .encoding , 'replace' ))
560
579
except UnicodeDecodeError :
561
580
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':
571
590
572
591
# a stream from our data simply gives us the plain message
573
592
# 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 ()
575
594
try :
576
- self .message = self .message .decode (self .encoding , 'replace' )
595
+ self .message = self .message_bytes .decode (self .encoding , 'replace' )
577
596
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 )
579
599
# END exception handling
580
600
581
601
return self
0 commit comments