Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/git/objects/submodule/base.py: 18%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

575 statements  

1# This module is part of GitPython and is released under the 

2# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ 

3 

4__all__ = ["Submodule", "UpdateProgress"] 

5 

6import gc 

7from io import BytesIO 

8import logging 

9import os 

10import os.path as osp 

11import stat 

12import sys 

13import uuid 

14 

15import git 

16from git.cmd import Git 

17from git.compat import defenc 

18from git.config import GitConfigParser, SectionConstraint, cp 

19from git.exc import ( 

20 BadName, 

21 InvalidGitRepositoryError, 

22 NoSuchPathError, 

23 RepositoryDirtyError, 

24) 

25from git.objects.base import IndexObject, Object 

26from git.objects.util import TraversableIterableObj 

27from git.util import ( 

28 IterableList, 

29 RemoteProgress, 

30 join_path_native, 

31 rmtree, 

32 to_native_path_linux, 

33 unbare_repo, 

34) 

35 

36from .util import ( 

37 SubmoduleConfigParser, 

38 find_first_remote_branch, 

39 mkhead, 

40 sm_name, 

41 sm_section, 

42) 

43 

44# typing ---------------------------------------------------------------------- 

45 

46from typing import ( 

47 Any, 

48 Callable, 

49 Dict, 

50 Iterator, 

51 Mapping, 

52 Sequence, 

53 TYPE_CHECKING, 

54 Union, 

55 cast, 

56) 

57 

58if sys.version_info >= (3, 8): 

59 from typing import Literal 

60else: 

61 from typing_extensions import Literal 

62 

63from git.types import Commit_ish, PathLike, TBD 

64 

65if TYPE_CHECKING: 

66 from git.index import IndexFile 

67 from git.objects.commit import Commit 

68 from git.refs import Head 

69 from git.repo import Repo 

70 

71# ----------------------------------------------------------------------------- 

72 

73_logger = logging.getLogger(__name__) 

74 

75 

76class UpdateProgress(RemoteProgress): 

77 """Class providing detailed progress information to the caller who should 

78 derive from it and implement the 

79 :meth:`update(...) <git.util.RemoteProgress.update>` message.""" 

80 

81 CLONE, FETCH, UPDWKTREE = [1 << x for x in range(RemoteProgress._num_op_codes, RemoteProgress._num_op_codes + 3)] 

82 _num_op_codes: int = RemoteProgress._num_op_codes + 3 

83 

84 __slots__ = () 

85 

86 

87BEGIN = UpdateProgress.BEGIN 

88END = UpdateProgress.END 

89CLONE = UpdateProgress.CLONE 

90FETCH = UpdateProgress.FETCH 

91UPDWKTREE = UpdateProgress.UPDWKTREE 

92 

93 

94# IndexObject comes via the util module. It's a 'hacky' fix thanks to Python's import 

95# mechanism, which causes plenty of trouble if the only reason for packages and modules 

96# is refactoring - subpackages shouldn't depend on parent packages. 

97class Submodule(IndexObject, TraversableIterableObj): 

98 """Implements access to a git submodule. They are special in that their sha 

99 represents a commit in the submodule's repository which is to be checked out 

100 at the path of this instance. 

101 

102 The submodule type does not have a string type associated with it, as it exists 

103 solely as a marker in the tree and index. 

104 

105 All methods work in bare and non-bare repositories. 

106 """ 

107 

108 _id_attribute_ = "name" 

109 k_modules_file = ".gitmodules" 

110 k_head_option = "branch" 

111 k_head_default = "master" 

112 k_default_mode = stat.S_IFDIR | stat.S_IFLNK 

113 """Submodule flags. Submodules are directories with link-status.""" 

114 

115 type: Literal["submodule"] = "submodule" # type: ignore[assignment] 

116 """This is a bogus type string for base class compatibility.""" 

117 

118 __slots__ = ("_parent_commit", "_url", "_branch_path", "_name", "__weakref__") 

119 

120 _cache_attrs = ("path", "_url", "_branch_path") 

121 

122 def __init__( 

123 self, 

124 repo: "Repo", 

125 binsha: bytes, 

126 mode: Union[int, None] = None, 

127 path: Union[PathLike, None] = None, 

128 name: Union[str, None] = None, 

129 parent_commit: Union["Commit", None] = None, 

130 url: Union[str, None] = None, 

131 branch_path: Union[PathLike, None] = None, 

132 ) -> None: 

133 """Initialize this instance with its attributes. 

134 

135 We only document the parameters that differ from 

136 :class:`~git.objects.base.IndexObject`. 

137 

138 :param repo: 

139 Our parent repository. 

140 

141 :param binsha: 

142 Binary sha referring to a commit in the remote repository. 

143 See the `url` parameter. 

144 

145 :param parent_commit: 

146 The :class:`~git.objects.commit.Commit` whose tree is supposed to contain 

147 the ``.gitmodules`` blob, or ``None`` to always point to the most recent 

148 commit. See :meth:`set_parent_commit` for details. 

149 

150 :param url: 

151 The URL to the remote repository which is the submodule. 

152 

153 :param branch_path: 

154 Full repository-relative path to ref to checkout when cloning the remote 

155 repository. 

156 """ 

157 super().__init__(repo, binsha, mode, path) 

158 self.size = 0 

159 self._parent_commit = parent_commit 

160 if url is not None: 

161 self._url = url 

162 if branch_path is not None: 

163 self._branch_path = branch_path 

164 if name is not None: 

165 self._name = name 

166 

167 def _set_cache_(self, attr: str) -> None: 

168 if attr in ("path", "_url", "_branch_path"): 

169 reader: SectionConstraint = self.config_reader() 

170 # Default submodule values. 

171 try: 

172 self.path = reader.get("path") 

173 except cp.NoSectionError as e: 

174 if self.repo.working_tree_dir is not None: 

175 raise ValueError( 

176 "This submodule instance does not exist anymore in '%s' file" 

177 % osp.join(self.repo.working_tree_dir, ".gitmodules") 

178 ) from e 

179 

180 self._url = reader.get("url") 

181 # GitPython extension values - optional. 

182 self._branch_path = reader.get_value(self.k_head_option, git.Head.to_full_path(self.k_head_default)) 

183 elif attr == "_name": 

184 raise AttributeError("Cannot retrieve the name of a submodule if it was not set initially") 

185 else: 

186 super()._set_cache_(attr) 

187 # END handle attribute name 

188 

189 @classmethod 

190 def _get_intermediate_items(cls, item: "Submodule") -> IterableList["Submodule"]: 

191 """:return: All the submodules of our module repository""" 

192 try: 

193 return cls.list_items(item.module()) 

194 except InvalidGitRepositoryError: 

195 return IterableList("") 

196 # END handle intermediate items 

197 

198 @classmethod 

199 def _need_gitfile_submodules(cls, git: Git) -> bool: 

200 return git.version_info[:3] >= (1, 7, 5) 

201 

202 def __eq__(self, other: Any) -> bool: 

203 """Compare with another submodule.""" 

204 # We may only compare by name as this should be the ID they are hashed with. 

205 # Otherwise this type wouldn't be hashable. 

206 # return self.path == other.path and self.url == other.url and super().__eq__(other) 

207 return self._name == other._name 

208 

209 def __ne__(self, other: object) -> bool: 

210 """Compare with another submodule for inequality.""" 

211 return not (self == other) 

212 

213 def __hash__(self) -> int: 

214 """Hash this instance using its logical id, not the sha.""" 

215 return hash(self._name) 

216 

217 def __str__(self) -> str: 

218 return self._name 

219 

220 def __repr__(self) -> str: 

221 return "git.%s(name=%s, path=%s, url=%s, branch_path=%s)" % ( 

222 type(self).__name__, 

223 self._name, 

224 self.path, 

225 self.url, 

226 self.branch_path, 

227 ) 

228 

229 @classmethod 

230 def _config_parser( 

231 cls, repo: "Repo", parent_commit: Union["Commit", None], read_only: bool 

232 ) -> SubmoduleConfigParser: 

233 """ 

234 :return: 

235 Config parser constrained to our submodule in read or write mode 

236 

237 :raise IOError: 

238 If the ``.gitmodules`` file cannot be found, either locally or in the 

239 repository at the given parent commit. Otherwise the exception would be 

240 delayed until the first access of the config parser. 

241 """ 

242 parent_matches_head = True 

243 if parent_commit is not None: 

244 try: 

245 parent_matches_head = repo.head.commit == parent_commit 

246 except ValueError: 

247 # We are most likely in an empty repository, so the HEAD doesn't point 

248 # to a valid ref. 

249 pass 

250 # END handle parent_commit 

251 fp_module: Union[str, BytesIO] 

252 if not repo.bare and parent_matches_head and repo.working_tree_dir: 

253 fp_module = osp.join(repo.working_tree_dir, cls.k_modules_file) 

254 else: 

255 assert parent_commit is not None, "need valid parent_commit in bare repositories" 

256 try: 

257 fp_module = cls._sio_modules(parent_commit) 

258 except KeyError as e: 

259 raise IOError( 

260 "Could not find %s file in the tree of parent commit %s" % (cls.k_modules_file, parent_commit) 

261 ) from e 

262 # END handle exceptions 

263 # END handle non-bare working tree 

264 

265 if not read_only and (repo.bare or not parent_matches_head): 

266 raise ValueError("Cannot write blobs of 'historical' submodule configurations") 

267 # END handle writes of historical submodules 

268 

269 return SubmoduleConfigParser(fp_module, read_only=read_only) 

270 

271 def _clear_cache(self) -> None: 

272 """Clear the possibly changed values.""" 

273 for name in self._cache_attrs: 

274 try: 

275 delattr(self, name) 

276 except AttributeError: 

277 pass 

278 # END try attr deletion 

279 # END for each name to delete 

280 

281 @classmethod 

282 def _sio_modules(cls, parent_commit: "Commit") -> BytesIO: 

283 """ 

284 :return: 

285 Configuration file as :class:`~io.BytesIO` - we only access it through the 

286 respective blob's data 

287 """ 

288 sio = BytesIO(parent_commit.tree[cls.k_modules_file].data_stream.read()) 

289 sio.name = cls.k_modules_file 

290 return sio 

291 

292 def _config_parser_constrained(self, read_only: bool) -> SectionConstraint: 

293 """:return: Config parser constrained to our submodule in read or write mode""" 

294 try: 

295 pc = self.parent_commit 

296 except ValueError: 

297 pc = None 

298 # END handle empty parent repository 

299 parser = self._config_parser(self.repo, pc, read_only) 

300 parser.set_submodule(self) 

301 return SectionConstraint(parser, sm_section(self.name)) 

302 

303 @classmethod 

304 def _module_abspath(cls, parent_repo: "Repo", path: PathLike, name: str) -> PathLike: 

305 if cls._need_gitfile_submodules(parent_repo.git): 

306 return osp.join(parent_repo.git_dir, "modules", name) 

307 if parent_repo.working_tree_dir: 

308 return osp.join(parent_repo.working_tree_dir, path) 

309 raise NotADirectoryError() 

310 

311 @classmethod 

312 def _clone_repo( 

313 cls, 

314 repo: "Repo", 

315 url: str, 

316 path: PathLike, 

317 name: str, 

318 allow_unsafe_options: bool = False, 

319 allow_unsafe_protocols: bool = False, 

320 **kwargs: Any, 

321 ) -> "Repo": 

322 """ 

323 :return: 

324 :class:`~git.repo.base.Repo` instance of newly cloned repository. 

325 

326 :param repo: 

327 Our parent repository. 

328 

329 :param url: 

330 URL to clone from. 

331 

332 :param path: 

333 Repository-relative path to the submodule checkout location. 

334 

335 :param name: 

336 Canonical name of the submodule. 

337 

338 :param allow_unsafe_protocols: 

339 Allow unsafe protocols to be used, like ``ext``. 

340 

341 :param allow_unsafe_options: 

342 Allow unsafe options to be used, like ``--upload-pack``. 

343 

344 :param kwargs: 

345 Additional arguments given to :manpage:`git-clone(1)`. 

346 """ 

347 module_abspath = cls._module_abspath(repo, path, name) 

348 module_checkout_path = module_abspath 

349 if cls._need_gitfile_submodules(repo.git): 

350 kwargs["separate_git_dir"] = module_abspath 

351 module_abspath_dir = osp.dirname(module_abspath) 

352 if not osp.isdir(module_abspath_dir): 

353 os.makedirs(module_abspath_dir) 

354 module_checkout_path = osp.join(str(repo.working_tree_dir), path) 

355 

356 clone = git.Repo.clone_from( 

357 url, 

358 module_checkout_path, 

359 allow_unsafe_options=allow_unsafe_options, 

360 allow_unsafe_protocols=allow_unsafe_protocols, 

361 **kwargs, 

362 ) 

363 if cls._need_gitfile_submodules(repo.git): 

364 cls._write_git_file_and_module_config(module_checkout_path, module_abspath) 

365 

366 return clone 

367 

368 @classmethod 

369 def _to_relative_path(cls, parent_repo: "Repo", path: PathLike) -> PathLike: 

370 """:return: A path guaranteed to be relative to the given parent repository 

371 

372 :raise ValueError: 

373 If path is not contained in the parent repository's working tree. 

374 """ 

375 path = to_native_path_linux(path) 

376 if path.endswith("/"): 

377 path = path[:-1] 

378 # END handle trailing slash 

379 

380 if osp.isabs(path) and parent_repo.working_tree_dir: 

381 working_tree_linux = to_native_path_linux(parent_repo.working_tree_dir) 

382 if not path.startswith(working_tree_linux): 

383 raise ValueError( 

384 "Submodule checkout path '%s' needs to be within the parents repository at '%s'" 

385 % (working_tree_linux, path) 

386 ) 

387 path = path[len(working_tree_linux.rstrip("/")) + 1 :] 

388 if not path: 

389 raise ValueError("Absolute submodule path '%s' didn't yield a valid relative path" % path) 

390 # END verify converted relative path makes sense 

391 # END convert to a relative path 

392 

393 return path 

394 

395 @classmethod 

396 def _write_git_file_and_module_config(cls, working_tree_dir: PathLike, module_abspath: PathLike) -> None: 

397 """Write a ``.git`` file containing a (preferably) relative path to the actual 

398 git module repository. 

399 

400 It is an error if the `module_abspath` cannot be made into a relative path, 

401 relative to the `working_tree_dir`. 

402 

403 :note: 

404 This will overwrite existing files! 

405 

406 :note: 

407 As we rewrite both the git file as well as the module configuration, we 

408 might fail on the configuration and will not roll back changes done to the 

409 git file. This should be a non-issue, but may easily be fixed if it becomes 

410 one. 

411 

412 :param working_tree_dir: 

413 Directory to write the ``.git`` file into. 

414 

415 :param module_abspath: 

416 Absolute path to the bare repository. 

417 """ 

418 git_file = osp.join(working_tree_dir, ".git") 

419 rela_path = osp.relpath(module_abspath, start=working_tree_dir) 

420 if sys.platform == "win32" and osp.isfile(git_file): 

421 os.remove(git_file) 

422 with open(git_file, "wb") as fp: 

423 fp.write(("gitdir: %s" % rela_path).encode(defenc)) 

424 

425 with GitConfigParser(osp.join(module_abspath, "config"), read_only=False, merge_includes=False) as writer: 

426 writer.set_value( 

427 "core", 

428 "worktree", 

429 to_native_path_linux(osp.relpath(working_tree_dir, start=module_abspath)), 

430 ) 

431 

432 # { Edit Interface 

433 

434 @classmethod 

435 def add( 

436 cls, 

437 repo: "Repo", 

438 name: str, 

439 path: PathLike, 

440 url: Union[str, None] = None, 

441 branch: Union[str, None] = None, 

442 no_checkout: bool = False, 

443 depth: Union[int, None] = None, 

444 env: Union[Mapping[str, str], None] = None, 

445 clone_multi_options: Union[Sequence[TBD], None] = None, 

446 allow_unsafe_options: bool = False, 

447 allow_unsafe_protocols: bool = False, 

448 ) -> "Submodule": 

449 """Add a new submodule to the given repository. This will alter the index as 

450 well as the ``.gitmodules`` file, but will not create a new commit. If the 

451 submodule already exists, no matter if the configuration differs from the one 

452 provided, the existing submodule will be returned. 

453 

454 :param repo: 

455 Repository instance which should receive the submodule. 

456 

457 :param name: 

458 The name/identifier for the submodule. 

459 

460 :param path: 

461 Repository-relative or absolute path at which the submodule should be 

462 located. 

463 It will be created as required during the repository initialization. 

464 

465 :param url: 

466 ``git clone ...``-compatible URL. See :manpage:`git-clone(1)` for more 

467 information. If ``None``, the repository is assumed to exist, and the URL of 

468 the first remote is taken instead. This is useful if you want to make an 

469 existing repository a submodule of another one. 

470 

471 :param branch: 

472 Name of branch at which the submodule should (later) be checked out. The 

473 given branch must exist in the remote repository, and will be checked out 

474 locally as a tracking branch. 

475 It will only be written into the configuration if it not ``None``, which is 

476 when the checked out branch will be the one the remote HEAD pointed to. 

477 The result you get in these situation is somewhat fuzzy, and it is 

478 recommended to specify at least ``master`` here. 

479 Examples are ``master`` or ``feature/new``. 

480 

481 :param no_checkout: 

482 If ``True``, and if the repository has to be cloned manually, no checkout 

483 will be performed. 

484 

485 :param depth: 

486 Create a shallow clone with a history truncated to the specified number of 

487 commits. 

488 

489 :param env: 

490 Optional dictionary containing the desired environment variables. 

491 

492 Note: Provided variables will be used to update the execution environment 

493 for ``git``. If some variable is not specified in `env` and is defined in 

494 attr:`os.environ`, the value from attr:`os.environ` will be used. If you 

495 want to unset some variable, consider providing an empty string as its 

496 value. 

497 

498 :param clone_multi_options: 

499 A list of clone options. Please see 

500 :meth:`Repo.clone <git.repo.base.Repo.clone>` for details. 

501 

502 :param allow_unsafe_protocols: 

503 Allow unsafe protocols to be used, like ``ext``. 

504 

505 :param allow_unsafe_options: 

506 Allow unsafe options to be used, like ``--upload-pack``. 

507 

508 :return: 

509 The newly created :class:`Submodule` instance. 

510 

511 :note: 

512 Works atomically, such that no change will be done if, for example, the 

513 repository update fails. 

514 """ 

515 if repo.bare: 

516 raise InvalidGitRepositoryError("Cannot add submodules to bare repositories") 

517 # END handle bare repos 

518 

519 path = cls._to_relative_path(repo, path) 

520 

521 # Ensure we never put backslashes into the URL, as might happen on Windows. 

522 if url is not None: 

523 url = to_native_path_linux(url) 

524 # END ensure URL correctness 

525 

526 # INSTANTIATE INTERMEDIATE SM 

527 sm = cls( 

528 repo, 

529 cls.NULL_BIN_SHA, 

530 cls.k_default_mode, 

531 path, 

532 name, 

533 url="invalid-temporary", 

534 ) 

535 if sm.exists(): 

536 # Reretrieve submodule from tree. 

537 try: 

538 sm = repo.head.commit.tree[str(path)] 

539 sm._name = name 

540 return sm 

541 except KeyError: 

542 # Could only be in index. 

543 index = repo.index 

544 entry = index.entries[index.entry_key(path, 0)] 

545 sm.binsha = entry.binsha 

546 return sm 

547 # END handle exceptions 

548 # END handle existing 

549 

550 # fake-repo - we only need the functionality on the branch instance. 

551 br = git.Head(repo, git.Head.to_full_path(str(branch) or cls.k_head_default)) 

552 has_module = sm.module_exists() 

553 branch_is_default = branch is None 

554 if has_module and url is not None: 

555 if url not in [r.url for r in sm.module().remotes]: 

556 raise ValueError( 

557 "Specified URL '%s' does not match any remote url of the repository at '%s'" % (url, sm.abspath) 

558 ) 

559 # END check url 

560 # END verify urls match 

561 

562 mrepo: Union[Repo, None] = None 

563 

564 if url is None: 

565 if not has_module: 

566 raise ValueError("A URL was not given and a repository did not exist at %s" % path) 

567 # END check url 

568 mrepo = sm.module() 

569 # assert isinstance(mrepo, git.Repo) 

570 urls = [r.url for r in mrepo.remotes] 

571 if not urls: 

572 raise ValueError("Didn't find any remote url in repository at %s" % sm.abspath) 

573 # END verify we have url 

574 url = urls[0] 

575 else: 

576 # Clone new repo. 

577 kwargs: Dict[str, Union[bool, int, str, Sequence[TBD]]] = {"n": no_checkout} 

578 if not branch_is_default: 

579 kwargs["b"] = br.name 

580 # END setup checkout-branch 

581 

582 if depth: 

583 if isinstance(depth, int): 

584 kwargs["depth"] = depth 

585 else: 

586 raise ValueError("depth should be an integer") 

587 if clone_multi_options: 

588 kwargs["multi_options"] = clone_multi_options 

589 

590 # _clone_repo(cls, repo, url, path, name, **kwargs): 

591 mrepo = cls._clone_repo( 

592 repo, 

593 url, 

594 path, 

595 name, 

596 env=env, 

597 allow_unsafe_options=allow_unsafe_options, 

598 allow_unsafe_protocols=allow_unsafe_protocols, 

599 **kwargs, 

600 ) 

601 # END verify url 

602 

603 ## See #525 for ensuring git URLs in config-files are valid under Windows. 

604 url = Git.polish_url(url) 

605 

606 # It's important to add the URL to the parent config, to let `git submodule` know. 

607 # Otherwise there is a '-' character in front of the submodule listing: 

608 # a38efa84daef914e4de58d1905a500d8d14aaf45 mymodule (v0.9.0-1-ga38efa8) 

609 # -a38efa84daef914e4de58d1905a500d8d14aaf45 submodules/intermediate/one 

610 writer: Union[GitConfigParser, SectionConstraint] 

611 

612 with sm.repo.config_writer() as writer: 

613 writer.set_value(sm_section(name), "url", url) 

614 

615 # Update configuration and index. 

616 index = sm.repo.index 

617 with sm.config_writer(index=index, write=False) as writer: 

618 writer.set_value("url", url) 

619 writer.set_value("path", path) 

620 

621 sm._url = url 

622 if not branch_is_default: 

623 # Store full path. 

624 writer.set_value(cls.k_head_option, br.path) 

625 sm._branch_path = br.path 

626 

627 # We deliberately assume that our head matches our index! 

628 if mrepo: 

629 sm.binsha = mrepo.head.commit.binsha 

630 index.add([sm], write=True) 

631 

632 return sm 

633 

634 def update( 

635 self, 

636 recursive: bool = False, 

637 init: bool = True, 

638 to_latest_revision: bool = False, 

639 progress: Union["UpdateProgress", None] = None, 

640 dry_run: bool = False, 

641 force: bool = False, 

642 keep_going: bool = False, 

643 env: Union[Mapping[str, str], None] = None, 

644 clone_multi_options: Union[Sequence[TBD], None] = None, 

645 allow_unsafe_options: bool = False, 

646 allow_unsafe_protocols: bool = False, 

647 ) -> "Submodule": 

648 """Update the repository of this submodule to point to the checkout we point at 

649 with the binsha of this instance. 

650 

651 :param recursive: 

652 If ``True``, we will operate recursively and update child modules as well. 

653 

654 :param init: 

655 If ``True``, the module repository will be cloned into place if necessary. 

656 

657 :param to_latest_revision: 

658 If ``True``, the submodule's sha will be ignored during checkout. Instead, 

659 the remote will be fetched, and the local tracking branch updated. This only 

660 works if we have a local tracking branch, which is the case if the remote 

661 repository had a master branch, or if the ``branch`` option was specified 

662 for this submodule and the branch existed remotely. 

663 

664 :param progress: 

665 :class:`UpdateProgress` instance, or ``None`` if no progress should be 

666 shown. 

667 

668 :param dry_run: 

669 If ``True``, the operation will only be simulated, but not performed. 

670 All performed operations are read-only. 

671 

672 :param force: 

673 If ``True``, we may reset heads even if the repository in question is dirty. 

674 Additionally we will be allowed to set a tracking branch which is ahead of 

675 its remote branch back into the past or the location of the remote branch. 

676 This will essentially 'forget' commits. 

677 

678 If ``False``, local tracking branches that are in the future of their 

679 respective remote branches will simply not be moved. 

680 

681 :param keep_going: 

682 If ``True``, we will ignore but log all errors, and keep going recursively. 

683 Unless `dry_run` is set as well, `keep_going` could cause 

684 subsequent/inherited errors you wouldn't see otherwise. 

685 In conjunction with `dry_run`, it can be useful to anticipate all errors 

686 when updating submodules. 

687 

688 :param env: 

689 Optional dictionary containing the desired environment variables. 

690 

691 Note: Provided variables will be used to update the execution environment 

692 for ``git``. If some variable is not specified in `env` and is defined in 

693 attr:`os.environ`, value from attr:`os.environ` will be used. 

694 

695 If you want to unset some variable, consider providing the empty string as 

696 its value. 

697 

698 :param clone_multi_options: 

699 List of :manpage:`git-clone(1)` options. 

700 Please see :meth:`Repo.clone <git.repo.base.Repo.clone>` for details. 

701 They only take effect with the `init` option. 

702 

703 :param allow_unsafe_protocols: 

704 Allow unsafe protocols to be used, like ``ext``. 

705 

706 :param allow_unsafe_options: 

707 Allow unsafe options to be used, like ``--upload-pack``. 

708 

709 :note: 

710 Does nothing in bare repositories. 

711 

712 :note: 

713 This method is definitely not atomic if `recursive` is ``True``. 

714 

715 :return: 

716 self 

717 """ 

718 if self.repo.bare: 

719 return self 

720 # END pass in bare mode 

721 

722 if progress is None: 

723 progress = UpdateProgress() 

724 # END handle progress 

725 prefix = "" 

726 if dry_run: 

727 prefix = "DRY-RUN: " 

728 # END handle prefix 

729 

730 # To keep things plausible in dry-run mode. 

731 if dry_run: 

732 mrepo = None 

733 # END init mrepo 

734 

735 try: 

736 # ENSURE REPO IS PRESENT AND UP-TO-DATE 

737 ####################################### 

738 try: 

739 mrepo = self.module() 

740 rmts = mrepo.remotes 

741 len_rmts = len(rmts) 

742 for i, remote in enumerate(rmts): 

743 op = FETCH 

744 if i == 0: 

745 op |= BEGIN 

746 # END handle start 

747 

748 progress.update( 

749 op, 

750 i, 

751 len_rmts, 

752 prefix + "Fetching remote %s of submodule %r" % (remote, self.name), 

753 ) 

754 # =============================== 

755 if not dry_run: 

756 remote.fetch(progress=progress) 

757 # END handle dry-run 

758 # =============================== 

759 if i == len_rmts - 1: 

760 op |= END 

761 # END handle end 

762 progress.update( 

763 op, 

764 i, 

765 len_rmts, 

766 prefix + "Done fetching remote of submodule %r" % self.name, 

767 ) 

768 # END fetch new data 

769 except InvalidGitRepositoryError: 

770 mrepo = None 

771 if not init: 

772 return self 

773 # END early abort if init is not allowed 

774 

775 # There is no git-repository yet - but delete empty paths. 

776 checkout_module_abspath = self.abspath 

777 if not dry_run and osp.isdir(checkout_module_abspath): 

778 try: 

779 os.rmdir(checkout_module_abspath) 

780 except OSError as e: 

781 raise OSError( 

782 "Module directory at %r does already exist and is non-empty" % checkout_module_abspath 

783 ) from e 

784 # END handle OSError 

785 # END handle directory removal 

786 

787 # Don't check it out at first - nonetheless it will create a local 

788 # branch according to the remote-HEAD if possible. 

789 progress.update( 

790 BEGIN | CLONE, 

791 0, 

792 1, 

793 prefix 

794 + "Cloning url '%s' to '%s' in submodule %r" % (self.url, checkout_module_abspath, self.name), 

795 ) 

796 if not dry_run: 

797 mrepo = self._clone_repo( 

798 self.repo, 

799 self.url, 

800 self.path, 

801 self.name, 

802 n=True, 

803 env=env, 

804 multi_options=clone_multi_options, 

805 allow_unsafe_options=allow_unsafe_options, 

806 allow_unsafe_protocols=allow_unsafe_protocols, 

807 ) 

808 # END handle dry-run 

809 progress.update( 

810 END | CLONE, 

811 0, 

812 1, 

813 prefix + "Done cloning to %s" % checkout_module_abspath, 

814 ) 

815 

816 if not dry_run: 

817 # See whether we have a valid branch to check out. 

818 try: 

819 mrepo = cast("Repo", mrepo) 

820 # Find a remote which has our branch - we try to be flexible. 

821 remote_branch = find_first_remote_branch(mrepo.remotes, self.branch_name) 

822 local_branch = mkhead(mrepo, self.branch_path) 

823 

824 # Have a valid branch, but no checkout - make sure we can figure 

825 # that out by marking the commit with a null_sha. 

826 local_branch.set_object(Object(mrepo, self.NULL_BIN_SHA)) 

827 # END initial checkout + branch creation 

828 

829 # Make sure HEAD is not detached. 

830 mrepo.head.set_reference( 

831 local_branch, 

832 logmsg="submodule: attaching head to %s" % local_branch, 

833 ) 

834 mrepo.head.reference.set_tracking_branch(remote_branch) 

835 except (IndexError, InvalidGitRepositoryError): 

836 _logger.warning("Failed to checkout tracking branch %s", self.branch_path) 

837 # END handle tracking branch 

838 

839 # NOTE: Have to write the repo config file as well, otherwise the 

840 # default implementation will be offended and not update the 

841 # repository. Maybe this is a good way to ensure it doesn't get into 

842 # our way, but we want to stay backwards compatible too... It's so 

843 # redundant! 

844 with self.repo.config_writer() as writer: 

845 writer.set_value(sm_section(self.name), "url", self.url) 

846 # END handle dry_run 

847 # END handle initialization 

848 

849 # DETERMINE SHAS TO CHECK OUT 

850 ############################# 

851 binsha = self.binsha 

852 hexsha = self.hexsha 

853 if mrepo is not None: 

854 # mrepo is only set if we are not in dry-run mode or if the module 

855 # existed. 

856 is_detached = mrepo.head.is_detached 

857 # END handle dry_run 

858 

859 if mrepo is not None and to_latest_revision: 

860 msg_base = "Cannot update to latest revision in repository at %r as " % mrepo.working_dir 

861 if not is_detached: 

862 rref = mrepo.head.reference.tracking_branch() 

863 if rref is not None: 

864 rcommit = rref.commit 

865 binsha = rcommit.binsha 

866 hexsha = rcommit.hexsha 

867 else: 

868 _logger.error( 

869 "%s a tracking branch was not set for local branch '%s'", 

870 msg_base, 

871 mrepo.head.reference, 

872 ) 

873 # END handle remote ref 

874 else: 

875 _logger.error("%s there was no local tracking branch", msg_base) 

876 # END handle detached head 

877 # END handle to_latest_revision option 

878 

879 # Update the working tree. 

880 # Handles dry_run. 

881 if mrepo is not None and mrepo.head.commit.binsha != binsha: 

882 # We must ensure that our destination sha (the one to point to) is in 

883 # the future of our current head. Otherwise, we will reset changes that 

884 # might have been done on the submodule, but were not yet pushed. We 

885 # also handle the case that history has been rewritten, leaving no 

886 # merge-base. In that case we behave conservatively, protecting possible 

887 # changes the user had done. 

888 may_reset = True 

889 if mrepo.head.commit.binsha != self.NULL_BIN_SHA: 

890 base_commit = mrepo.merge_base(mrepo.head.commit, hexsha) 

891 if len(base_commit) == 0 or (base_commit[0] is not None and base_commit[0].hexsha == hexsha): 

892 if force: 

893 msg = "Will force checkout or reset on local branch that is possibly in the future of" 

894 msg += " the commit it will be checked out to, effectively 'forgetting' new commits" 

895 _logger.debug(msg) 

896 else: 

897 msg = "Skipping %s on branch '%s' of submodule repo '%s' as it contains un-pushed commits" 

898 msg %= ( 

899 is_detached and "checkout" or "reset", 

900 mrepo.head, 

901 mrepo, 

902 ) 

903 _logger.info(msg) 

904 may_reset = False 

905 # END handle force 

906 # END handle if we are in the future 

907 

908 if may_reset and not force and mrepo.is_dirty(index=True, working_tree=True, untracked_files=True): 

909 raise RepositoryDirtyError(mrepo, "Cannot reset a dirty repository") 

910 # END handle force and dirty state 

911 # END handle empty repo 

912 

913 # END verify future/past 

914 progress.update( 

915 BEGIN | UPDWKTREE, 

916 0, 

917 1, 

918 prefix 

919 + "Updating working tree at %s for submodule %r to revision %s" % (self.path, self.name, hexsha), 

920 ) 

921 

922 if not dry_run and may_reset: 

923 if is_detached: 

924 # NOTE: For now we force. The user is not supposed to change 

925 # detached submodules anyway. Maybe at some point this becomes 

926 # an option, to properly handle user modifications - see below 

927 # for future options regarding rebase and merge. 

928 mrepo.git.checkout(hexsha, force=force) 

929 else: 

930 mrepo.head.reset(hexsha, index=True, working_tree=True) 

931 # END handle checkout 

932 # If we may reset/checkout. 

933 progress.update( 

934 END | UPDWKTREE, 

935 0, 

936 1, 

937 prefix + "Done updating working tree for submodule %r" % self.name, 

938 ) 

939 # END update to new commit only if needed 

940 except Exception as err: 

941 if not keep_going: 

942 raise 

943 _logger.error(str(err)) 

944 # END handle keep_going 

945 

946 # HANDLE RECURSION 

947 ################## 

948 if recursive: 

949 # In dry_run mode, the module might not exist. 

950 if mrepo is not None: 

951 for submodule in self.iter_items(self.module()): 

952 submodule.update( 

953 recursive, 

954 init, 

955 to_latest_revision, 

956 progress=progress, 

957 dry_run=dry_run, 

958 force=force, 

959 keep_going=keep_going, 

960 ) 

961 # END handle recursive update 

962 # END handle dry run 

963 # END for each submodule 

964 

965 return self 

966 

967 @unbare_repo 

968 def move(self, module_path: PathLike, configuration: bool = True, module: bool = True) -> "Submodule": 

969 """Move the submodule to a another module path. This involves physically moving 

970 the repository at our current path, changing the configuration, as well as 

971 adjusting our index entry accordingly. 

972 

973 :param module_path: 

974 The path to which to move our module in the parent repository's working 

975 tree, given as repository-relative or absolute path. Intermediate 

976 directories will be created accordingly. If the path already exists, it must 

977 be empty. Trailing (back)slashes are removed automatically. 

978 

979 :param configuration: 

980 If ``True``, the configuration will be adjusted to let the submodule point 

981 to the given path. 

982 

983 :param module: 

984 If ``True``, the repository managed by this submodule will be moved as well. 

985 If ``False``, we don't move the submodule's checkout, which may leave the 

986 parent repository in an inconsistent state. 

987 

988 :return: 

989 self 

990 

991 :raise ValueError: 

992 If the module path existed and was not empty, or was a file. 

993 

994 :note: 

995 Currently the method is not atomic, and it could leave the repository in an 

996 inconsistent state if a sub-step fails for some reason. 

997 """ 

998 if module + configuration < 1: 

999 raise ValueError("You must specify to move at least the module or the configuration of the submodule") 

1000 # END handle input 

1001 

1002 module_checkout_path = self._to_relative_path(self.repo, module_path) 

1003 

1004 # VERIFY DESTINATION 

1005 if module_checkout_path == self.path: 

1006 return self 

1007 # END handle no change 

1008 

1009 module_checkout_abspath = join_path_native(str(self.repo.working_tree_dir), module_checkout_path) 

1010 if osp.isfile(module_checkout_abspath): 

1011 raise ValueError("Cannot move repository onto a file: %s" % module_checkout_abspath) 

1012 # END handle target files 

1013 

1014 index = self.repo.index 

1015 tekey = index.entry_key(module_checkout_path, 0) 

1016 # if the target item already exists, fail 

1017 if configuration and tekey in index.entries: 

1018 raise ValueError("Index entry for target path did already exist") 

1019 # END handle index key already there 

1020 

1021 # Remove existing destination. 

1022 if module: 

1023 if osp.exists(module_checkout_abspath): 

1024 if len(os.listdir(module_checkout_abspath)): 

1025 raise ValueError("Destination module directory was not empty") 

1026 # END handle non-emptiness 

1027 

1028 if osp.islink(module_checkout_abspath): 

1029 os.remove(module_checkout_abspath) 

1030 else: 

1031 os.rmdir(module_checkout_abspath) 

1032 # END handle link 

1033 else: 

1034 # Recreate parent directories. 

1035 # NOTE: renames() does that now. 

1036 pass 

1037 # END handle existence 

1038 # END handle module 

1039 

1040 # Move the module into place if possible. 

1041 cur_path = self.abspath 

1042 renamed_module = False 

1043 if module and osp.exists(cur_path): 

1044 os.renames(cur_path, module_checkout_abspath) 

1045 renamed_module = True 

1046 

1047 if osp.isfile(osp.join(module_checkout_abspath, ".git")): 

1048 module_abspath = self._module_abspath(self.repo, self.path, self.name) 

1049 self._write_git_file_and_module_config(module_checkout_abspath, module_abspath) 

1050 # END handle git file rewrite 

1051 # END move physical module 

1052 

1053 # Rename the index entry - we have to manipulate the index directly as git-mv 

1054 # cannot be used on submodules... yeah. 

1055 previous_sm_path = self.path 

1056 try: 

1057 if configuration: 

1058 try: 

1059 ekey = index.entry_key(self.path, 0) 

1060 entry = index.entries[ekey] 

1061 del index.entries[ekey] 

1062 nentry = git.IndexEntry(entry[:3] + (module_checkout_path,) + entry[4:]) 

1063 index.entries[tekey] = nentry 

1064 except KeyError as e: 

1065 raise InvalidGitRepositoryError("Submodule's entry at %r did not exist" % (self.path)) from e 

1066 # END handle submodule doesn't exist 

1067 

1068 # Update configuration. 

1069 with self.config_writer(index=index) as writer: # Auto-write. 

1070 writer.set_value("path", module_checkout_path) 

1071 self.path = module_checkout_path 

1072 # END handle configuration flag 

1073 except Exception: 

1074 if renamed_module: 

1075 os.renames(module_checkout_abspath, cur_path) 

1076 # END undo module renaming 

1077 raise 

1078 # END handle undo rename 

1079 

1080 # Auto-rename submodule if its name was 'default', that is, the checkout 

1081 # directory. 

1082 if previous_sm_path == self.name: 

1083 self.rename(module_checkout_path) 

1084 

1085 return self 

1086 

1087 @unbare_repo 

1088 def remove( 

1089 self, 

1090 module: bool = True, 

1091 force: bool = False, 

1092 configuration: bool = True, 

1093 dry_run: bool = False, 

1094 ) -> "Submodule": 

1095 """Remove this submodule from the repository. This will remove our entry 

1096 from the ``.gitmodules`` file and the entry in the ``.git/config`` file. 

1097 

1098 :param module: 

1099 If ``True``, the checked out module we point to will be deleted as well. If 

1100 that module is currently on a commit outside any branch in the remote, or if 

1101 it is ahead of its tracking branch, or if there are modified or untracked 

1102 files in its working tree, then the removal will fail. In case the removal 

1103 of the repository fails for these reasons, the submodule status will not 

1104 have been altered. 

1105 

1106 If this submodule has child modules of its own, these will be deleted prior 

1107 to touching the direct submodule. 

1108 

1109 :param force: 

1110 Enforces the deletion of the module even though it contains modifications. 

1111 This basically enforces a brute-force file system based deletion. 

1112 

1113 :param configuration: 

1114 If ``True``, the submodule is deleted from the configuration, otherwise it 

1115 isn't. Although this should be enabled most of the time, this flag enables 

1116 you to safely delete the repository of your submodule. 

1117 

1118 :param dry_run: 

1119 If ``True``, we will not actually do anything, but throw the errors we would 

1120 usually throw. 

1121 

1122 :return: 

1123 self 

1124 

1125 :note: 

1126 Doesn't work in bare repositories. 

1127 

1128 :note: 

1129 Doesn't work atomically, as failure to remove any part of the submodule will 

1130 leave an inconsistent state. 

1131 

1132 :raise git.exc.InvalidGitRepositoryError: 

1133 Thrown if the repository cannot be deleted. 

1134 

1135 :raise OSError: 

1136 If directories or files could not be removed. 

1137 """ 

1138 if not (module or configuration): 

1139 raise ValueError("Need to specify to delete at least the module, or the configuration") 

1140 # END handle parameters 

1141 

1142 # Recursively remove children of this submodule. 

1143 nc = 0 

1144 for csm in self.children(): 

1145 nc += 1 

1146 csm.remove(module, force, configuration, dry_run) 

1147 del csm 

1148 

1149 if configuration and not dry_run and nc > 0: 

1150 # Ensure we don't leave the parent repository in a dirty state, and commit 

1151 # our changes. It's important for recursive, unforced, deletions to work as 

1152 # expected. 

1153 self.module().index.commit("Removed at least one of child-modules of '%s'" % self.name) 

1154 # END handle recursion 

1155 

1156 # DELETE REPOSITORY WORKING TREE 

1157 ################################ 

1158 if module and self.module_exists(): 

1159 mod = self.module() 

1160 git_dir = mod.git_dir 

1161 if force: 

1162 # Take the fast lane and just delete everything in our module path. 

1163 # TODO: If we run into permission problems, we have a highly 

1164 # inconsistent state. Delete the .git folders last, start with the 

1165 # submodules first. 

1166 mp = self.abspath 

1167 method: Union[None, Callable[[PathLike], None]] = None 

1168 if osp.islink(mp): 

1169 method = os.remove 

1170 elif osp.isdir(mp): 

1171 method = rmtree 

1172 elif osp.exists(mp): 

1173 raise AssertionError("Cannot forcibly delete repository as it was neither a link, nor a directory") 

1174 # END handle brutal deletion 

1175 if not dry_run: 

1176 assert method 

1177 method(mp) 

1178 # END apply deletion method 

1179 else: 

1180 # Verify we may delete our module. 

1181 if mod.is_dirty(index=True, working_tree=True, untracked_files=True): 

1182 raise InvalidGitRepositoryError( 

1183 "Cannot delete module at %s with any modifications, unless force is specified" 

1184 % mod.working_tree_dir 

1185 ) 

1186 # END check for dirt 

1187 

1188 # Figure out whether we have new commits compared to the remotes. 

1189 # NOTE: If the user pulled all the time, the remote heads might not have 

1190 # been updated, so commits coming from the remote look as if they come 

1191 # from us. But we stay strictly read-only and don't fetch beforehand. 

1192 for remote in mod.remotes: 

1193 num_branches_with_new_commits = 0 

1194 rrefs = remote.refs 

1195 for rref in rrefs: 

1196 num_branches_with_new_commits += len(mod.git.cherry(rref)) != 0 

1197 # END for each remote ref 

1198 # Not a single remote branch contained all our commits. 

1199 if len(rrefs) and num_branches_with_new_commits == len(rrefs): 

1200 raise InvalidGitRepositoryError( 

1201 "Cannot delete module at %s as there are new commits" % mod.working_tree_dir 

1202 ) 

1203 # END handle new commits 

1204 # We have to manually delete some references to allow resources to 

1205 # be cleaned up immediately when we are done with them, because 

1206 # Python's scoping is no more granular than the whole function (loop 

1207 # bodies are not scopes). When the objects stay alive longer, they 

1208 # can keep handles open. On Windows, this is a problem. 

1209 if len(rrefs): 

1210 del rref # skipcq: PYL-W0631 

1211 # END handle remotes 

1212 del rrefs 

1213 del remote 

1214 # END for each remote 

1215 

1216 # Finally delete our own submodule. 

1217 if not dry_run: 

1218 self._clear_cache() 

1219 wtd = mod.working_tree_dir 

1220 del mod # Release file-handles (Windows). 

1221 gc.collect() 

1222 rmtree(str(wtd)) 

1223 # END delete tree if possible 

1224 # END handle force 

1225 

1226 if not dry_run and osp.isdir(git_dir): 

1227 self._clear_cache() 

1228 rmtree(git_dir) 

1229 # END handle separate bare repository 

1230 # END handle module deletion 

1231 

1232 # Void our data so as not to delay invalid access. 

1233 if not dry_run: 

1234 self._clear_cache() 

1235 

1236 # DELETE CONFIGURATION 

1237 ###################### 

1238 if configuration and not dry_run: 

1239 # First the index-entry. 

1240 parent_index = self.repo.index 

1241 try: 

1242 del parent_index.entries[parent_index.entry_key(self.path, 0)] 

1243 except KeyError: 

1244 pass 

1245 # END delete entry 

1246 parent_index.write() 

1247 

1248 # Now git config - we need the config intact, otherwise we can't query 

1249 # information anymore. 

1250 

1251 with self.repo.config_writer() as gcp_writer: 

1252 gcp_writer.remove_section(sm_section(self.name)) 

1253 

1254 with self.config_writer() as sc_writer: 

1255 sc_writer.remove_section() 

1256 # END delete configuration 

1257 

1258 return self 

1259 

1260 def set_parent_commit(self, commit: Union[Commit_ish, str, None], check: bool = True) -> "Submodule": 

1261 """Set this instance to use the given commit whose tree is supposed to 

1262 contain the ``.gitmodules`` blob. 

1263 

1264 :param commit: 

1265 Commit-ish reference pointing at the root tree, or ``None`` to always point 

1266 to the most recent commit. 

1267 

1268 :param check: 

1269 If ``True``, relatively expensive checks will be performed to verify 

1270 validity of the submodule. 

1271 

1272 :raise ValueError: 

1273 If the commit's tree didn't contain the ``.gitmodules`` blob. 

1274 

1275 :raise ValueError: 

1276 If the parent commit didn't store this submodule under the current path. 

1277 

1278 :return: 

1279 self 

1280 """ 

1281 if commit is None: 

1282 self._parent_commit = None 

1283 return self 

1284 # END handle None 

1285 pcommit = self.repo.commit(commit) 

1286 pctree = pcommit.tree 

1287 if self.k_modules_file not in pctree: 

1288 raise ValueError("Tree of commit %s did not contain the %s file" % (commit, self.k_modules_file)) 

1289 # END handle exceptions 

1290 

1291 prev_pc = self._parent_commit 

1292 self._parent_commit = pcommit 

1293 

1294 if check: 

1295 parser = self._config_parser(self.repo, self._parent_commit, read_only=True) 

1296 if not parser.has_section(sm_section(self.name)): 

1297 self._parent_commit = prev_pc 

1298 raise ValueError("Submodule at path %r did not exist in parent commit %s" % (self.path, commit)) 

1299 # END handle submodule did not exist 

1300 # END handle checking mode 

1301 

1302 # Update our sha, it could have changed. 

1303 # If check is False, we might see a parent-commit that doesn't even contain the 

1304 # submodule anymore. in that case, mark our sha as being NULL. 

1305 try: 

1306 self.binsha = pctree[str(self.path)].binsha 

1307 except KeyError: 

1308 self.binsha = self.NULL_BIN_SHA 

1309 

1310 self._clear_cache() 

1311 return self 

1312 

1313 @unbare_repo 

1314 def config_writer( 

1315 self, index: Union["IndexFile", None] = None, write: bool = True 

1316 ) -> SectionConstraint["SubmoduleConfigParser"]: 

1317 """ 

1318 :return: 

1319 A config writer instance allowing you to read and write the data belonging 

1320 to this submodule into the ``.gitmodules`` file. 

1321 

1322 :param index: 

1323 If not ``None``, an :class:`~git.index.base.IndexFile` instance which should 

1324 be written. Defaults to the index of the :class:`Submodule`'s parent 

1325 repository. 

1326 

1327 :param write: 

1328 If ``True``, the index will be written each time a configuration value changes. 

1329 

1330 :note: 

1331 The parameters allow for a more efficient writing of the index, as you can 

1332 pass in a modified index on your own, prevent automatic writing, and write 

1333 yourself once the whole operation is complete. 

1334 

1335 :raise ValueError: 

1336 If trying to get a writer on a parent_commit which does not match the 

1337 current head commit. 

1338 

1339 :raise IOError: 

1340 If the ``.gitmodules`` file/blob could not be read. 

1341 """ 

1342 writer = self._config_parser_constrained(read_only=False) 

1343 if index is not None: 

1344 writer.config._index = index 

1345 writer.config._auto_write = write 

1346 return writer 

1347 

1348 @unbare_repo 

1349 def rename(self, new_name: str) -> "Submodule": 

1350 """Rename this submodule. 

1351 

1352 :note: 

1353 This method takes care of renaming the submodule in various places, such as: 

1354 

1355 * ``$parent_git_dir / config`` 

1356 * ``$working_tree_dir / .gitmodules`` 

1357 * (git >= v1.8.0: move submodule repository to new name) 

1358 

1359 As ``.gitmodules`` will be changed, you would need to make a commit afterwards. 

1360 The changed ``.gitmodules`` file will already be added to the index. 

1361 

1362 :return: 

1363 This :class:`Submodule` instance 

1364 """ 

1365 if self.name == new_name: 

1366 return self 

1367 

1368 # .git/config 

1369 with self.repo.config_writer() as pw: 

1370 # As we ourselves didn't write anything about submodules into the parent 

1371 # .git/config, we will not require it to exist, and just ignore missing 

1372 # entries. 

1373 if pw.has_section(sm_section(self.name)): 

1374 pw.rename_section(sm_section(self.name), sm_section(new_name)) 

1375 

1376 # .gitmodules 

1377 with self.config_writer(write=True).config as cw: 

1378 cw.rename_section(sm_section(self.name), sm_section(new_name)) 

1379 

1380 self._name = new_name 

1381 

1382 # .git/modules 

1383 mod = self.module() 

1384 if mod.has_separate_working_tree(): 

1385 destination_module_abspath = self._module_abspath(self.repo, self.path, new_name) 

1386 source_dir = mod.git_dir 

1387 # Let's be sure the submodule name is not so obviously tied to a directory. 

1388 if str(destination_module_abspath).startswith(str(mod.git_dir)): 

1389 tmp_dir = self._module_abspath(self.repo, self.path, str(uuid.uuid4())) 

1390 os.renames(source_dir, tmp_dir) 

1391 source_dir = tmp_dir 

1392 # END handle self-containment 

1393 os.renames(source_dir, destination_module_abspath) 

1394 if mod.working_tree_dir: 

1395 self._write_git_file_and_module_config(mod.working_tree_dir, destination_module_abspath) 

1396 # END move separate git repository 

1397 

1398 return self 

1399 

1400 # } END edit interface 

1401 

1402 # { Query Interface 

1403 

1404 @unbare_repo 

1405 def module(self) -> "Repo": 

1406 """ 

1407 :return: 

1408 :class:`~git.repo.base.Repo` instance initialized from the repository at our 

1409 submodule path 

1410 

1411 :raise git.exc.InvalidGitRepositoryError: 

1412 If a repository was not available. 

1413 This could also mean that it was not yet initialized. 

1414 """ 

1415 module_checkout_abspath = self.abspath 

1416 try: 

1417 repo = git.Repo(module_checkout_abspath) 

1418 if repo != self.repo: 

1419 return repo 

1420 # END handle repo uninitialized 

1421 except (InvalidGitRepositoryError, NoSuchPathError) as e: 

1422 raise InvalidGitRepositoryError("No valid repository at %s" % module_checkout_abspath) from e 

1423 else: 

1424 raise InvalidGitRepositoryError("Repository at %r was not yet checked out" % module_checkout_abspath) 

1425 # END handle exceptions 

1426 

1427 def module_exists(self) -> bool: 

1428 """ 

1429 :return: 

1430 ``True`` if our module exists and is a valid git repository. 

1431 See the :meth:`module` method. 

1432 """ 

1433 try: 

1434 self.module() 

1435 return True 

1436 except Exception: 

1437 return False 

1438 # END handle exception 

1439 

1440 def exists(self) -> bool: 

1441 """ 

1442 :return: 

1443 ``True`` if the submodule exists, ``False`` otherwise. 

1444 Please note that a submodule may exist (in the ``.gitmodules`` file) even 

1445 though its module doesn't exist on disk. 

1446 """ 

1447 # Keep attributes for later, and restore them if we have no valid data. 

1448 # This way we do not actually alter the state of the object. 

1449 loc = locals() 

1450 for attr in self._cache_attrs: 

1451 try: 

1452 if hasattr(self, attr): 

1453 loc[attr] = getattr(self, attr) 

1454 # END if we have the attribute cache 

1455 except (cp.NoSectionError, ValueError): 

1456 # On PY3, this can happen apparently... don't know why this doesn't 

1457 # happen on PY2. 

1458 pass 

1459 # END for each attr 

1460 self._clear_cache() 

1461 

1462 try: 

1463 try: 

1464 self.path # noqa: B018 

1465 return True 

1466 except Exception: 

1467 return False 

1468 # END handle exceptions 

1469 finally: 

1470 for attr in self._cache_attrs: 

1471 if attr in loc: 

1472 setattr(self, attr, loc[attr]) 

1473 # END if we have a cache 

1474 # END reapply each attribute 

1475 # END handle object state consistency 

1476 

1477 @property 

1478 def branch(self) -> "Head": 

1479 """ 

1480 :return: 

1481 The branch instance that we are to checkout 

1482 

1483 :raise git.exc.InvalidGitRepositoryError: 

1484 If our module is not yet checked out. 

1485 """ 

1486 return mkhead(self.module(), self._branch_path) 

1487 

1488 @property 

1489 def branch_path(self) -> PathLike: 

1490 """ 

1491 :return: 

1492 Full repository-relative path as string to the branch we would checkout from 

1493 the remote and track 

1494 """ 

1495 return self._branch_path 

1496 

1497 @property 

1498 def branch_name(self) -> str: 

1499 """ 

1500 :return: 

1501 The name of the branch, which is the shortest possible branch name 

1502 """ 

1503 # Use an instance method, for this we create a temporary Head instance which 

1504 # uses a repository that is available at least (it makes no difference). 

1505 return git.Head(self.repo, self._branch_path).name 

1506 

1507 @property 

1508 def url(self) -> str: 

1509 """:return: The url to the repository our submodule's repository refers to""" 

1510 return self._url 

1511 

1512 @property 

1513 def parent_commit(self) -> "Commit": 

1514 """ 

1515 :return: 

1516 :class:`~git.objects.commit.Commit` instance with the tree containing the 

1517 ``.gitmodules`` file 

1518 

1519 :note: 

1520 Will always point to the current head's commit if it was not set explicitly. 

1521 """ 

1522 if self._parent_commit is None: 

1523 return self.repo.commit() 

1524 return self._parent_commit 

1525 

1526 @property 

1527 def name(self) -> str: 

1528 """ 

1529 :return: 

1530 The name of this submodule. It is used to identify it within the 

1531 ``.gitmodules`` file. 

1532 

1533 :note: 

1534 By default, this is the name is the path at which to find the submodule, but 

1535 in GitPython it should be a unique identifier similar to the identifiers 

1536 used for remotes, which allows to change the path of the submodule easily. 

1537 """ 

1538 return self._name 

1539 

1540 def config_reader(self) -> SectionConstraint[SubmoduleConfigParser]: 

1541 """ 

1542 :return: 

1543 ConfigReader instance which allows you to query the configuration values of 

1544 this submodule, as provided by the ``.gitmodules`` file. 

1545 

1546 :note: 

1547 The config reader will actually read the data directly from the repository 

1548 and thus does not need nor care about your working tree. 

1549 

1550 :note: 

1551 Should be cached by the caller and only kept as long as needed. 

1552 

1553 :raise IOError: 

1554 If the ``.gitmodules`` file/blob could not be read. 

1555 """ 

1556 return self._config_parser_constrained(read_only=True) 

1557 

1558 def children(self) -> IterableList["Submodule"]: 

1559 """ 

1560 :return: 

1561 IterableList(Submodule, ...) An iterable list of :class:`Submodule` 

1562 instances which are children of this submodule or 0 if the submodule is not 

1563 checked out. 

1564 """ 

1565 return self._get_intermediate_items(self) 

1566 

1567 # } END query interface 

1568 

1569 # { Iterable Interface 

1570 

1571 @classmethod 

1572 def iter_items( 

1573 cls, 

1574 repo: "Repo", 

1575 parent_commit: Union[Commit_ish, str] = "HEAD", 

1576 *args: Any, 

1577 **kwargs: Any, 

1578 ) -> Iterator["Submodule"]: 

1579 """ 

1580 :return: 

1581 Iterator yielding :class:`Submodule` instances available in the given 

1582 repository 

1583 """ 

1584 try: 

1585 pc = repo.commit(parent_commit) # Parent commit instance 

1586 parser = cls._config_parser(repo, pc, read_only=True) 

1587 except (IOError, BadName): 

1588 return 

1589 # END handle empty iterator 

1590 

1591 for sms in parser.sections(): 

1592 n = sm_name(sms) 

1593 p = parser.get(sms, "path") 

1594 u = parser.get(sms, "url") 

1595 b = cls.k_head_default 

1596 if parser.has_option(sms, cls.k_head_option): 

1597 b = str(parser.get(sms, cls.k_head_option)) 

1598 # END handle optional information 

1599 

1600 # Get the binsha. 

1601 index = repo.index 

1602 try: 

1603 rt = pc.tree # Root tree 

1604 sm = rt[p] 

1605 except KeyError: 

1606 # Try the index, maybe it was just added. 

1607 try: 

1608 entry = index.entries[index.entry_key(p, 0)] 

1609 sm = Submodule(repo, entry.binsha, entry.mode, entry.path) 

1610 except KeyError: 

1611 # The submodule doesn't exist, probably it wasn't removed from the 

1612 # .gitmodules file. 

1613 continue 

1614 # END handle keyerror 

1615 # END handle critical error 

1616 

1617 # Make sure we are looking at a submodule object. 

1618 if type(sm) is not git.objects.submodule.base.Submodule: 

1619 continue 

1620 

1621 # Fill in remaining info - saves time as it doesn't have to be parsed again. 

1622 sm._name = n 

1623 if pc != repo.commit(): 

1624 sm._parent_commit = pc 

1625 # END set only if not most recent! 

1626 sm._branch_path = git.Head.to_full_path(b) 

1627 sm._url = u 

1628 

1629 yield sm 

1630 # END for each section 

1631 

1632 # } END iterable interface