Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/git/objects/submodule/root.py: 20%

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

141 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__ = ["RootModule", "RootUpdateProgress"] 

5 

6import logging 

7 

8# typing ------------------------------------------------------------------- 

9from typing import TYPE_CHECKING, Union 

10 

11import git 

12from git.exc import InvalidGitRepositoryError 

13from git.types import Commit_ish 

14from git.util import IterableList 

15 

16from .base import Submodule, UpdateProgress 

17from .util import find_first_remote_branch 

18 

19if TYPE_CHECKING: 

20 from git.repo import Repo 

21 

22# ---------------------------------------------------------------------------- 

23 

24_logger = logging.getLogger(__name__) 

25 

26 

27class RootUpdateProgress(UpdateProgress): 

28 """Utility class which adds more opcodes to 

29 :class:`~git.objects.submodule.base.UpdateProgress`.""" 

30 

31 REMOVE, PATHCHANGE, BRANCHCHANGE, URLCHANGE = [ 

32 1 << x for x in range(UpdateProgress._num_op_codes, UpdateProgress._num_op_codes + 4) 

33 ] 

34 _num_op_codes = UpdateProgress._num_op_codes + 4 

35 

36 __slots__ = () 

37 

38 

39BEGIN = RootUpdateProgress.BEGIN 

40END = RootUpdateProgress.END 

41REMOVE = RootUpdateProgress.REMOVE 

42BRANCHCHANGE = RootUpdateProgress.BRANCHCHANGE 

43URLCHANGE = RootUpdateProgress.URLCHANGE 

44PATHCHANGE = RootUpdateProgress.PATHCHANGE 

45 

46 

47class RootModule(Submodule): 

48 """A (virtual) root of all submodules in the given repository. 

49 

50 This can be used to more easily traverse all submodules of the 

51 superproject (master repository). 

52 """ 

53 

54 __slots__ = () 

55 

56 k_root_name = "__ROOT__" 

57 

58 def __init__(self, repo: "Repo") -> None: 

59 # repo, binsha, mode=None, path=None, name = None, parent_commit=None, url=None, ref=None) 

60 super().__init__( 

61 repo, 

62 binsha=self.NULL_BIN_SHA, 

63 mode=self.k_default_mode, 

64 path="", 

65 name=self.k_root_name, 

66 parent_commit=repo.head.commit, 

67 url="", 

68 branch_path=git.Head.to_full_path(self.k_head_default), 

69 ) 

70 

71 def _clear_cache(self) -> None: 

72 """May not do anything.""" 

73 pass 

74 

75 # { Interface 

76 

77 def update( # type: ignore[override] 

78 self, 

79 previous_commit: Union[Commit_ish, str, None] = None, 

80 recursive: bool = True, 

81 force_remove: bool = False, 

82 init: bool = True, 

83 to_latest_revision: bool = False, 

84 progress: Union[None, "RootUpdateProgress"] = None, 

85 dry_run: bool = False, 

86 force_reset: bool = False, 

87 keep_going: bool = False, 

88 no_fetch: bool = False, 

89 ) -> "RootModule": 

90 """Update the submodules of this repository to the current HEAD commit. 

91 

92 This method behaves smartly by determining changes of the path of a submodule's 

93 repository, next to changes to the to-be-checked-out commit or the branch to be 

94 checked out. This works if the submodule's ID does not change. 

95 

96 Additionally it will detect addition and removal of submodules, which will be 

97 handled gracefully. 

98 

99 :param previous_commit: 

100 If set to a commit-ish, the commit we should use as the previous commit the 

101 HEAD pointed to before it was set to the commit it points to now. 

102 If ``None``, it defaults to ``HEAD@{1}`` otherwise. 

103 

104 :param recursive: 

105 If ``True``, the children of submodules will be updated as well using the 

106 same technique. 

107 

108 :param force_remove: 

109 If submodules have been deleted, they will be forcibly removed. Otherwise 

110 the update may fail if a submodule's repository cannot be deleted as changes 

111 have been made to it. 

112 (See :meth:`Submodule.update <git.objects.submodule.base.Submodule.update>` 

113 for more information.) 

114 

115 :param init: 

116 If we encounter a new module which would need to be initialized, then do it. 

117 

118 :param to_latest_revision: 

119 If ``True``, instead of checking out the revision pointed to by this 

120 submodule's sha, the checked out tracking branch will be merged with the 

121 latest remote branch fetched from the repository's origin. 

122 

123 Unless `force_reset` is specified, a local tracking branch will never be 

124 reset into its past, therefore the remote branch must be in the future for 

125 this to have an effect. 

126 

127 :param force_reset: 

128 If ``True``, submodules may checkout or reset their branch even if the 

129 repository has pending changes that would be overwritten, or if the local 

130 tracking branch is in the future of the remote tracking branch and would be 

131 reset into its past. 

132 

133 :param progress: 

134 :class:`RootUpdateProgress` instance, or ``None`` if no progress should be 

135 sent. 

136 

137 :param dry_run: 

138 If ``True``, operations will not actually be performed. Progress messages 

139 will change accordingly to indicate the WOULD DO state of the operation. 

140 

141 :param keep_going: 

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

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

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

145 In conjunction with `dry_run`, this can be useful to anticipate all errors 

146 when updating submodules. 

147 

148 :param no_fetch: 

149 If ``True``, update using locally available objects and remote-tracking 

150 refs without fetching or cloning. Cached refs are preserved and used even 

151 when a submodule's URL changes. 

152 

153 :return: 

154 self 

155 """ 

156 if self.repo.bare: 

157 raise InvalidGitRepositoryError("Cannot update submodules in bare repositories") 

158 # END handle bare 

159 

160 if progress is None: 

161 progress = RootUpdateProgress() 

162 # END ensure progress is set 

163 

164 prefix = "" 

165 if dry_run: 

166 prefix = "DRY-RUN: " 

167 

168 repo = self.repo 

169 sms: "IterableList[Submodule]" = IterableList("name") 

170 

171 try: 

172 # SETUP BASE COMMIT 

173 ################### 

174 cur_commit = repo.head.commit 

175 if previous_commit is None: 

176 try: 

177 previous_commit = repo.commit(repo.head.log_entry(-1).oldhexsha) 

178 if previous_commit.binsha == previous_commit.NULL_BIN_SHA: 

179 raise IndexError 

180 # END handle initial commit 

181 except IndexError: 

182 # In new repositories, there is no previous commit. 

183 previous_commit = cur_commit 

184 # END exception handling 

185 else: 

186 previous_commit = repo.commit(previous_commit) # Obtain commit object. 

187 # END handle previous commit 

188 

189 psms: "IterableList[Submodule]" = self.list_items(repo, parent_commit=previous_commit) 

190 sms = self.list_items(repo) 

191 spsms = set(psms) 

192 ssms = set(sms) 

193 

194 # HANDLE REMOVALS 

195 ################### 

196 rrsm = spsms - ssms 

197 len_rrsm = len(rrsm) 

198 

199 for i, rsm in enumerate(rrsm): 

200 op = REMOVE 

201 if i == 0: 

202 op |= BEGIN 

203 # END handle begin 

204 

205 # Fake it into thinking its at the current commit to allow deletion 

206 # of previous module. Trigger the cache to be updated before that. 

207 progress.update( 

208 op, 

209 i, 

210 len_rrsm, 

211 prefix + "Removing submodule %r at %s" % (rsm.name, rsm.abspath), 

212 ) 

213 rsm._parent_commit = repo.head.commit 

214 rsm.remove( 

215 configuration=False, 

216 module=True, 

217 force=force_remove, 

218 dry_run=dry_run, 

219 ) 

220 

221 if i == len_rrsm - 1: 

222 op |= END 

223 # END handle end 

224 progress.update(op, i, len_rrsm, prefix + "Done removing submodule %r" % rsm.name) 

225 # END for each removed submodule 

226 

227 # HANDLE PATH RENAMES 

228 ##################### 

229 # URL changes + branch changes. 

230 csms = spsms & ssms 

231 len_csms = len(csms) 

232 for i, csm in enumerate(csms): 

233 psm: "Submodule" = psms[csm.name] 

234 sm: "Submodule" = sms[csm.name] 

235 

236 # PATH CHANGES 

237 ############## 

238 if sm.path != psm.path and psm.module_exists(): 

239 progress.update( 

240 BEGIN | PATHCHANGE, 

241 i, 

242 len_csms, 

243 prefix + "Moving repository of submodule %r from %s to %s" % (sm.name, psm.abspath, sm.abspath), 

244 ) 

245 # Move the module to the new path. 

246 if not dry_run: 

247 psm.move(sm.path, module=True, configuration=False) 

248 # END handle dry_run 

249 progress.update( 

250 END | PATHCHANGE, 

251 i, 

252 len_csms, 

253 prefix + "Done moving repository of submodule %r" % sm.name, 

254 ) 

255 # END handle path changes 

256 

257 if sm.module_exists(): 

258 # HANDLE URL CHANGE 

259 ################### 

260 if sm.url != psm.url: 

261 # When fetching, add the new remote and remove the old one. 

262 # This way, if the url just changes, the commits will not have 

263 # to be re-retrieved. 

264 nn = "__new_origin__" 

265 smm = sm.module() 

266 rmts = smm.remotes 

267 

268 # Don't do anything if we already have the url we search in 

269 # place. 

270 if len([r for r in rmts if r.url == sm.url]) == 0: 

271 progress.update( 

272 BEGIN | URLCHANGE, 

273 i, 

274 len_csms, 

275 prefix + "Changing url of submodule %r from %s to %s" % (sm.name, psm.url, sm.url), 

276 ) 

277 

278 if not dry_run: 

279 previous_remote = None 

280 for remote in rmts: 

281 if remote.url == psm.url: 

282 previous_remote = remote 

283 break 

284 # END if urls match 

285 # END for each remote 

286 

287 # If we didn't find a matching remote, but have exactly 

288 # one, we can safely use this one. 

289 if previous_remote is None: 

290 if len(rmts) == 1: 

291 previous_remote = rmts[0] 

292 else: 

293 # If we have not found any remote with the 

294 # original URL we may not have a name. This is a 

295 # special case, and its okay to fail here. 

296 # Alternatively we could just generate a unique 

297 # name and leave all existing ones in place. 

298 raise InvalidGitRepositoryError( 

299 "Couldn't find original remote-repo at url %r" % psm.url 

300 ) 

301 # END handle one single remote 

302 # END handle check we found a remote 

303 

304 if no_fetch: 

305 # A new remote would have no cached refs. Preserve 

306 # the existing refs and tracking configuration for 

307 # offline updates instead of replacing the remote. 

308 previous_remote.set_url(git.Git.polish_url(sm.url, expand_vars=False)) 

309 else: 

310 assert nn not in [r.name for r in rmts] 

311 smr = smm.create_remote(nn, sm.url) 

312 smr.fetch(progress=progress) 

313 

314 # If we have a tracking branch, it should be available 

315 # in the new remote as well. 

316 if len([r for r in smr.refs if r.remote_head == sm.branch_name]) == 0: 

317 raise ValueError( 

318 "Submodule branch named %r was not available in new submodule remote at %r" 

319 % (sm.branch_name, sm.url) 

320 ) 

321 # END head is not detached 

322 

323 orig_name = previous_remote.name 

324 smm.delete_remote(previous_remote) 

325 # NOTE: Currently we leave tags from the deleted remotes 

326 # as well as separate tracking branches in the possibly 

327 # totally changed repository (someone could have changed 

328 # the url to another project). At some point, one might 

329 # want to clean it up, but the danger is high to remove 

330 # stuff the user has added explicitly. 

331 

332 # Rename the new remote back to what it was. 

333 smr.rename(orig_name) 

334 

335 # Early on, we verified that the our current tracking 

336 # branch exists in the remote. Now we have to ensure 

337 # that the sha we point to is still contained in the new 

338 # remote tracking branch. 

339 smsha = sm.binsha 

340 found = False 

341 rref = smr.refs[self.branch_name] 

342 for c in rref.commit.traverse(): 

343 if c.binsha == smsha: 

344 found = True 

345 break 

346 # END traverse all commits in search for sha 

347 # END for each commit 

348 

349 if not found: 

350 # Adjust our internal binsha to use the one of the 

351 # remote this way, it will be checked out in the 

352 # next step. This will change the submodule relative 

353 # to us, so the user will be able to commit the 

354 # change easily. 

355 _logger.warning( 

356 "Current sha %s was not contained in the tracking\ 

357 branch at the new remote, setting it the the remote's tracking branch", 

358 sm.hexsha, 

359 ) 

360 sm.binsha = rref.commit.binsha 

361 # END reset binsha 

362 

363 # NOTE: All checkout is performed by the base 

364 # implementation of update. 

365 # END handle dry_run 

366 progress.update( 

367 END | URLCHANGE, 

368 i, 

369 len_csms, 

370 prefix + "Done adjusting url of submodule %r" % (sm.name), 

371 ) 

372 # END skip remote handling if new url already exists in module 

373 # END handle url 

374 

375 # HANDLE PATH CHANGES 

376 ##################### 

377 if sm.branch_path != psm.branch_path: 

378 # Finally, create a new tracking branch which tracks the new 

379 # remote branch. 

380 progress.update( 

381 BEGIN | BRANCHCHANGE, 

382 i, 

383 len_csms, 

384 prefix 

385 + "Changing branch of submodule %r from %s to %s" 

386 % (sm.name, psm.branch_path, sm.branch_path), 

387 ) 

388 if not dry_run: 

389 smm = sm.module() 

390 smmr = smm.remotes 

391 # As the branch might not exist yet, fetch all remotes 

392 # unless restricted to locally cached refs. 

393 if not no_fetch: 

394 for remote in smmr: 

395 remote.fetch(progress=progress) 

396 # END for each remote 

397 

398 try: 

399 tbr = git.Head.create( 

400 smm, 

401 sm.branch_name, 

402 logmsg="branch: Created from HEAD", 

403 ) 

404 except OSError: 

405 # ...or reuse the existing one. 

406 tbr = git.Head(smm, sm.branch_path) 

407 # END ensure tracking branch exists 

408 

409 tbr.set_tracking_branch(find_first_remote_branch(smmr, sm.branch_name)) 

410 # NOTE: All head-resetting is done in the base 

411 # implementation of update but we will have to checkout the 

412 # new branch here. As it still points to the currently 

413 # checked out commit, we don't do any harm. 

414 # As we don't want to update working-tree or index, changing 

415 # the ref is all there is to do. 

416 smm.head.reference = tbr 

417 # END handle dry_run 

418 

419 progress.update( 

420 END | BRANCHCHANGE, 

421 i, 

422 len_csms, 

423 prefix + "Done changing branch of submodule %r" % sm.name, 

424 ) 

425 # END handle branch 

426 # END handle 

427 # END for each common submodule 

428 except Exception as err: 

429 if not keep_going: 

430 raise 

431 _logger.error(str(err)) 

432 # END handle keep_going 

433 

434 # FINALLY UPDATE ALL ACTUAL SUBMODULES 

435 ###################################### 

436 for sm in sms: 

437 # Update the submodule using the default method. 

438 sm.update( 

439 recursive=False, 

440 init=init, 

441 to_latest_revision=to_latest_revision, 

442 progress=progress, 

443 dry_run=dry_run, 

444 force=force_reset, 

445 keep_going=keep_going, 

446 no_fetch=no_fetch, 

447 ) 

448 

449 # Update recursively depth first - question is which inconsistent state will 

450 # be better in case it fails somewhere. Defective branch or defective depth. 

451 # The RootSubmodule type will never process itself, which was done in the 

452 # previous expression. 

453 if recursive: 

454 # The module would exist by now if we are not in dry_run mode. 

455 if sm.module_exists(): 

456 type(self)(sm.module()).update( 

457 recursive=True, 

458 force_remove=force_remove, 

459 init=init, 

460 to_latest_revision=to_latest_revision, 

461 progress=progress, 

462 dry_run=dry_run, 

463 force_reset=force_reset, 

464 keep_going=keep_going, 

465 no_fetch=no_fetch, 

466 ) 

467 # END handle dry_run 

468 # END handle recursive 

469 # END for each submodule to update 

470 

471 return self 

472 

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

474 """:return: The actual repository containing the submodules""" 

475 return self.repo 

476 

477 # } END interface 

478 

479 

480# } END classes