Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/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

136 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 

8import git 

9from git.exc import InvalidGitRepositoryError 

10 

11from .base import Submodule, UpdateProgress 

12from .util import find_first_remote_branch 

13 

14# typing ------------------------------------------------------------------- 

15 

16from typing import TYPE_CHECKING, Union 

17 

18from git.types import Commit_ish 

19 

20if TYPE_CHECKING: 

21 from git.repo import Repo 

22 from git.util import IterableList 

23 

24# ---------------------------------------------------------------------------- 

25 

26_logger = logging.getLogger(__name__) 

27 

28 

29class RootUpdateProgress(UpdateProgress): 

30 """Utility class which adds more opcodes to 

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

32 

33 REMOVE, PATHCHANGE, BRANCHCHANGE, URLCHANGE = [ 

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

35 ] 

36 _num_op_codes = UpdateProgress._num_op_codes + 4 

37 

38 __slots__ = () 

39 

40 

41BEGIN = RootUpdateProgress.BEGIN 

42END = RootUpdateProgress.END 

43REMOVE = RootUpdateProgress.REMOVE 

44BRANCHCHANGE = RootUpdateProgress.BRANCHCHANGE 

45URLCHANGE = RootUpdateProgress.URLCHANGE 

46PATHCHANGE = RootUpdateProgress.PATHCHANGE 

47 

48 

49class RootModule(Submodule): 

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

51 

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

53 superproject (master repository). 

54 """ 

55 

56 __slots__ = () 

57 

58 k_root_name = "__ROOT__" 

59 

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

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

62 super().__init__( 

63 repo, 

64 binsha=self.NULL_BIN_SHA, 

65 mode=self.k_default_mode, 

66 path="", 

67 name=self.k_root_name, 

68 parent_commit=repo.head.commit, 

69 url="", 

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

71 ) 

72 

73 def _clear_cache(self) -> None: 

74 """May not do anything.""" 

75 pass 

76 

77 # { Interface 

78 

79 def update( # type: ignore[override] 

80 self, 

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

82 recursive: bool = True, 

83 force_remove: bool = False, 

84 init: bool = True, 

85 to_latest_revision: bool = False, 

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

87 dry_run: bool = False, 

88 force_reset: bool = False, 

89 keep_going: bool = False, 

90 ) -> "RootModule": 

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

92 

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

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

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

96 

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

98 handled gracefully. 

99 

100 :param previous_commit: 

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

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

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

104 

105 :param recursive: 

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

107 same technique. 

108 

109 :param force_remove: 

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

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

112 have been made to it. 

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

114 for more information.) 

115 

116 :param init: 

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

118 

119 :param to_latest_revision: 

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

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

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

123 

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

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

126 this to have an effect. 

127 

128 :param force_reset: 

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

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

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

132 reset into its past. 

133 

134 :param progress: 

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

136 sent. 

137 

138 :param dry_run: 

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

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

141 

142 :param keep_going: 

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

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

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

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

147 when updating submodules. 

148 

149 :return: 

150 self 

151 """ 

152 if self.repo.bare: 

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

154 # END handle bare 

155 

156 if progress is None: 

157 progress = RootUpdateProgress() 

158 # END ensure progress is set 

159 

160 prefix = "" 

161 if dry_run: 

162 prefix = "DRY-RUN: " 

163 

164 repo = self.repo 

165 

166 try: 

167 # SETUP BASE COMMIT 

168 ################### 

169 cur_commit = repo.head.commit 

170 if previous_commit is None: 

171 try: 

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

173 if previous_commit.binsha == previous_commit.NULL_BIN_SHA: 

174 raise IndexError 

175 # END handle initial commit 

176 except IndexError: 

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

178 previous_commit = cur_commit 

179 # END exception handling 

180 else: 

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

182 # END handle previous commit 

183 

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

185 sms: "IterableList[Submodule]" = self.list_items(repo) 

186 spsms = set(psms) 

187 ssms = set(sms) 

188 

189 # HANDLE REMOVALS 

190 ################### 

191 rrsm = spsms - ssms 

192 len_rrsm = len(rrsm) 

193 

194 for i, rsm in enumerate(rrsm): 

195 op = REMOVE 

196 if i == 0: 

197 op |= BEGIN 

198 # END handle begin 

199 

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

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

202 progress.update( 

203 op, 

204 i, 

205 len_rrsm, 

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

207 ) 

208 rsm._parent_commit = repo.head.commit 

209 rsm.remove( 

210 configuration=False, 

211 module=True, 

212 force=force_remove, 

213 dry_run=dry_run, 

214 ) 

215 

216 if i == len_rrsm - 1: 

217 op |= END 

218 # END handle end 

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

220 # END for each removed submodule 

221 

222 # HANDLE PATH RENAMES 

223 ##################### 

224 # URL changes + branch changes. 

225 csms = spsms & ssms 

226 len_csms = len(csms) 

227 for i, csm in enumerate(csms): 

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

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

230 

231 # PATH CHANGES 

232 ############## 

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

234 progress.update( 

235 BEGIN | PATHCHANGE, 

236 i, 

237 len_csms, 

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

239 ) 

240 # Move the module to the new path. 

241 if not dry_run: 

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

243 # END handle dry_run 

244 progress.update( 

245 END | PATHCHANGE, 

246 i, 

247 len_csms, 

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

249 ) 

250 # END handle path changes 

251 

252 if sm.module_exists(): 

253 # HANDLE URL CHANGE 

254 ################### 

255 if sm.url != psm.url: 

256 # Add the new remote, remove the old one. 

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

258 # to be re-retrieved. 

259 nn = "__new_origin__" 

260 smm = sm.module() 

261 rmts = smm.remotes 

262 

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

264 # place. 

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

266 progress.update( 

267 BEGIN | URLCHANGE, 

268 i, 

269 len_csms, 

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

271 ) 

272 

273 if not dry_run: 

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

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

276 smr.fetch(progress=progress) 

277 

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

279 # in the new remote as well. 

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

281 raise ValueError( 

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

283 % (sm.branch_name, sm.url) 

284 ) 

285 # END head is not detached 

286 

287 # Now delete the changed one. 

288 rmt_for_deletion = None 

289 for remote in rmts: 

290 if remote.url == psm.url: 

291 rmt_for_deletion = remote 

292 break 

293 # END if urls match 

294 # END for each remote 

295 

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

297 # one, we can safely use this one. 

298 if rmt_for_deletion is None: 

299 if len(rmts) == 1: 

300 rmt_for_deletion = rmts[0] 

301 else: 

302 # If we have not found any remote with the 

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

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

305 # Alternatively we could just generate a unique 

306 # name and leave all existing ones in place. 

307 raise InvalidGitRepositoryError( 

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

309 ) 

310 # END handle one single remote 

311 # END handle check we found a remote 

312 

313 orig_name = rmt_for_deletion.name 

314 smm.delete_remote(rmt_for_deletion) 

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

316 # as well as separate tracking branches in the possibly 

317 # totally changed repository (someone could have changed 

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

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

320 # stuff the user has added explicitly. 

321 

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

323 smr.rename(orig_name) 

324 

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

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

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

328 # remote tracking branch. 

329 smsha = sm.binsha 

330 found = False 

331 rref = smr.refs[self.branch_name] 

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

333 if c.binsha == smsha: 

334 found = True 

335 break 

336 # END traverse all commits in search for sha 

337 # END for each commit 

338 

339 if not found: 

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

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

342 # next step. This will change the submodule relative 

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

344 # change easily. 

345 _logger.warning( 

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

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

348 sm.hexsha, 

349 ) 

350 sm.binsha = rref.commit.binsha 

351 # END reset binsha 

352 

353 # NOTE: All checkout is performed by the base 

354 # implementation of update. 

355 # END handle dry_run 

356 progress.update( 

357 END | URLCHANGE, 

358 i, 

359 len_csms, 

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

361 ) 

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

363 # END handle url 

364 

365 # HANDLE PATH CHANGES 

366 ##################### 

367 if sm.branch_path != psm.branch_path: 

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

369 # remote branch. 

370 progress.update( 

371 BEGIN | BRANCHCHANGE, 

372 i, 

373 len_csms, 

374 prefix 

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

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

377 ) 

378 if not dry_run: 

379 smm = sm.module() 

380 smmr = smm.remotes 

381 # As the branch might not exist yet, we will have to fetch 

382 # all remotes to be sure... 

383 for remote in smmr: 

384 remote.fetch(progress=progress) 

385 # END for each remote 

386 

387 try: 

388 tbr = git.Head.create( 

389 smm, 

390 sm.branch_name, 

391 logmsg="branch: Created from HEAD", 

392 ) 

393 except OSError: 

394 # ...or reuse the existing one. 

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

396 # END ensure tracking branch exists 

397 

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

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

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

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

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

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

404 # the ref is all there is to do. 

405 smm.head.reference = tbr 

406 # END handle dry_run 

407 

408 progress.update( 

409 END | BRANCHCHANGE, 

410 i, 

411 len_csms, 

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

413 ) 

414 # END handle branch 

415 # END handle 

416 # END for each common submodule 

417 except Exception as err: 

418 if not keep_going: 

419 raise 

420 _logger.error(str(err)) 

421 # END handle keep_going 

422 

423 # FINALLY UPDATE ALL ACTUAL SUBMODULES 

424 ###################################### 

425 for sm in sms: 

426 # Update the submodule using the default method. 

427 sm.update( 

428 recursive=False, 

429 init=init, 

430 to_latest_revision=to_latest_revision, 

431 progress=progress, 

432 dry_run=dry_run, 

433 force=force_reset, 

434 keep_going=keep_going, 

435 ) 

436 

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

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

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

440 # previous expression. 

441 if recursive: 

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

443 if sm.module_exists(): 

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

445 recursive=True, 

446 force_remove=force_remove, 

447 init=init, 

448 to_latest_revision=to_latest_revision, 

449 progress=progress, 

450 dry_run=dry_run, 

451 force_reset=force_reset, 

452 keep_going=keep_going, 

453 ) 

454 # END handle dry_run 

455 # END handle recursive 

456 # END for each submodule to update 

457 

458 return self 

459 

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

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

462 return self.repo 

463 

464 # } END interface 

465 

466 

467# } END classes