4747find_first_remote_branch
4848)
4949
50+ from git .repo import Repo
5051
5152# typing ----------------------------------------------------------------------
52- from typing import Dict ,TYPE_CHECKING
53+ from typing import Callable , Dict , Mapping , Sequence ,TYPE_CHECKING
5354from typing import Any ,Iterator ,Union
5455
55- from git .types import Commit_ish ,PathLike
56+ from git .types import Commit_ish ,PathLike , TBD
5657
5758if TYPE_CHECKING :
58- from git .repo import Repo
59+ from git .index import IndexFile
5960
6061
6162# -----------------------------------------------------------------------------
@@ -131,14 +132,14 @@ def __init__(self, repo: 'Repo', binsha: bytes,
131132if url is not None :
132133self ._url = url
133134if branch_path is not None :
134- assert isinstance (branch_path ,str )
135+ # assert isinstance(branch_path, str)
135136self ._branch_path = branch_path
136137if name is not None :
137138self ._name = name
138139
139140def _set_cache_ (self ,attr :str )-> None :
140141if attr in ('path' ,'_url' ,'_branch_path' ):
141- reader = self .config_reader ()
142+ reader : SectionConstraint = self .config_reader ()
142143# default submodule values
143144try :
144145self .path = reader .get ('path' )
@@ -226,7 +227,7 @@ def _config_parser(cls, repo: 'Repo',
226227
227228return SubmoduleConfigParser (fp_module ,read_only = read_only )
228229
229- def _clear_cache (self ):
230+ def _clear_cache (self )-> None :
230231# clear the possibly changed values
231232for name in self ._cache_attrs :
232233try :
@@ -246,7 +247,7 @@ def _sio_modules(cls, parent_commit: Commit_ish) -> BytesIO:
246247def _config_parser_constrained (self ,read_only :bool )-> SectionConstraint :
247248""":return: Config Parser constrained to our submodule in read or write mode"""
248249try :
249- pc = self .parent_commit
250+ pc : Union [ 'Commit_ish' , None ] = self .parent_commit
250251except ValueError :
251252pc = None
252253# end handle empty parent repository
@@ -255,10 +256,12 @@ def _config_parser_constrained(self, read_only: bool) -> SectionConstraint:
255256return SectionConstraint (parser ,sm_section (self .name ))
256257
257258@classmethod
258- def _module_abspath (cls ,parent_repo ,path ,name ) :
259+ def _module_abspath (cls ,parent_repo : 'Repo' ,path : PathLike ,name : str ) -> PathLike :
259260if cls ._need_gitfile_submodules (parent_repo .git ):
260261return osp .join (parent_repo .git_dir ,'modules' ,name )
261- return osp .join (parent_repo .working_tree_dir ,path )
262+ if parent_repo .working_tree_dir :
263+ return osp .join (parent_repo .working_tree_dir ,path )
264+ raise NotADirectoryError ()
262265# end
263266
264267@classmethod
@@ -286,15 +289,15 @@ def _clone_repo(cls, repo, url, path, name, **kwargs):
286289return clone
287290
288291@classmethod
289- def _to_relative_path (cls ,parent_repo ,path ) :
292+ def _to_relative_path (cls ,parent_repo : 'Repo' ,path : PathLike ) -> PathLike :
290293""":return: a path guaranteed to be relative to the given parent - repository
291294 :raise ValueError: if path is not contained in the parent repository's working tree"""
292295path = to_native_path_linux (path )
293296if path .endswith ('/' ):
294297path = path [:- 1 ]
295298# END handle trailing slash
296299
297- if osp .isabs (path ):
300+ if osp .isabs (path )and parent_repo . working_tree_dir :
298301working_tree_linux = to_native_path_linux (parent_repo .working_tree_dir )
299302if not path .startswith (working_tree_linux ):
300303raise ValueError ("Submodule checkout path '%s' needs to be within the parents repository at '%s'"
@@ -308,7 +311,7 @@ def _to_relative_path(cls, parent_repo, path):
308311return path
309312
310313@classmethod
311- def _write_git_file_and_module_config (cls ,working_tree_dir ,module_abspath ) :
314+ def _write_git_file_and_module_config (cls ,working_tree_dir : PathLike ,module_abspath : PathLike ) -> None :
312315"""Writes a .git file containing a(preferably) relative path to the actual git module repository.
313316 It is an error if the module_abspath cannot be made into a relative path, relative to the working_tree_dir
314317 :note: will overwrite existing files !
@@ -335,7 +338,8 @@ def _write_git_file_and_module_config(cls, working_tree_dir, module_abspath):
335338
336339@classmethod
337340def add (cls ,repo :'Repo' ,name :str ,path :PathLike ,url :Union [str ,None ]= None ,
338- branch = None ,no_checkout :bool = False ,depth = None ,env = None ,clone_multi_options = None
341+ branch :Union [str ,None ]= None ,no_checkout :bool = False ,depth :Union [int ,None ]= None ,
342+ env :Mapping [str ,str ]= None ,clone_multi_options :Union [Sequence [TBD ],None ]= None
339343 )-> 'Submodule' :
340344"""Add a new submodule to the given repository. This will alter the index
341345 as well as the .gitmodules file, but will not create a new commit.
@@ -391,7 +395,7 @@ def add(cls, repo: 'Repo', name: str, path: PathLike, url: Union[str, None] = No
391395if sm .exists ():
392396# reretrieve submodule from tree
393397try :
394- sm = repo .head .commit .tree [path ] # type: ignore
398+ sm = repo .head .commit .tree [str ( path )]
395399sm ._name = name
396400return sm
397401except KeyError :
@@ -414,7 +418,8 @@ def add(cls, repo: 'Repo', name: str, path: PathLike, url: Union[str, None] = No
414418# END check url
415419# END verify urls match
416420
417- mrepo = None
421+ mrepo :Union [Repo ,None ]= None
422+
418423if url is None :
419424if not has_module :
420425raise ValueError ("A URL was not given and a repository did not exist at %s" % path )
@@ -427,7 +432,7 @@ def add(cls, repo: 'Repo', name: str, path: PathLike, url: Union[str, None] = No
427432url = urls [0 ]
428433else :
429434# clone new repo
430- kwargs :Dict [str ,Union [bool ,int ]]= {'n' :no_checkout }
435+ kwargs :Dict [str ,Union [bool ,int , Sequence [ TBD ] ]]= {'n' :no_checkout }
431436if not branch_is_default :
432437kwargs ['b' ]= br .name
433438# END setup checkout-branch
@@ -451,6 +456,8 @@ def add(cls, repo: 'Repo', name: str, path: PathLike, url: Union[str, None] = No
451456# otherwise there is a '-' character in front of the submodule listing
452457# a38efa84daef914e4de58d1905a500d8d14aaf45 mymodule (v0.9.0-1-ga38efa8)
453458# -a38efa84daef914e4de58d1905a500d8d14aaf45 submodules/intermediate/one
459+ writer :Union [GitConfigParser ,SectionConstraint ]
460+
454461with sm .repo .config_writer ()as writer :
455462writer .set_value (sm_section (name ),'url' ,url )
456463
@@ -467,13 +474,15 @@ def add(cls, repo: 'Repo', name: str, path: PathLike, url: Union[str, None] = No
467474sm ._branch_path = br .path
468475
469476# we deliberately assume that our head matches our index !
470- sm .binsha = mrepo .head .commit .binsha
477+ sm .binsha = mrepo .head .commit .binsha # type: ignore
471478index .add ([sm ],write = True )
472479
473480return sm
474481
475- def update (self ,recursive = False ,init = True ,to_latest_revision = False ,progress = None ,dry_run = False ,
476- force = False ,keep_going = False ,env = None ,clone_multi_options = None ):
482+ def update (self ,recursive :bool = False ,init :bool = True ,to_latest_revision :bool = False ,
483+ progress :Union ['UpdateProgress' ,None ]= None ,dry_run :bool = False ,
484+ force :bool = False ,keep_going :bool = False ,env :Mapping [str ,str ]= None ,
485+ clone_multi_options :Union [Sequence [TBD ],None ]= None ):
477486"""Update the repository of this submodule to point to the checkout
478487 we point at with the binsha of this instance.
479488
@@ -580,6 +589,7 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress=
580589if not dry_run :
581590# see whether we have a valid branch to checkout
582591try :
592+ assert isinstance (mrepo ,Repo )
583593# find a remote which has our branch - we try to be flexible
584594remote_branch = find_first_remote_branch (mrepo .remotes ,self .branch_name )
585595local_branch = mkhead (mrepo ,self .branch_path )
@@ -640,7 +650,7 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress=
640650may_reset = True
641651if mrepo .head .commit .binsha != self .NULL_BIN_SHA :
642652base_commit = mrepo .merge_base (mrepo .head .commit ,hexsha )
643- if len (base_commit )== 0 or base_commit [0 ].hexsha == hexsha :
653+ if len (base_commit )== 0 or ( base_commit [0 ]is not None and base_commit [ 0 ] .hexsha == hexsha ) :
644654if force :
645655msg = "Will force checkout or reset on local branch that is possibly in the future of"
646656msg += "the commit it will be checked out to, effectively 'forgetting' new commits"
@@ -807,7 +817,8 @@ def move(self, module_path, configuration=True, module=True):
807817return self
808818
809819@unbare_repo
810- def remove (self ,module = True ,force = False ,configuration = True ,dry_run = False ):
820+ def remove (self ,module :bool = True ,force :bool = False ,
821+ configuration :bool = True ,dry_run :bool = False )-> 'Submodule' :
811822"""Remove this submodule from the repository. This will remove our entry
812823 from the .gitmodules file and the entry in the .git / config file.
813824
@@ -861,7 +872,7 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False):
861872# TODO: If we run into permission problems, we have a highly inconsistent
862873# state. Delete the .git folders last, start with the submodules first
863874mp = self .abspath
864- method = None
875+ method : Union [ None , Callable [[ PathLike ], None ]] = None
865876if osp .islink (mp ):
866877method = os .remove
867878elif osp .isdir (mp ):
@@ -914,7 +925,7 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False):
914925import gc
915926gc .collect ()
916927try :
917- rmtree (wtd )
928+ rmtree (str ( wtd ) )
918929except Exception as ex :
919930if HIDE_WINDOWS_KNOWN_ERRORS :
920931raise SkipTest ("FIXME: fails with: PermissionError\n {}" .format (ex ))from ex
@@ -928,7 +939,7 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False):
928939rmtree (git_dir )
929940except Exception as ex :
930941if HIDE_WINDOWS_KNOWN_ERRORS :
931- raise SkipTest ("FIXME: fails with: PermissionError\n %s" , ex )from ex
942+ raise SkipTest (f "FIXME: fails with: PermissionError\n { ex } " )from ex
932943else :
933944raise
934945# end handle separate bare repository
@@ -952,6 +963,8 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False):
952963
953964# now git config - need the config intact, otherwise we can't query
954965# information anymore
966+ writer :Union [GitConfigParser ,SectionConstraint ]
967+
955968with self .repo .config_writer ()as writer :
956969writer .remove_section (sm_section (self .name ))
957970
@@ -961,7 +974,7 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False):
961974
962975return self
963976
964- def set_parent_commit (self ,commit :Union [Commit_ish ,None ],check = True ):
977+ def set_parent_commit (self ,commit :Union [Commit_ish ,None ],check : bool = True )-> 'Submodule' :
965978"""Set this instance to use the given commit whose tree is supposed to
966979 contain the .gitmodules blob.
967980
@@ -1009,7 +1022,7 @@ def set_parent_commit(self, commit: Union[Commit_ish, None], check=True):
10091022return self
10101023
10111024@unbare_repo
1012- def config_writer (self ,index = None ,write = True ):
1025+ def config_writer (self ,index : Union [ 'IndexFile' , None ] = None ,write : bool = True )-> SectionConstraint :
10131026""":return: a config writer instance allowing you to read and write the data
10141027 belonging to this submodule into the .gitmodules file.
10151028
@@ -1030,7 +1043,7 @@ def config_writer(self, index=None, write=True):
10301043return writer
10311044
10321045@unbare_repo
1033- def rename (self ,new_name ) :
1046+ def rename (self ,new_name : str ) -> 'Submodule' :
10341047"""Rename this submodule
10351048 :note: This method takes care of renaming the submodule in various places, such as
10361049
@@ -1065,13 +1078,14 @@ def rename(self, new_name):
10651078destination_module_abspath = self ._module_abspath (self .repo ,self .path ,new_name )
10661079source_dir = mod .git_dir
10671080# Let's be sure the submodule name is not so obviously tied to a directory
1068- if destination_module_abspath .startswith (mod .git_dir ):
1081+ if str ( destination_module_abspath ) .startswith (str ( mod .git_dir ) ):
10691082tmp_dir = self ._module_abspath (self .repo ,self .path ,str (uuid .uuid4 ()))
10701083os .renames (source_dir ,tmp_dir )
10711084source_dir = tmp_dir
10721085# end handle self-containment
10731086os .renames (source_dir ,destination_module_abspath )
1074- self ._write_git_file_and_module_config (mod .working_tree_dir ,destination_module_abspath )
1087+ if mod .working_tree_dir :
1088+ self ._write_git_file_and_module_config (mod .working_tree_dir ,destination_module_abspath )
10751089# end move separate git repository
10761090
10771091return self
@@ -1081,7 +1095,7 @@ def rename(self, new_name):
10811095#{ Query Interface
10821096
10831097@unbare_repo
1084- def module (self ):
1098+ def module (self )-> 'Repo' :
10851099""":return: Repo instance initialized from the repository at our submodule path
10861100 :raise InvalidGitRepositoryError: if a repository was not available. This could
10871101 also mean that it was not yet initialized"""
@@ -1098,7 +1112,7 @@ def module(self):
10981112raise InvalidGitRepositoryError ("Repository at %r was not yet checked out" % module_checkout_abspath )
10991113# END handle exceptions
11001114
1101- def module_exists (self ):
1115+ def module_exists (self )-> bool :
11021116""":return: True if our module exists and is a valid git repository. See module() method"""
11031117try :
11041118self .module ()
@@ -1107,7 +1121,7 @@ def module_exists(self):
11071121return False
11081122# END handle exception
11091123
1110- def exists (self ):
1124+ def exists (self )-> bool :
11111125"""
11121126 :return: True if the submodule exists, False otherwise. Please note that
11131127 a submodule may exist ( in the .gitmodules file) even though its module
@@ -1148,34 +1162,34 @@ def branch(self):
11481162return mkhead (self .module (),self ._branch_path )
11491163
11501164@property
1151- def branch_path (self ):
1165+ def branch_path (self )-> PathLike :
11521166"""
11531167 :return: full(relative) path as string to the branch we would checkout
11541168 from the remote and track"""
11551169return self ._branch_path
11561170
11571171@property
1158- def branch_name (self ):
1172+ def branch_name (self )-> str :
11591173""":return: the name of the branch, which is the shortest possible branch name"""
11601174# use an instance method, for this we create a temporary Head instance
11611175# which uses a repository that is available at least ( it makes no difference )
11621176return git .Head (self .repo ,self ._branch_path ).name
11631177
11641178@property
1165- def url (self ):
1179+ def url (self )-> str :
11661180""":return: The url to the repository which our module - repository refers to"""
11671181return self ._url
11681182
11691183@property
1170- def parent_commit (self ):
1184+ def parent_commit (self )-> 'Commit_ish' :
11711185""":return: Commit instance with the tree containing the .gitmodules file
11721186 :note: will always point to the current head's commit if it was not set explicitly"""
11731187if self ._parent_commit is None :
11741188return self .repo .commit ()
11751189return self ._parent_commit
11761190
11771191@property
1178- def name (self ):
1192+ def name (self )-> str :
11791193""":return: The name of this submodule. It is used to identify it within the
11801194 .gitmodules file.
11811195 :note: by default, the name is the path at which to find the submodule, but