Skip to content

Commit 3ce319f

Browse files
committed
Add types to submodule.update()
1 parent a935134 commit 3ce319f

File tree

4 files changed

+52
-32
lines changed

4 files changed

+52
-32
lines changed

git/objects/submodule/base.py

+35-23
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@
4949

5050

5151
# typing ----------------------------------------------------------------------
52-
from typing import Callable, Dict, TYPE_CHECKING
52+
from typing import Callable, Dict, Mapping, Sequence, TYPE_CHECKING
5353
from typing import Any, Iterator, Union
5454

55-
from git.types import Commit_ish, PathLike
55+
from git.types import Commit_ish, PathLike, TBD
5656

5757
if TYPE_CHECKING:
5858
from git.repo import Repo
@@ -227,7 +227,7 @@ def _config_parser(cls, repo: 'Repo',
227227

228228
return SubmoduleConfigParser(fp_module, read_only=read_only)
229229

230-
def _clear_cache(self):
230+
def _clear_cache(self) -> None:
231231
# clear the possibly changed values
232232
for name in self._cache_attrs:
233233
try:
@@ -247,7 +247,7 @@ def _sio_modules(cls, parent_commit: Commit_ish) -> BytesIO:
247247
def _config_parser_constrained(self, read_only: bool) -> SectionConstraint:
248248
""":return: Config Parser constrained to our submodule in read or write mode"""
249249
try:
250-
pc = self.parent_commit
250+
pc: Union['Commit_ish', None] = self.parent_commit
251251
except ValueError:
252252
pc = None
253253
# end handle empty parent repository
@@ -256,10 +256,12 @@ def _config_parser_constrained(self, read_only: bool) -> SectionConstraint:
256256
return SectionConstraint(parser, sm_section(self.name))
257257

258258
@classmethod
259-
def _module_abspath(cls, parent_repo, path, name):
259+
def _module_abspath(cls, parent_repo: 'Repo', path: PathLike, name: str) -> PathLike:
260260
if cls._need_gitfile_submodules(parent_repo.git):
261261
return osp.join(parent_repo.git_dir, 'modules', name)
262-
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()
263265
# end
264266

265267
@classmethod
@@ -287,15 +289,15 @@ def _clone_repo(cls, repo, url, path, name, **kwargs):
287289
return clone
288290

289291
@classmethod
290-
def _to_relative_path(cls, parent_repo, path):
292+
def _to_relative_path(cls, parent_repo: 'Repo', path: PathLike) -> PathLike:
291293
""":return: a path guaranteed to be relative to the given parent - repository
292294
:raise ValueError: if path is not contained in the parent repository's working tree"""
293295
path = to_native_path_linux(path)
294296
if path.endswith('/'):
295297
path = path[:-1]
296298
# END handle trailing slash
297299

298-
if osp.isabs(path):
300+
if osp.isabs(path) and parent_repo.working_tree_dir:
299301
working_tree_linux = to_native_path_linux(parent_repo.working_tree_dir)
300302
if not path.startswith(working_tree_linux):
301303
raise ValueError("Submodule checkout path '%s' needs to be within the parents repository at '%s'"
@@ -309,7 +311,7 @@ def _to_relative_path(cls, parent_repo, path):
309311
return path
310312

311313
@classmethod
312-
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:
313315
"""Writes a .git file containing a(preferably) relative path to the actual git module repository.
314316
It is an error if the module_abspath cannot be made into a relative path, relative to the working_tree_dir
315317
:note: will overwrite existing files !
@@ -336,7 +338,8 @@ def _write_git_file_and_module_config(cls, working_tree_dir, module_abspath):
336338

337339
@classmethod
338340
def add(cls, repo: 'Repo', name: str, path: PathLike, url: Union[str, None] = None,
339-
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
340343
) -> 'Submodule':
341344
"""Add a new submodule to the given repository. This will alter the index
342345
as well as the .gitmodules file, but will not create a new commit.
@@ -415,7 +418,8 @@ def add(cls, repo: 'Repo', name: str, path: PathLike, url: Union[str, None] = No
415418
# END check url
416419
# END verify urls match
417420

418-
mrepo = None
421+
# mrepo: Union[Repo, None] = None
422+
419423
if url is None:
420424
if not has_module:
421425
raise ValueError("A URL was not given and a repository did not exist at %s" % path)
@@ -428,7 +432,7 @@ def add(cls, repo: 'Repo', name: str, path: PathLike, url: Union[str, None] = No
428432
url = urls[0]
429433
else:
430434
# clone new repo
431-
kwargs: Dict[str, Union[bool, int]] = {'n': no_checkout}
435+
kwargs: Dict[str, Union[bool, int, Sequence[TBD]]] = {'n': no_checkout}
432436
if not branch_is_default:
433437
kwargs['b'] = br.name
434438
# END setup checkout-branch
@@ -452,6 +456,8 @@ def add(cls, repo: 'Repo', name: str, path: PathLike, url: Union[str, None] = No
452456
# otherwise there is a '-' character in front of the submodule listing
453457
# a38efa84daef914e4de58d1905a500d8d14aaf45 mymodule (v0.9.0-1-ga38efa8)
454458
# -a38efa84daef914e4de58d1905a500d8d14aaf45 submodules/intermediate/one
459+
writer: Union[GitConfigParser, SectionConstraint]
460+
455461
with sm.repo.config_writer() as writer:
456462
writer.set_value(sm_section(name), 'url', url)
457463

@@ -473,8 +479,10 @@ def add(cls, repo: 'Repo', name: str, path: PathLike, url: Union[str, None] = No
473479

474480
return sm
475481

476-
def update(self, recursive=False, init=True, to_latest_revision=False, progress=None, dry_run=False,
477-
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):
478486
"""Update the repository of this submodule to point to the checkout
479487
we point at with the binsha of this instance.
480488
@@ -581,6 +589,7 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress=
581589
if not dry_run:
582590
# see whether we have a valid branch to checkout
583591
try:
592+
assert isinstance(mrepo, Repo)
584593
# find a remote which has our branch - we try to be flexible
585594
remote_branch = find_first_remote_branch(mrepo.remotes, self.branch_name)
586595
local_branch = mkhead(mrepo, self.branch_path)
@@ -641,7 +650,7 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress=
641650
may_reset = True
642651
if mrepo.head.commit.binsha != self.NULL_BIN_SHA:
643652
base_commit = mrepo.merge_base(mrepo.head.commit, hexsha)
644-
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):
645654
if force:
646655
msg = "Will force checkout or reset on local branch that is possibly in the future of"
647656
msg += "the commit it will be checked out to, effectively 'forgetting' new commits"
@@ -916,7 +925,7 @@ def remove(self, module: bool = True, force: bool = False,
916925
import gc
917926
gc.collect()
918927
try:
919-
rmtree(wtd)
928+
rmtree(str(wtd))
920929
except Exception as ex:
921930
if HIDE_WINDOWS_KNOWN_ERRORS:
922931
raise SkipTest("FIXME: fails with: PermissionError\n {}".format(ex)) from ex
@@ -954,6 +963,8 @@ def remove(self, module: bool = True, force: bool = False,
954963

955964
# now git config - need the config intact, otherwise we can't query
956965
# information anymore
966+
writer: Union[GitConfigParser, SectionConstraint]
967+
957968
with self.repo.config_writer() as writer:
958969
writer.remove_section(sm_section(self.name))
959970

@@ -1067,13 +1078,14 @@ def rename(self, new_name: str) -> 'Submodule':
10671078
destination_module_abspath = self._module_abspath(self.repo, self.path, new_name)
10681079
source_dir = mod.git_dir
10691080
# Let's be sure the submodule name is not so obviously tied to a directory
1070-
if destination_module_abspath.startswith(mod.git_dir):
1081+
if str(destination_module_abspath).startswith(str(mod.git_dir)):
10711082
tmp_dir = self._module_abspath(self.repo, self.path, str(uuid.uuid4()))
10721083
os.renames(source_dir, tmp_dir)
10731084
source_dir = tmp_dir
10741085
# end handle self-containment
10751086
os.renames(source_dir, destination_module_abspath)
1076-
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)
10771089
# end move separate git repository
10781090

10791091
return self
@@ -1150,34 +1162,34 @@ def branch(self):
11501162
return mkhead(self.module(), self._branch_path)
11511163

11521164
@property
1153-
def branch_path(self):
1165+
def branch_path(self) -> PathLike:
11541166
"""
11551167
:return: full(relative) path as string to the branch we would checkout
11561168
from the remote and track"""
11571169
return self._branch_path
11581170

11591171
@property
1160-
def branch_name(self):
1172+
def branch_name(self) -> str:
11611173
""":return: the name of the branch, which is the shortest possible branch name"""
11621174
# use an instance method, for this we create a temporary Head instance
11631175
# which uses a repository that is available at least ( it makes no difference )
11641176
return git.Head(self.repo, self._branch_path).name
11651177

11661178
@property
1167-
def url(self):
1179+
def url(self) -> str:
11681180
""":return: The url to the repository which our module - repository refers to"""
11691181
return self._url
11701182

11711183
@property
1172-
def parent_commit(self):
1184+
def parent_commit(self) -> 'Commit_ish':
11731185
""":return: Commit instance with the tree containing the .gitmodules file
11741186
:note: will always point to the current head's commit if it was not set explicitly"""
11751187
if self._parent_commit is None:
11761188
return self.repo.commit()
11771189
return self._parent_commit
11781190

11791191
@property
1180-
def name(self):
1192+
def name(self) -> str:
11811193
""":return: The name of this submodule. It is used to identify it within the
11821194
.gitmodules file.
11831195
:note: by default, the name is the path at which to find the submodule, but

git/remote.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242

4343
if TYPE_CHECKING:
4444
from git.repo.base import Repo
45+
from git.objects.submodule.base import UpdateProgress
4546
# from git.objects.commit import Commit
4647
# from git.objects import Blob, Tree, TagObject
4748

@@ -64,7 +65,9 @@ def is_flagKeyLiteral(inp: str) -> TypeGuard[flagKeyLiteral]:
6465
#{ Utilities
6566

6667

67-
def add_progress(kwargs: Any, git: Git, progress: Union[Callable[..., Any], None]) -> Any:
68+
def add_progress(kwargs: Any, git: Git,
69+
progress: Union[RemoteProgress, 'UpdateProgress', Callable[..., RemoteProgress], None]
70+
) -> Any:
6871
"""Add the --progress flag to the given kwargs dict if supported by the
6972
git command. If the actual progress in the given progress instance is not
7073
given, we do not request any progress
@@ -794,7 +797,7 @@ def _assert_refspec(self) -> None:
794797
config.release()
795798

796799
def fetch(self, refspec: Union[str, List[str], None] = None,
797-
progress: Union[Callable[..., Any], None] = None,
800+
progress: Union[RemoteProgress, None, 'UpdateProgress'] = None,
798801
verbose: bool = True, **kwargs: Any) -> IterableList[FetchInfo]:
799802
"""Fetch the latest changes for this remote
800803
@@ -841,7 +844,7 @@ def fetch(self, refspec: Union[str, List[str], None] = None,
841844
return res
842845

843846
def pull(self, refspec: Union[str, List[str], None] = None,
844-
progress: Union[Callable[..., Any], None] = None,
847+
progress: Union[RemoteProgress, 'UpdateProgress', None] = None,
845848
**kwargs: Any) -> IterableList[FetchInfo]:
846849
"""Pull changes from the given branch, being the same as a fetch followed
847850
by a merge of branch with your local branch.
@@ -862,7 +865,7 @@ def pull(self, refspec: Union[str, List[str], None] = None,
862865
return res
863866

864867
def push(self, refspec: Union[str, List[str], None] = None,
865-
progress: Union[Callable[..., Any], None] = None,
868+
progress: Union[RemoteProgress, 'UpdateProgress', Callable[..., RemoteProgress], None] = None,
866869
**kwargs: Any) -> IterableList[PushInfo]:
867870
"""Push changes from source branch in refspec to target branch in refspec.
868871

git/repo/base.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
from git.util import IterableList
4949
from git.refs.symbolic import SymbolicReference
5050
from git.objects import Tree
51+
from git.objects.submodule.base import UpdateProgress
52+
from git.remote import RemoteProgress
5153

5254

5355
# -----------------------------------------------------------
@@ -575,7 +577,7 @@ def iter_commits(self, rev: Optional[TBD] = None, paths: Union[PathLike, Sequenc
575577
return Commit.iter_items(self, rev, paths, **kwargs)
576578

577579
def merge_base(self, *rev: TBD, **kwargs: Any
578-
) -> List[Union['SymbolicReference', Commit_ish, None]]:
580+
) -> List[Union[Commit_ish, None]]:
579581
"""Find the closest common ancestor for the given revision (e.g. Commits, Tags, References, etc)
580582
581583
:param rev: At least two revs to find the common ancestor for.
@@ -588,7 +590,7 @@ def merge_base(self, *rev: TBD, **kwargs: Any
588590
raise ValueError("Please specify at least two revs, got only %i" % len(rev))
589591
# end handle input
590592

591-
res = [] # type: List[Union['SymbolicReference', Commit_ish, None]]
593+
res = [] # type: List[Union[Commit_ish, None]]
592594
try:
593595
lines = self.git.merge_base(*rev, **kwargs).splitlines() # List[str]
594596
except GitCommandError as err:
@@ -1014,7 +1016,8 @@ def init(cls, path: PathLike = None, mkdir: bool = True, odbt: Type[GitCmdObject
10141016

10151017
@classmethod
10161018
def _clone(cls, git: 'Git', url: PathLike, path: PathLike, odb_default_type: Type[GitCmdObjectDB],
1017-
progress: Optional[Callable], multi_options: Optional[List[str]] = None, **kwargs: Any
1019+
progress: Union['RemoteProgress', 'UpdateProgress', Callable[..., 'RemoteProgress'], None],
1020+
multi_options: Optional[List[str]] = None, **kwargs: Any
10181021
) -> 'Repo':
10191022
odbt = kwargs.pop('odbt', odb_default_type)
10201023

git/util.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,15 @@
8282

8383
#{ Utility Methods
8484

85+
T = TypeVar('T')
8586

86-
def unbare_repo(func: Callable) -> Callable:
87+
88+
def unbare_repo(func: Callable[..., T]) -> Callable[..., T]:
8789
"""Methods with this decorator raise InvalidGitRepositoryError if they
8890
encounter a bare repository"""
8991

9092
@wraps(func)
91-
def wrapper(self: 'Remote', *args: Any, **kwargs: Any) -> Callable:
93+
def wrapper(self: 'Remote', *args: Any, **kwargs: Any) -> T:
9294
if self.repo.bare:
9395
raise InvalidGitRepositoryError("Method '%s' cannot operate on bare repositories" % func.__name__)
9496
# END bare method

0 commit comments

Comments
 (0)