|
2 | 2 | Submodule,
|
3 | 3 | UpdateProgress
|
4 | 4 | )
|
5 |
| -from .util import ( |
6 |
| - find_first_remote_branch |
7 |
| -) |
8 |
| -from git.exc import InvalidGitRepositoryError |
9 |
| -import git |
10 |
| - |
11 |
| -import logging |
12 |
| - |
13 |
| -# typing ------------------------------------------------------------------- |
14 |
| - |
15 |
| -from typing import TYPE_CHECKING, Union |
16 |
| - |
17 |
| -from git.types import Commit_ish |
18 |
| - |
19 |
| -if TYPE_CHECKING: |
20 |
| - from git.repo import Repo |
21 |
| - from git.util import IterableList |
22 |
| - |
23 |
| -# ---------------------------------------------------------------------------- |
24 |
| - |
25 |
| -__all__ = ["RootModule", "RootUpdateProgress"] |
26 |
| - |
27 |
| -log = logging.getLogger('git.objects.submodule.root') |
28 |
| -log.addHandler(logging.NullHandler()) |
29 |
| - |
30 |
| - |
31 |
| -class RootUpdateProgress(UpdateProgress): |
32 |
| - """Utility class which adds more opcodes to the UpdateProgress""" |
33 |
| - REMOVE, PATHCHANGE, BRANCHCHANGE, URLCHANGE = [ |
34 |
| - 1 << x for x in range(UpdateProgress._num_op_codes, UpdateProgress._num_op_codes + 4)] |
35 |
| - _num_op_codes = UpdateProgress._num_op_codes + 4 |
36 |
| - |
37 |
| - __slots__ = () |
38 |
| - |
39 |
| - |
40 |
| -BEGIN = RootUpdateProgress.BEGIN |
41 |
| -END = RootUpdateProgress.END |
42 |
| -REMOVE = RootUpdateProgress.REMOVE |
43 |
| -BRANCHCHANGE = RootUpdateProgress.BRANCHCHANGE |
44 |
| -URLCHANGE = RootUpdateProgress.URLCHANGE |
45 |
| -PATHCHANGE = RootUpdateProgress.PATHCHANGE |
46 |
| - |
47 |
| - |
48 |
| -class RootModule(Submodule): |
49 |
| - |
50 |
| - """A (virtual) Root of all submodules in the given repository. It can be used |
51 |
| - to more easily traverse all submodules of the master repository""" |
52 |
| - |
53 |
| - __slots__ = () |
54 |
| - |
55 |
| - k_root_name = '__ROOT__' |
56 |
| - |
57 |
| - def __init__(self, repo: 'Repo'): |
58 |
| - # repo, binsha, mode=None, path=None, name = None, parent_commit=None, url=None, ref=None) |
59 |
| - super(RootModule, self).__init__( |
60 |
| - repo, |
61 |
| - binsha=self.NULL_BIN_SHA, |
62 |
| - mode=self.k_default_mode, |
63 |
| - path='', |
64 |
| - name=self.k_root_name, |
65 |
| - parent_commit=repo.head.commit, |
66 |
| - url='', |
67 |
| - branch_path=git.Head.to_full_path(self.k_head_default) |
68 |
| - ) |
69 |
| - |
70 |
| - def _clear_cache(self) -> None: |
71 |
| - """May not do anything""" |
72 |
| - pass |
73 |
| - |
74 |
| - #{ Interface |
75 |
| - |
76 |
| - def update(self, previous_commit: Union[Commit_ish, None] = None, # type: ignore[override] |
77 |
| - recursive: bool = True, force_remove: bool = False, init: bool = True, |
78 |
| - to_latest_revision: bool = False, progress: Union[None, 'RootUpdateProgress'] = None, |
79 |
| - dry_run: bool = False, force_reset: bool = False, keep_going: bool = False |
80 |
| - ) -> 'RootModule': |
81 |
| - """Update the submodules of this repository to the current HEAD commit. |
82 |
| - This method behaves smartly by determining changes of the path of a submodules |
83 |
| - repository, next to changes to the to-be-checked-out commit or the branch to be |
84 |
| - checked out. This works if the submodules ID does not change. |
85 |
| - Additionally it will detect addition and removal of submodules, which will be handled |
86 |
| - gracefully. |
87 |
| -
|
88 |
| - :param previous_commit: If set to a commit'ish, the commit we should use |
89 |
| - as the previous commit the HEAD pointed to before it was set to the commit it points to now. |
90 |
| - If None, it defaults to HEAD@{1} otherwise |
91 |
| - :param recursive: if True, the children of submodules will be updated as well |
92 |
| - using the same technique |
93 |
| - :param force_remove: If submodules have been deleted, they will be forcibly removed. |
94 |
| - Otherwise the update may fail if a submodule's repository cannot be deleted as |
95 |
| - changes have been made to it (see Submodule.update() for more information) |
96 |
| - :param init: If we encounter a new module which would need to be initialized, then do it. |
97 |
| - :param to_latest_revision: If True, instead of checking out the revision pointed to |
98 |
| - by this submodule's sha, the checked out tracking branch will be merged with the |
99 |
| - latest remote branch fetched from the repository's origin. |
100 |
| - Unless force_reset is specified, a local tracking branch will never be reset into its past, therefore |
101 |
| - the remote branch must be in the future for this to have an effect. |
102 |
| - :param force_reset: if True, submodules may checkout or reset their branch even if the repository has |
103 |
| - pending changes that would be overwritten, or if the local tracking branch is in the future of the |
104 |
| - remote tracking branch and would be reset into its past. |
105 |
| - :param progress: RootUpdateProgress instance or None if no progress should be sent |
106 |
| - :param dry_run: if True, operations will not actually be performed. Progress messages |
107 |
| - will change accordingly to indicate the WOULD DO state of the operation. |
108 |
| - :param keep_going: if True, we will ignore but log all errors, and keep going recursively. |
109 |
| - Unless dry_run is set as well, keep_going could cause subsequent/inherited errors you wouldn't see |
110 |
| - otherwise. |
111 |
| - In conjunction with dry_run, it can be useful to anticipate all errors when updating submodules |
112 |
| - :return: self""" |
113 |
| - if self.repo.bare: |
114 |
| - raise InvalidGitRepositoryError("Cannot update submodules in bare repositories") |
115 |
| - # END handle bare |
116 |
| - |
117 |
| - if progress is None: |
118 |
| - progress = RootUpdateProgress() |
119 |
| - # END assure progress is set |
120 |
| - |
121 |
| - prefix = '' |
122 |
| - if dry_run: |
123 |
| - prefix = 'DRY-RUN: ' |
124 |
| - |
125 |
| - repo = self.repo |
126 |
| - |
127 |
| - try: |
128 |
| - # SETUP BASE COMMIT |
129 |
| - ################### |
130 |
| - cur_commit = repo.head.commit |
131 |
| - if previous_commit is None: |
132 |
| - try: |
133 |
| - previous_commit = repo.commit(repo.head.log_entry(-1).oldhexsha) |
134 |
| - if previous_commit.binsha == previous_commit.NULL_BIN_SHA: |
135 |
| - raise IndexError |
136 |
| - # END handle initial commit |
137 |
| - except IndexError: |
138 |
| - # in new repositories, there is no previous commit |
139 |
| - previous_commit = cur_commit |
140 |
| - # END exception handling |
141 |
| - else: |
142 |
| - previous_commit = repo.commit(previous_commit) # obtain commit object |
143 |
| - # END handle previous commit |
144 |
| - |
145 |
| - psms: 'IterableList[Submodule]' = self.list_items(repo, parent_commit=previous_commit) |
146 |
| - sms: 'IterableList[Submodule]' = self.list_items(repo) |
147 |
| - spsms = set(psms) |
148 |
| - ssms = set(sms) |
149 |
| - |
150 |
| - # HANDLE REMOVALS |
151 |
| - ################### |
152 |
| - rrsm = (spsms - ssms) |
153 |
| - len_rrsm = len(rrsm) |
154 |
| - |
155 |
| - for i, rsm in enumerate(rrsm): |
156 |
| - op = REMOVE |
157 |
| - if i == 0: |
158 |
| - op |= BEGIN |
159 |
| - # END handle begin |
160 |
| - |
161 |
| - # fake it into thinking its at the current commit to allow deletion |
162 |
| - # of previous module. Trigger the cache to be updated before that |
163 |
| - progress.update(op, i, len_rrsm, prefix + "Removing submodule %r at %s" % (rsm.name, rsm.abspath)) |
164 |
| - rsm._parent_commit = repo.head.commit |
165 |
| - rsm.remove(configuration=False, module=True, force=force_remove, dry_run=dry_run) |
166 |
| - |
167 |
| - if i == len_rrsm - 1: |
168 |
| - op |= END |
169 |
| - # END handle end |
170 |
| - progress.update(op, i, len_rrsm, prefix + "Done removing submodule %r" % rsm.name) |
171 |
| - # END for each removed submodule |
172 |
| - |
173 |
| - # HANDLE PATH RENAMES |
174 |
| - ##################### |
175 |
| - # url changes + branch changes |
176 |
| - csms = (spsms & ssms) |
177 |
| - len_csms = len(csms) |
178 |
| - for i, csm in enumerate(csms): |
179 |
| - psm: 'Submodule' = psms[csm.name] |
180 |
| - sm: 'Submodule' = sms[csm.name] |
181 |
| - |
182 |
| - # PATH CHANGES |
183 |
| - ############## |
184 |
| - if sm.path != psm.path and psm.module_exists(): |
185 |
| - progress.update(BEGIN | PATHCHANGE, i, len_csms, prefix + |
186 |
| - "Moving repository of submodule %r from %s to %s" |
187 |
| - % (sm.name, psm.abspath, sm.abspath)) |
188 |
| - # move the module to the new path |
189 |
| - if not dry_run: |
190 |
| - psm.move(sm.path, module=True, configuration=False) |
191 |
| - # END handle dry_run |
192 |
| - progress.update( |
193 |
| - END | PATHCHANGE, i, len_csms, prefix + "Done moving repository of submodule %r" % sm.name) |
194 |
| - # END handle path changes |
195 |
| - |
196 |
| - if sm.module_exists(): |
197 |
| - # HANDLE URL CHANGE |
198 |
| - ################### |
199 |
| - if sm.url != psm.url: |
200 |
| - # Add the new remote, remove the old one |
201 |
| - # This way, if the url just changes, the commits will not |
202 |
| - # have to be re-retrieved |
203 |
| - nn = '__new_origin__' |
204 |
| - smm = sm.module() |
205 |
| - rmts = smm.remotes |
206 |
| - |
207 |
| - # don't do anything if we already have the url we search in place |
208 |
| - if len([r for r in rmts if r.url == sm.url]) == 0: |
209 |
| - progress.update(BEGIN | URLCHANGE, i, len_csms, prefix + |
210 |
| - "Changing url of submodule %r from %s to %s" % (sm.name, psm.url, sm.url)) |
211 |
| - |
212 |
| - if not dry_run: |
213 |
| - assert nn not in [r.name for r in rmts] |
214 |
| - smr = smm.create_remote(nn, sm.url) |
215 |
| - smr.fetch(progress=progress) |
216 |
| - |
217 |
| - # If we have a tracking branch, it should be available |
218 |
| - # in the new remote as well. |
219 |
| - if len([r for r in smr.refs if r.remote_head == sm.branch_name]) == 0: |
220 |
| - raise ValueError( |
221 |
| - "Submodule branch named %r was not available in new submodule remote at %r" |
222 |
| - % (sm.branch_name, sm.url) |
223 |
| - ) |
224 |
| - # END head is not detached |
225 |
| - |
226 |
| - # now delete the changed one |
227 |
| - rmt_for_deletion = None |
228 |
| - for remote in rmts: |
229 |
| - if remote.url == psm.url: |
230 |
| - rmt_for_deletion = remote |
231 |
| - break |
232 |
| - # END if urls match |
233 |
| - # END for each remote |
234 |
| - |
235 |
| - # if we didn't find a matching remote, but have exactly one, |
236 |
| - # we can safely use this one |
237 |
| - if rmt_for_deletion is None: |
238 |
| - if len(rmts) == 1: |
239 |
| - rmt_for_deletion = rmts[0] |
240 |
| - else: |
241 |
| - # if we have not found any remote with the original url |
242 |
| - # we may not have a name. This is a special case, |
243 |
| - # and its okay to fail here |
244 |
| - # Alternatively we could just generate a unique name and leave all |
245 |
| - # existing ones in place |
246 |
| - raise InvalidGitRepositoryError( |
247 |
| - "Couldn't find original remote-repo at url %r" % psm.url) |
248 |
| - # END handle one single remote |
249 |
| - # END handle check we found a remote |
250 |
| - |
251 |
| - orig_name = rmt_for_deletion.name |
252 |
| - smm.delete_remote(rmt_for_deletion) |
253 |
| - # NOTE: Currently we leave tags from the deleted remotes |
254 |
| - # as well as separate tracking branches in the possibly totally |
255 |
| - # changed repository ( someone could have changed the url to |
256 |
| - # another project ). At some point, one might want to clean |
257 |
| - # it up, but the danger is high to remove stuff the user |
258 |
| - # has added explicitly |
259 |
| - |
260 |
| - # rename the new remote back to what it was |
261 |
| - smr.rename(orig_name) |
262 |
| - |
263 |
| - # early on, we verified that the our current tracking branch |
264 |
| - # exists in the remote. Now we have to assure that the |
265 |
| - # sha we point to is still contained in the new remote |
266 |
| - # tracking branch. |
267 |
| - smsha = sm.binsha |
268 |
| - found = False |
269 |
| - rref = smr.refs[self.branch_name] |
270 |
| - for c in rref.commit.traverse(): |
271 |
| - if c.binsha == smsha: |
272 |
| - found = True |
273 |
| - break |
274 |
| - # END traverse all commits in search for sha |
275 |
| - # END for each commit |
276 |
| - |
277 |
| - if not found: |
278 |
| - # adjust our internal binsha to use the one of the remote |
279 |
| - # this way, it will be checked out in the next step |
280 |
| - # This will change the submodule relative to us, so |
281 |
| - # the user will be able to commit the change easily |
282 |
| - log.warning("Current sha %s was not contained in the tracking\ |
283 |
| - branch at the new remote, setting it the the remote's tracking branch", sm.hexsha) |
284 |
| - sm.binsha = rref.commit.binsha |
285 |
| - # END reset binsha |
286 |
| - |
287 |
| - # NOTE: All checkout is performed by the base implementation of update |
288 |
| - # END handle dry_run |
289 |
| - progress.update( |
290 |
| - END | URLCHANGE, i, len_csms, prefix + "Done adjusting url of submodule %r" % (sm.name)) |
291 |
| - # END skip remote handling if new url already exists in module |
292 |
| - # END handle url |
293 |
| - |
294 |
| - # HANDLE PATH CHANGES |
295 |
| - ##################### |
296 |
| - if sm.branch_path != psm.branch_path: |
297 |
| - # finally, create a new tracking branch which tracks the |
298 |
| - # new remote branch |
299 |
| - progress.update(BEGIN | BRANCHCHANGE, i, len_csms, prefix + |
300 |
| - "Changing branch of submodule %r from %s to %s" |
301 |
| - % (sm.name, psm.branch_path, sm.branch_path)) |
302 |
| - if not dry_run: |
303 |
| - smm = sm.module() |
304 |
| - smmr = smm.remotes |
305 |
| - # As the branch might not exist yet, we will have to fetch all remotes to be sure ... . |
306 |
| - for remote in smmr: |
307 |
| - remote.fetch(progress=progress) |
308 |
| - # end for each remote |
309 |
| - |
310 |
| - try: |
311 |
| - tbr = git.Head.create(smm, sm.branch_name, logmsg='branch: Created from HEAD') |
312 |
| - except OSError: |
313 |
| - # ... or reuse the existing one |
314 |
| - tbr = git.Head(smm, sm.branch_path) |
315 |
| - # END assure tracking branch exists |
316 |
| - |
317 |
| - tbr.set_tracking_branch(find_first_remote_branch(smmr, sm.branch_name)) |
318 |
| - # NOTE: All head-resetting is done in the base implementation of update |
319 |
| - # but we will have to checkout the new branch here. As it still points to the currently |
320 |
| - # checkout out commit, we don't do any harm. |
321 |
| - # As we don't want to update working-tree or index, changing the ref is all there is to do |
322 |
| - smm.head.reference = tbr |
323 |
| - # END handle dry_run |
324 |
| - |
325 |
| - progress.update( |
326 |
| - END | BRANCHCHANGE, i, len_csms, prefix + "Done changing branch of submodule %r" % sm.name) |
327 |
| - # END handle branch |
328 |
| - # END handle |
329 |
| - # END for each common submodule |
330 |
| - except Exception as err: |
331 |
| - if not keep_going: |
332 |
| - raise |
333 |
| - log.error(str(err)) |
334 |
| - # end handle keep_going |
335 |
| - |
336 |
| - # FINALLY UPDATE ALL ACTUAL SUBMODULES |
337 |
| - ###################################### |
338 |
| - for sm in sms: |
339 |
| - # update the submodule using the default method |
340 |
| - sm.update(recursive=False, init=init, to_latest_revision=to_latest_revision, |
341 |
| - progress=progress, dry_run=dry_run, force=force_reset, keep_going=keep_going) |
342 |
| - |
343 |
| - # update recursively depth first - question is which inconsitent |
344 |
| - # state will be better in case it fails somewhere. Defective branch |
345 |
| - # or defective depth. The RootSubmodule type will never process itself, |
346 |
| - # which was done in the previous expression |
347 |
| - if recursive: |
348 |
| - # the module would exist by now if we are not in dry_run mode |
349 |
| - if sm.module_exists(): |
350 |
| - type(self)(sm.module()).update(recursive=True, force_remove=force_remove, |
351 |
| - init=init, to_latest_revision=to_latest_revision, |
352 |
| - progress=progress, dry_run=dry_run, force_reset=force_reset, |
353 |
| - keep_going=keep_going) |
354 |
| - # END handle dry_run |
355 |
| - # END handle recursive |
356 |
| - # END for each submodule to update |
357 |
| - |
358 |
| - return self |
359 |
| - |
360 |
| -from .base import ( |
361 |
| - Submodule, |
362 |
| - UpdateProgress |
363 |
| -) |
364 |
| -from .util import ( |
365 |
| - find_first_remote_branch |
366 |
| -) |
| 5 | +from .util import find_first_remote_branch |
367 | 6 | from git.exc import InvalidGitRepositoryError
|
368 | 7 | import git
|
369 | 8 |
|
@@ -720,8 +359,4 @@ def module(self) -> 'Repo':
|
720 | 359 | """:return: the actual repository containing the submodules"""
|
721 | 360 | return self.repo
|
722 | 361 | #} END interface
|
723 |
| -#} END classes |
724 |
| - """:return: the actual repository containing the submodules""" |
725 |
| - return self.repo |
726 |
| - #} END interface |
727 | 362 | #} END classes
|
0 commit comments