Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/git/objects/submodule/root.py: 17%

137 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1from .base import Submodule, UpdateProgress 

2from .util import find_first_remote_branch 

3from git.exc import InvalidGitRepositoryError 

4import git 

5 

6import logging 

7 

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

9 

10from typing import TYPE_CHECKING, Union 

11 

12from git.types import Commit_ish 

13 

14if TYPE_CHECKING: 14 ↛ 15line 14 didn't jump to line 15, because the condition on line 14 was never true

15 from git.repo import Repo 

16 from git.util import IterableList 

17 

18# ---------------------------------------------------------------------------- 

19 

20__all__ = ["RootModule", "RootUpdateProgress"] 

21 

22log = logging.getLogger("git.objects.submodule.root") 

23log.addHandler(logging.NullHandler()) 

24 

25 

26class RootUpdateProgress(UpdateProgress): 

27 """Utility class which adds more opcodes to the UpdateProgress""" 

28 

29 REMOVE, PATHCHANGE, BRANCHCHANGE, URLCHANGE = [ 

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

31 ] 

32 _num_op_codes = UpdateProgress._num_op_codes + 4 

33 

34 __slots__ = () 

35 

36 

37BEGIN = RootUpdateProgress.BEGIN 

38END = RootUpdateProgress.END 

39REMOVE = RootUpdateProgress.REMOVE 

40BRANCHCHANGE = RootUpdateProgress.BRANCHCHANGE 

41URLCHANGE = RootUpdateProgress.URLCHANGE 

42PATHCHANGE = RootUpdateProgress.PATHCHANGE 

43 

44 

45class RootModule(Submodule): 

46 

47 """A (virtual) Root of all submodules in the given repository. It can be used 

48 to more easily traverse all submodules of the master repository""" 

49 

50 __slots__ = () 

51 

52 k_root_name = "__ROOT__" 

53 

54 def __init__(self, repo: "Repo"): 

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

56 super(RootModule, self).__init__( 

57 repo, 

58 binsha=self.NULL_BIN_SHA, 

59 mode=self.k_default_mode, 

60 path="", 

61 name=self.k_root_name, 

62 parent_commit=repo.head.commit, 

63 url="", 

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

65 ) 

66 

67 def _clear_cache(self) -> None: 

68 """May not do anything""" 

69 pass 

70 

71 # { Interface 

72 

73 def update( 

74 self, 

75 previous_commit: Union[Commit_ish, None] = None, # type: ignore[override] 

76 recursive: bool = True, 

77 force_remove: bool = False, 

78 init: bool = True, 

79 to_latest_revision: bool = False, 

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

81 dry_run: bool = False, 

82 force_reset: bool = False, 

83 keep_going: bool = False, 

84 ) -> "RootModule": 

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

86 This method behaves smartly by determining changes of the path of a submodules 

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

88 checked out. This works if the submodules ID does not change. 

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

90 gracefully. 

91 

92 :param previous_commit: If set to a commit'ish, the commit we should use 

93 as the previous commit the HEAD pointed to before it was set to the commit it points to now. 

94 If None, it defaults to HEAD@{1} otherwise 

95 :param recursive: if True, the children of submodules will be updated as well 

96 using the same technique 

97 :param force_remove: If submodules have been deleted, they will be forcibly removed. 

98 Otherwise the update may fail if a submodule's repository cannot be deleted as 

99 changes have been made to it (see Submodule.update() for more information) 

100 :param init: If we encounter a new module which would need to be initialized, then do it. 

101 :param to_latest_revision: If True, instead of checking out the revision pointed to 

102 by this submodule's sha, the checked out tracking branch will be merged with the 

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

104 Unless force_reset is specified, a local tracking branch will never be reset into its past, therefore 

105 the remote branch must be in the future for this to have an effect. 

106 :param force_reset: if True, submodules may checkout or reset their branch even if the repository has 

107 pending changes that would be overwritten, or if the local tracking branch is in the future of the 

108 remote tracking branch and would be reset into its past. 

109 :param progress: RootUpdateProgress instance or None if no progress should be sent 

110 :param dry_run: if True, operations will not actually be performed. Progress messages 

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

112 :param keep_going: if True, we will ignore but log all errors, and keep going recursively. 

113 Unless dry_run is set as well, keep_going could cause subsequent/inherited errors you wouldn't see 

114 otherwise. 

115 In conjunction with dry_run, it can be useful to anticipate all errors when updating submodules 

116 :return: self""" 

117 if self.repo.bare: 

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

119 # END handle bare 

120 

121 if progress is None: 

122 progress = RootUpdateProgress() 

123 # END assure progress is set 

124 

125 prefix = "" 

126 if dry_run: 

127 prefix = "DRY-RUN: " 

128 

129 repo = self.repo 

130 

131 try: 

132 # SETUP BASE COMMIT 

133 ################### 

134 cur_commit = repo.head.commit 

135 if previous_commit is None: 

136 try: 

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

138 if previous_commit.binsha == previous_commit.NULL_BIN_SHA: 

139 raise IndexError 

140 # END handle initial commit 

141 except IndexError: 

142 # in new repositories, there is no previous commit 

143 previous_commit = cur_commit 

144 # END exception handling 

145 else: 

146 previous_commit = repo.commit(previous_commit) # obtain commit object 

147 # END handle previous commit 

148 

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

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

151 spsms = set(psms) 

152 ssms = set(sms) 

153 

154 # HANDLE REMOVALS 

155 ################### 

156 rrsm = spsms - ssms 

157 len_rrsm = len(rrsm) 

158 

159 for i, rsm in enumerate(rrsm): 

160 op = REMOVE 

161 if i == 0: 

162 op |= BEGIN 

163 # END handle begin 

164 

165 # fake it into thinking its at the current commit to allow deletion 

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

167 progress.update( 

168 op, 

169 i, 

170 len_rrsm, 

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

172 ) 

173 rsm._parent_commit = repo.head.commit 

174 rsm.remove( 

175 configuration=False, 

176 module=True, 

177 force=force_remove, 

178 dry_run=dry_run, 

179 ) 

180 

181 if i == len_rrsm - 1: 

182 op |= END 

183 # END handle end 

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

185 # END for each removed submodule 

186 

187 # HANDLE PATH RENAMES 

188 ##################### 

189 # url changes + branch changes 

190 csms = spsms & ssms 

191 len_csms = len(csms) 

192 for i, csm in enumerate(csms): 

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

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

195 

196 # PATH CHANGES 

197 ############## 

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

199 progress.update( 

200 BEGIN | PATHCHANGE, 

201 i, 

202 len_csms, 

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

204 ) 

205 # move the module to the new path 

206 if not dry_run: 

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

208 # END handle dry_run 

209 progress.update( 

210 END | PATHCHANGE, 

211 i, 

212 len_csms, 

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

214 ) 

215 # END handle path changes 

216 

217 if sm.module_exists(): 

218 # HANDLE URL CHANGE 

219 ################### 

220 if sm.url != psm.url: 

221 # Add the new remote, remove the old one 

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

223 # have to be re-retrieved 

224 nn = "__new_origin__" 

225 smm = sm.module() 

226 rmts = smm.remotes 

227 

228 # don't do anything if we already have the url we search in place 

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

230 progress.update( 

231 BEGIN | URLCHANGE, 

232 i, 

233 len_csms, 

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

235 ) 

236 

237 if not dry_run: 

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

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

240 smr.fetch(progress=progress) 

241 

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

243 # in the new remote as well. 

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

245 raise ValueError( 

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

247 % (sm.branch_name, sm.url) 

248 ) 

249 # END head is not detached 

250 

251 # now delete the changed one 

252 rmt_for_deletion = None 

253 for remote in rmts: 

254 if remote.url == psm.url: 

255 rmt_for_deletion = remote 

256 break 

257 # END if urls match 

258 # END for each remote 

259 

260 # if we didn't find a matching remote, but have exactly one, 

261 # we can safely use this one 

262 if rmt_for_deletion is None: 

263 if len(rmts) == 1: 

264 rmt_for_deletion = rmts[0] 

265 else: 

266 # if we have not found any remote with the original url 

267 # we may not have a name. This is a special case, 

268 # and its okay to fail here 

269 # Alternatively we could just generate a unique name and leave all 

270 # existing ones in place 

271 raise InvalidGitRepositoryError( 

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

273 ) 

274 # END handle one single remote 

275 # END handle check we found a remote 

276 

277 orig_name = rmt_for_deletion.name 

278 smm.delete_remote(rmt_for_deletion) 

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

280 # as well as separate tracking branches in the possibly totally 

281 # changed repository ( someone could have changed the url to 

282 # another project ). At some point, one might want to clean 

283 # it up, but the danger is high to remove stuff the user 

284 # has added explicitly 

285 

286 # rename the new remote back to what it was 

287 smr.rename(orig_name) 

288 

289 # early on, we verified that the our current tracking branch 

290 # exists in the remote. Now we have to assure that the 

291 # sha we point to is still contained in the new remote 

292 # tracking branch. 

293 smsha = sm.binsha 

294 found = False 

295 rref = smr.refs[self.branch_name] 

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

297 if c.binsha == smsha: 

298 found = True 

299 break 

300 # END traverse all commits in search for sha 

301 # END for each commit 

302 

303 if not found: 

304 # adjust our internal binsha to use the one of the remote 

305 # this way, it will be checked out in the next step 

306 # This will change the submodule relative to us, so 

307 # the user will be able to commit the change easily 

308 log.warning( 

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

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

311 sm.hexsha, 

312 ) 

313 sm.binsha = rref.commit.binsha 

314 # END reset binsha 

315 

316 # NOTE: All checkout is performed by the base implementation of update 

317 # END handle dry_run 

318 progress.update( 

319 END | URLCHANGE, 

320 i, 

321 len_csms, 

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

323 ) 

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

325 # END handle url 

326 

327 # HANDLE PATH CHANGES 

328 ##################### 

329 if sm.branch_path != psm.branch_path: 

330 # finally, create a new tracking branch which tracks the 

331 # new remote branch 

332 progress.update( 

333 BEGIN | BRANCHCHANGE, 

334 i, 

335 len_csms, 

336 prefix 

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

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

339 ) 

340 if not dry_run: 

341 smm = sm.module() 

342 smmr = smm.remotes 

343 # As the branch might not exist yet, we will have to fetch all remotes to be sure ... . 

344 for remote in smmr: 

345 remote.fetch(progress=progress) 

346 # end for each remote 

347 

348 try: 

349 tbr = git.Head.create( 

350 smm, 

351 sm.branch_name, 

352 logmsg="branch: Created from HEAD", 

353 ) 

354 except OSError: 

355 # ... or reuse the existing one 

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

357 # END assure tracking branch exists 

358 

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

360 # NOTE: All head-resetting is done in the base implementation of update 

361 # but we will have to checkout the new branch here. As it still points to the currently 

362 # checkout out commit, we don't do any harm. 

363 # As we don't want to update working-tree or index, changing the ref is all there is to do 

364 smm.head.reference = tbr 

365 # END handle dry_run 

366 

367 progress.update( 

368 END | BRANCHCHANGE, 

369 i, 

370 len_csms, 

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

372 ) 

373 # END handle branch 

374 # END handle 

375 # END for each common submodule 

376 except Exception as err: 

377 if not keep_going: 

378 raise 

379 log.error(str(err)) 

380 # end handle keep_going 

381 

382 # FINALLY UPDATE ALL ACTUAL SUBMODULES 

383 ###################################### 

384 for sm in sms: 

385 # update the submodule using the default method 

386 sm.update( 

387 recursive=False, 

388 init=init, 

389 to_latest_revision=to_latest_revision, 

390 progress=progress, 

391 dry_run=dry_run, 

392 force=force_reset, 

393 keep_going=keep_going, 

394 ) 

395 

396 # update recursively depth first - question is which inconsistent 

397 # state will be better in case it fails somewhere. Defective branch 

398 # or defective depth. The RootSubmodule type will never process itself, 

399 # which was done in the previous expression 

400 if recursive: 

401 # the module would exist by now if we are not in dry_run mode 

402 if sm.module_exists(): 

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

404 recursive=True, 

405 force_remove=force_remove, 

406 init=init, 

407 to_latest_revision=to_latest_revision, 

408 progress=progress, 

409 dry_run=dry_run, 

410 force_reset=force_reset, 

411 keep_going=keep_going, 

412 ) 

413 # END handle dry_run 

414 # END handle recursive 

415 # END for each submodule to update 

416 

417 return self 

418 

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

420 """:return: the actual repository containing the submodules""" 

421 return self.repo 

422 

423 # } END interface 

424 

425 

426# } END classes