63
63
git_working_dir
64
64
)
65
65
66
+ # typing -----------------------------------------------------------------------------
67
+
68
+ from typing import Any , Callable , Dict , IO , Iterator , List , Sequence , TYPE_CHECKING , Tuple , Union
69
+
70
+ from git .types import PathLike , TBD
71
+
72
+ if TYPE_CHECKING :
73
+ from subprocess import Popen
74
+ from git .repo import Repo
75
+
76
+ StageType = int
77
+ Treeish = Union [Tree , Commit , bytes ]
78
+
66
79
67
80
__all__ = ('IndexFile' , 'CheckoutError' )
68
81
@@ -93,7 +106,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
93
106
_VERSION = 2 # latest version we support
94
107
S_IFGITLINK = S_IFGITLINK # a submodule
95
108
96
- def __init__ (self , repo , file_path = None ):
109
+ def __init__ (self , repo : 'Repo' , file_path : PathLike = None ) -> None :
97
110
"""Initialize this Index instance, optionally from the given ``file_path``.
98
111
If no file_path is given, we will be created from the current index file.
99
112
@@ -102,9 +115,9 @@ def __init__(self, repo, file_path=None):
102
115
self .repo = repo
103
116
self .version = self ._VERSION
104
117
self ._extension_data = b''
105
- self ._file_path = file_path or self ._index_path ()
118
+ self ._file_path = file_path or self ._index_path () # type: PathLike
106
119
107
- def _set_cache_ (self , attr ) :
120
+ def _set_cache_ (self , attr : str ) -> None :
108
121
if attr == "entries" :
109
122
# read the current index
110
123
# try memory map for speed
@@ -115,8 +128,8 @@ def _set_cache_(self, attr):
115
128
ok = True
116
129
except OSError :
117
130
# in new repositories, there may be no index, which means we are empty
118
- self .entries = {}
119
- return
131
+ self .entries = {} # type: Dict[Tuple[PathLike, StageType], IndexEntry]
132
+ return None
120
133
finally :
121
134
if not ok :
122
135
lfd .rollback ()
@@ -133,15 +146,18 @@ def _set_cache_(self, attr):
133
146
else :
134
147
super (IndexFile , self )._set_cache_ (attr )
135
148
136
- def _index_path (self ):
137
- return join_path_native (self .repo .git_dir , "index" )
149
+ def _index_path (self ) -> PathLike :
150
+ if self .repo .git_dir :
151
+ return join_path_native (self .repo .git_dir , "index" )
152
+ else :
153
+ raise GitCommandError ("No git directory given to join index path" )
138
154
139
155
@property
140
- def path (self ):
156
+ def path (self ) -> PathLike :
141
157
""" :return: Path to the index file we are representing """
142
158
return self ._file_path
143
159
144
- def _delete_entries_cache (self ):
160
+ def _delete_entries_cache (self ) -> None :
145
161
"""Safely clear the entries cache so it can be recreated"""
146
162
try :
147
163
del (self .entries )
@@ -152,26 +168,26 @@ def _delete_entries_cache(self):
152
168
153
169
#{ Serializable Interface
154
170
155
- def _deserialize (self , stream ) :
171
+ def _deserialize (self , stream : IO ) -> 'IndexFile' :
156
172
"""Initialize this instance with index values read from the given stream"""
157
173
self .version , self .entries , self ._extension_data , _conten_sha = read_cache (stream )
158
174
return self
159
175
160
- def _entries_sorted (self ):
176
+ def _entries_sorted (self ) -> List [ TBD ] :
161
177
""":return: list of entries, in a sorted fashion, first by path, then by stage"""
162
178
return sorted (self .entries .values (), key = lambda e : (e .path , e .stage ))
163
179
164
- def _serialize (self , stream , ignore_extension_data = False ):
180
+ def _serialize (self , stream : IO , ignore_extension_data : bool = False ) -> 'IndexFile' :
165
181
entries = self ._entries_sorted ()
166
- extension_data = self ._extension_data
182
+ extension_data = self ._extension_data # type: Union[None, bytes]
167
183
if ignore_extension_data :
168
184
extension_data = None
169
185
write_cache (entries , stream , extension_data )
170
186
return self
171
187
172
188
#} END serializable interface
173
189
174
- def write (self , file_path = None , ignore_extension_data = False ):
190
+ def write (self , file_path : Union [ None , PathLike ] = None , ignore_extension_data : bool = False ) -> None :
175
191
"""Write the current state to our file path or to the given one
176
192
177
193
:param file_path:
@@ -191,7 +207,7 @@ def write(self, file_path=None, ignore_extension_data=False):
191
207
Alternatively, use IndexFile.write_tree() to handle this case
192
208
automatically
193
209
194
- :return: self"""
210
+ :return: self # does it? or returns None? """
195
211
# make sure we have our entries read before getting a write lock
196
212
# else it would be done when streaming. This can happen
197
213
# if one doesn't change the index, but writes it right away
@@ -215,7 +231,7 @@ def write(self, file_path=None, ignore_extension_data=False):
215
231
216
232
@post_clear_cache
217
233
@default_index
218
- def merge_tree (self , rhs , base = None ):
234
+ def merge_tree (self , rhs : Treeish , base : Union [ None , Treeish ] = None ) -> 'IndexFile' :
219
235
"""Merge the given rhs treeish into the current index, possibly taking
220
236
a common base treeish into account.
221
237
@@ -242,7 +258,7 @@ def merge_tree(self, rhs, base=None):
242
258
# -i : ignore working tree status
243
259
# --aggressive : handle more merge cases
244
260
# -m : do an actual merge
245
- args = ["--aggressive" , "-i" , "-m" ]
261
+ args = ["--aggressive" , "-i" , "-m" ] # type: List[Union[Treeish, str]]
246
262
if base is not None :
247
263
args .append (base )
248
264
args .append (rhs )
@@ -251,7 +267,7 @@ def merge_tree(self, rhs, base=None):
251
267
return self
252
268
253
269
@classmethod
254
- def new (cls , repo , * tree_sha ) :
270
+ def new (cls , repo : 'Repo' , * tree_sha : bytes ) -> 'IndexFile' :
255
271
""" Merge the given treeish revisions into a new index which is returned.
256
272
This method behaves like git-read-tree --aggressive when doing the merge.
257
273
@@ -275,7 +291,7 @@ def new(cls, repo, *tree_sha):
275
291
return inst
276
292
277
293
@classmethod
278
- def from_tree (cls , repo , * treeish , ** kwargs ) :
294
+ def from_tree (cls , repo : 'Repo' , * treeish : Treeish , ** kwargs : Any ) -> 'IndexFile' :
279
295
"""Merge the given treeish revisions into a new index which is returned.
280
296
The original index will remain unaltered
281
297
@@ -312,7 +328,7 @@ def from_tree(cls, repo, *treeish, **kwargs):
312
328
if len (treeish ) == 0 or len (treeish ) > 3 :
313
329
raise ValueError ("Please specify between 1 and 3 treeish, got %i" % len (treeish ))
314
330
315
- arg_list = []
331
+ arg_list = [] # type: List[Union[Treeish, str]]
316
332
# ignore that working tree and index possibly are out of date
317
333
if len (treeish ) > 1 :
318
334
# drop unmerged entries when reading our index and merging
@@ -331,7 +347,8 @@ def from_tree(cls, repo, *treeish, **kwargs):
331
347
# as it considers existing entries. moving it essentially clears the index.
332
348
# Unfortunately there is no 'soft' way to do it.
333
349
# The TemporaryFileSwap assure the original file get put back
334
- index_handler = TemporaryFileSwap (join_path_native (repo .git_dir , 'index' ))
350
+ if repo .git_dir :
351
+ index_handler = TemporaryFileSwap (join_path_native (repo .git_dir , 'index' ))
335
352
try :
336
353
repo .git .read_tree (* arg_list , ** kwargs )
337
354
index = cls (repo , tmp_index )
@@ -346,18 +363,18 @@ def from_tree(cls, repo, *treeish, **kwargs):
346
363
347
364
# UTILITIES
348
365
@unbare_repo
349
- def _iter_expand_paths (self , paths ) :
366
+ def _iter_expand_paths (self , paths : Sequence [ PathLike ]) -> Iterator [ PathLike ] :
350
367
"""Expand the directories in list of paths to the corresponding paths accordingly,
351
368
352
369
Note: git will add items multiple times even if a glob overlapped
353
370
with manually specified paths or if paths where specified multiple
354
371
times - we respect that and do not prune"""
355
372
def raise_exc (e ):
356
373
raise e
357
- r = self .repo .working_tree_dir
374
+ r = str ( self .repo .working_tree_dir )
358
375
rs = r + os .sep
359
376
for path in paths :
360
- abs_path = path
377
+ abs_path = str ( path )
361
378
if not osp .isabs (abs_path ):
362
379
abs_path = osp .join (r , path )
363
380
# END make absolute path
@@ -374,7 +391,7 @@ def raise_exc(e):
374
391
# end check symlink
375
392
376
393
# if the path is not already pointing to an existing file, resolve globs if possible
377
- if not os .path .exists (path ) and ('?' in path or '*' in path or '[' in path ):
394
+ if not os .path .exists (abs_path ) and ('?' in abs_path or '*' in abs_path or '[' in abs_path ):
378
395
resolved_paths = glob .glob (abs_path )
379
396
# not abs_path in resolved_paths:
380
397
# a glob() resolving to the same path we are feeding it with
@@ -396,12 +413,12 @@ def raise_exc(e):
396
413
# END for each subdirectory
397
414
except OSError :
398
415
# was a file or something that could not be iterated
399
- yield path .replace (rs , '' )
416
+ yield abs_path .replace (rs , '' )
400
417
# END path exception handling
401
418
# END for each path
402
419
403
- def _write_path_to_stdin (self , proc , filepath , item , fmakeexc , fprogress ,
404
- read_from_stdout = True ):
420
+ def _write_path_to_stdin (self , proc : 'Popen' , filepath : PathLike , item , fmakeexc , fprogress ,
421
+ read_from_stdout : bool = True ) -> Union [ None , str ] :
405
422
"""Write path to proc.stdin and make sure it processes the item, including progress.
406
423
407
424
:return: stdout string
@@ -417,20 +434,24 @@ def _write_path_to_stdin(self, proc, filepath, item, fmakeexc, fprogress,
417
434
we will close stdin to break the pipe."""
418
435
419
436
fprogress (filepath , False , item )
420
- rval = None
421
- try :
422
- proc .stdin .write (("%s\n " % filepath ).encode (defenc ))
423
- except IOError as e :
424
- # pipe broke, usually because some error happened
425
- raise fmakeexc () from e
426
- # END write exception handling
427
- proc .stdin .flush ()
428
- if read_from_stdout :
437
+ rval = None # type: Union[None, str]
438
+
439
+ if proc .stdin is not None :
440
+ try :
441
+ proc .stdin .write (("%s\n " % filepath ).encode (defenc ))
442
+ except IOError as e :
443
+ # pipe broke, usually because some error happened
444
+ raise fmakeexc () from e
445
+ # END write exception handling
446
+ proc .stdin .flush ()
447
+
448
+ if read_from_stdout and proc .stdout is not None :
429
449
rval = proc .stdout .readline ().strip ()
430
450
fprogress (filepath , True , item )
431
451
return rval
432
452
433
- def iter_blobs (self , predicate = lambda t : True ):
453
+ def iter_blobs (self , predicate : Callable [[Tuple [StageType , Blob ]], bool ] = lambda t : True
454
+ ) -> Iterator [Tuple [StageType , Blob ]]:
434
455
"""
435
456
:return: Iterator yielding tuples of Blob objects and stages, tuple(stage, Blob)
436
457
@@ -446,20 +467,21 @@ def iter_blobs(self, predicate=lambda t: True):
446
467
yield output
447
468
# END for each entry
448
469
449
- def unmerged_blobs (self ):
470
+ def unmerged_blobs (self ) -> Dict [ PathLike , List [ Tuple [ StageType , Blob ]]] :
450
471
"""
451
472
:return:
452
473
Iterator yielding dict(path : list( tuple( stage, Blob, ...))), being
453
474
a dictionary associating a path in the index with a list containing
454
475
sorted stage/blob pairs
476
+ ##### Does it return iterator? or just the Dict?
455
477
456
478
:note:
457
479
Blobs that have been removed in one side simply do not exist in the
458
480
given stage. I.e. a file removed on the 'other' branch whose entries
459
481
are at stage 3 will not have a stage 3 entry.
460
482
"""
461
483
is_unmerged_blob = lambda t : t [0 ] != 0
462
- path_map = {}
484
+ path_map = {} # type: Dict[PathLike, List[Tuple[TBD, Blob]]]
463
485
for stage , blob in self .iter_blobs (is_unmerged_blob ):
464
486
path_map .setdefault (blob .path , []).append ((stage , blob ))
465
487
# END for each unmerged blob
@@ -468,10 +490,10 @@ def unmerged_blobs(self):
468
490
return path_map
469
491
470
492
@classmethod
471
- def entry_key (cls , * entry ) :
472
- return entry_key (* entry )
493
+ def entry_key (cls , entry : Union [ Tuple [ BaseIndexEntry ], Tuple [ PathLike , StageType ]]) -> Tuple [ PathLike , StageType ] :
494
+ return entry_key (entry )
473
495
474
- def resolve_blobs (self , iter_blobs ) :
496
+ def resolve_blobs (self , iter_blobs : Iterator [ Blob ]) -> 'IndexFile' :
475
497
"""Resolve the blobs given in blob iterator. This will effectively remove the
476
498
index entries of the respective path at all non-null stages and add the given
477
499
blob as new stage null blob.
@@ -489,9 +511,9 @@ def resolve_blobs(self, iter_blobs):
489
511
for blob in iter_blobs :
490
512
stage_null_key = (blob .path , 0 )
491
513
if stage_null_key in self .entries :
492
- raise ValueError ("Path %r already exists at stage 0" % blob .path )
514
+ raise ValueError ("Path %r already exists at stage 0" % str ( blob .path ) )
493
515
# END assert blob is not stage 0 already
494
-
516
+
495
517
# delete all possible stages
496
518
for stage in (1 , 2 , 3 ):
497
519
try :
@@ -506,7 +528,7 @@ def resolve_blobs(self, iter_blobs):
506
528
507
529
return self
508
530
509
- def update (self ):
531
+ def update (self ) -> 'IndexFile' :
510
532
"""Reread the contents of our index file, discarding all cached information
511
533
we might have.
512
534
@@ -517,7 +539,7 @@ def update(self):
517
539
# allows to lazily reread on demand
518
540
return self
519
541
520
- def write_tree (self ):
542
+ def write_tree (self ) -> Tree :
521
543
"""Writes this index to a corresponding Tree object into the repository's
522
544
object database and return it.
523
545
@@ -542,22 +564,22 @@ def write_tree(self):
542
564
root_tree ._cache = tree_items
543
565
return root_tree
544
566
545
- def _process_diff_args (self , args ) :
567
+ def _process_diff_args (self , args : Any ) -> List [ Any ] :
546
568
try :
547
569
args .pop (args .index (self ))
548
570
except IndexError :
549
571
pass
550
572
# END remove self
551
573
return args
552
574
553
- def _to_relative_path (self , path ) :
575
+ def _to_relative_path (self , path : PathLike ) -> PathLike :
554
576
""":return: Version of path relative to our git directory or raise ValueError
555
577
if it is not within our git direcotory"""
556
578
if not osp .isabs (path ):
557
579
return path
558
580
if self .repo .bare :
559
581
raise InvalidGitRepositoryError ("require non-bare repository" )
560
- if not path .startswith (self .repo .working_tree_dir ):
582
+ if not str ( path ) .startswith (str ( self .repo .working_tree_dir ) ):
561
583
raise ValueError ("Absolute path %r is not in git repository at %r" % (path , self .repo .working_tree_dir ))
562
584
return os .path .relpath (path , self .repo .working_tree_dir )
563
585
0 commit comments