| |
| |
|
|
| __all__ = ["RootModule", "RootUpdateProgress"] |
|
|
| import logging |
|
|
| import git |
| from git.exc import InvalidGitRepositoryError |
|
|
| from .base import Submodule, UpdateProgress |
| from .util import find_first_remote_branch |
|
|
| |
|
|
| from typing import TYPE_CHECKING, Union |
|
|
| from git.types import Commit_ish |
|
|
| if TYPE_CHECKING: |
| from git.repo import Repo |
| from git.util import IterableList |
|
|
| |
|
|
| _logger = logging.getLogger(__name__) |
|
|
|
|
| class RootUpdateProgress(UpdateProgress): |
| """Utility class which adds more opcodes to |
| :class:`~git.objects.submodule.base.UpdateProgress`.""" |
|
|
| REMOVE, PATHCHANGE, BRANCHCHANGE, URLCHANGE = [ |
| 1 << x for x in range(UpdateProgress._num_op_codes, UpdateProgress._num_op_codes + 4) |
| ] |
| _num_op_codes = UpdateProgress._num_op_codes + 4 |
|
|
| __slots__ = () |
|
|
|
|
| BEGIN = RootUpdateProgress.BEGIN |
| END = RootUpdateProgress.END |
| REMOVE = RootUpdateProgress.REMOVE |
| BRANCHCHANGE = RootUpdateProgress.BRANCHCHANGE |
| URLCHANGE = RootUpdateProgress.URLCHANGE |
| PATHCHANGE = RootUpdateProgress.PATHCHANGE |
|
|
|
|
| class RootModule(Submodule): |
| """A (virtual) root of all submodules in the given repository. |
| |
| This can be used to more easily traverse all submodules of the |
| superproject (master repository). |
| """ |
|
|
| __slots__ = () |
|
|
| k_root_name = "__ROOT__" |
|
|
| def __init__(self, repo: "Repo") -> None: |
| |
| super().__init__( |
| repo, |
| binsha=self.NULL_BIN_SHA, |
| mode=self.k_default_mode, |
| path="", |
| name=self.k_root_name, |
| parent_commit=repo.head.commit, |
| url="", |
| branch_path=git.Head.to_full_path(self.k_head_default), |
| ) |
|
|
| def _clear_cache(self) -> None: |
| """May not do anything.""" |
| pass |
|
|
| |
|
|
| def update( |
| self, |
| previous_commit: Union[Commit_ish, str, None] = None, |
| recursive: bool = True, |
| force_remove: bool = False, |
| init: bool = True, |
| to_latest_revision: bool = False, |
| progress: Union[None, "RootUpdateProgress"] = None, |
| dry_run: bool = False, |
| force_reset: bool = False, |
| keep_going: bool = False, |
| ) -> "RootModule": |
| """Update the submodules of this repository to the current HEAD commit. |
| |
| This method behaves smartly by determining changes of the path of a submodule's |
| repository, next to changes to the to-be-checked-out commit or the branch to be |
| checked out. This works if the submodule's ID does not change. |
| |
| Additionally it will detect addition and removal of submodules, which will be |
| handled gracefully. |
| |
| :param previous_commit: |
| If set to a commit-ish, the commit we should use as the previous commit the |
| HEAD pointed to before it was set to the commit it points to now. |
| If ``None``, it defaults to ``HEAD@{1}`` otherwise. |
| |
| :param recursive: |
| If ``True``, the children of submodules will be updated as well using the |
| same technique. |
| |
| :param force_remove: |
| If submodules have been deleted, they will be forcibly removed. Otherwise |
| the update may fail if a submodule's repository cannot be deleted as changes |
| have been made to it. |
| (See :meth:`Submodule.update <git.objects.submodule.base.Submodule.update>` |
| for more information.) |
| |
| :param init: |
| If we encounter a new module which would need to be initialized, then do it. |
| |
| :param to_latest_revision: |
| If ``True``, instead of checking out the revision pointed to by this |
| submodule's sha, the checked out tracking branch will be merged with the |
| latest remote branch fetched from the repository's origin. |
| |
| Unless `force_reset` is specified, a local tracking branch will never be |
| reset into its past, therefore the remote branch must be in the future for |
| this to have an effect. |
| |
| :param force_reset: |
| If ``True``, submodules may checkout or reset their branch even if the |
| repository has pending changes that would be overwritten, or if the local |
| tracking branch is in the future of the remote tracking branch and would be |
| reset into its past. |
| |
| :param progress: |
| :class:`RootUpdateProgress` instance, or ``None`` if no progress should be |
| sent. |
| |
| :param dry_run: |
| If ``True``, operations will not actually be performed. Progress messages |
| will change accordingly to indicate the WOULD DO state of the operation. |
| |
| :param keep_going: |
| If ``True``, we will ignore but log all errors, and keep going recursively. |
| Unless `dry_run` is set as well, `keep_going` could cause |
| subsequent/inherited errors you wouldn't see otherwise. |
| In conjunction with `dry_run`, this can be useful to anticipate all errors |
| when updating submodules. |
| |
| :return: |
| self |
| """ |
| if self.repo.bare: |
| raise InvalidGitRepositoryError("Cannot update submodules in bare repositories") |
| |
|
|
| if progress is None: |
| progress = RootUpdateProgress() |
| |
|
|
| prefix = "" |
| if dry_run: |
| prefix = "DRY-RUN: " |
|
|
| repo = self.repo |
|
|
| try: |
| |
| |
| cur_commit = repo.head.commit |
| if previous_commit is None: |
| try: |
| previous_commit = repo.commit(repo.head.log_entry(-1).oldhexsha) |
| if previous_commit.binsha == previous_commit.NULL_BIN_SHA: |
| raise IndexError |
| |
| except IndexError: |
| |
| previous_commit = cur_commit |
| |
| else: |
| previous_commit = repo.commit(previous_commit) |
| |
|
|
| psms: "IterableList[Submodule]" = self.list_items(repo, parent_commit=previous_commit) |
| sms: "IterableList[Submodule]" = self.list_items(repo) |
| spsms = set(psms) |
| ssms = set(sms) |
|
|
| |
| |
| rrsm = spsms - ssms |
| len_rrsm = len(rrsm) |
|
|
| for i, rsm in enumerate(rrsm): |
| op = REMOVE |
| if i == 0: |
| op |= BEGIN |
| |
|
|
| |
| |
| progress.update( |
| op, |
| i, |
| len_rrsm, |
| prefix + "Removing submodule %r at %s" % (rsm.name, rsm.abspath), |
| ) |
| rsm._parent_commit = repo.head.commit |
| rsm.remove( |
| configuration=False, |
| module=True, |
| force=force_remove, |
| dry_run=dry_run, |
| ) |
|
|
| if i == len_rrsm - 1: |
| op |= END |
| |
| progress.update(op, i, len_rrsm, prefix + "Done removing submodule %r" % rsm.name) |
| |
|
|
| |
| |
| |
| csms = spsms & ssms |
| len_csms = len(csms) |
| for i, csm in enumerate(csms): |
| psm: "Submodule" = psms[csm.name] |
| sm: "Submodule" = sms[csm.name] |
|
|
| |
| |
| if sm.path != psm.path and psm.module_exists(): |
| progress.update( |
| BEGIN | PATHCHANGE, |
| i, |
| len_csms, |
| prefix + "Moving repository of submodule %r from %s to %s" % (sm.name, psm.abspath, sm.abspath), |
| ) |
| |
| if not dry_run: |
| psm.move(sm.path, module=True, configuration=False) |
| |
| progress.update( |
| END | PATHCHANGE, |
| i, |
| len_csms, |
| prefix + "Done moving repository of submodule %r" % sm.name, |
| ) |
| |
|
|
| if sm.module_exists(): |
| |
| |
| if sm.url != psm.url: |
| |
| |
| |
| nn = "__new_origin__" |
| smm = sm.module() |
| rmts = smm.remotes |
|
|
| |
| |
| if len([r for r in rmts if r.url == sm.url]) == 0: |
| progress.update( |
| BEGIN | URLCHANGE, |
| i, |
| len_csms, |
| prefix + "Changing url of submodule %r from %s to %s" % (sm.name, psm.url, sm.url), |
| ) |
|
|
| if not dry_run: |
| assert nn not in [r.name for r in rmts] |
| smr = smm.create_remote(nn, sm.url) |
| smr.fetch(progress=progress) |
|
|
| |
| |
| if len([r for r in smr.refs if r.remote_head == sm.branch_name]) == 0: |
| raise ValueError( |
| "Submodule branch named %r was not available in new submodule remote at %r" |
| % (sm.branch_name, sm.url) |
| ) |
| |
|
|
| |
| rmt_for_deletion = None |
| for remote in rmts: |
| if remote.url == psm.url: |
| rmt_for_deletion = remote |
| break |
| |
| |
|
|
| |
| |
| if rmt_for_deletion is None: |
| if len(rmts) == 1: |
| rmt_for_deletion = rmts[0] |
| else: |
| |
| |
| |
| |
| |
| raise InvalidGitRepositoryError( |
| "Couldn't find original remote-repo at url %r" % psm.url |
| ) |
| |
| |
|
|
| orig_name = rmt_for_deletion.name |
| smm.delete_remote(rmt_for_deletion) |
| |
| |
| |
| |
| |
| |
|
|
| |
| smr.rename(orig_name) |
|
|
| |
| |
| |
| |
| smsha = sm.binsha |
| found = False |
| rref = smr.refs[self.branch_name] |
| for c in rref.commit.traverse(): |
| if c.binsha == smsha: |
| found = True |
| break |
| |
| |
|
|
| if not found: |
| |
| |
| |
| |
| |
| _logger.warning( |
| "Current sha %s was not contained in the tracking\ |
| branch at the new remote, setting it the the remote's tracking branch", |
| sm.hexsha, |
| ) |
| sm.binsha = rref.commit.binsha |
| |
|
|
| |
| |
| |
| progress.update( |
| END | URLCHANGE, |
| i, |
| len_csms, |
| prefix + "Done adjusting url of submodule %r" % (sm.name), |
| ) |
| |
| |
|
|
| |
| |
| if sm.branch_path != psm.branch_path: |
| |
| |
| progress.update( |
| BEGIN | BRANCHCHANGE, |
| i, |
| len_csms, |
| prefix |
| + "Changing branch of submodule %r from %s to %s" |
| % (sm.name, psm.branch_path, sm.branch_path), |
| ) |
| if not dry_run: |
| smm = sm.module() |
| smmr = smm.remotes |
| |
| |
| for remote in smmr: |
| remote.fetch(progress=progress) |
| |
|
|
| try: |
| tbr = git.Head.create( |
| smm, |
| sm.branch_name, |
| logmsg="branch: Created from HEAD", |
| ) |
| except OSError: |
| |
| tbr = git.Head(smm, sm.branch_path) |
| |
|
|
| tbr.set_tracking_branch(find_first_remote_branch(smmr, sm.branch_name)) |
| |
| |
| |
| |
| |
| |
| smm.head.reference = tbr |
| |
|
|
| progress.update( |
| END | BRANCHCHANGE, |
| i, |
| len_csms, |
| prefix + "Done changing branch of submodule %r" % sm.name, |
| ) |
| |
| |
| |
| except Exception as err: |
| if not keep_going: |
| raise |
| _logger.error(str(err)) |
| |
|
|
| |
| |
| for sm in sms: |
| |
| sm.update( |
| recursive=False, |
| init=init, |
| to_latest_revision=to_latest_revision, |
| progress=progress, |
| dry_run=dry_run, |
| force=force_reset, |
| keep_going=keep_going, |
| ) |
|
|
| |
| |
| |
| |
| if recursive: |
| |
| if sm.module_exists(): |
| type(self)(sm.module()).update( |
| recursive=True, |
| force_remove=force_remove, |
| init=init, |
| to_latest_revision=to_latest_revision, |
| progress=progress, |
| dry_run=dry_run, |
| force_reset=force_reset, |
| keep_going=keep_going, |
| ) |
| |
| |
| |
|
|
| return self |
|
|
| def module(self) -> "Repo": |
| """:return: The actual repository containing the submodules""" |
| return self.repo |
|
|
| |
|
|
|
|
| |
|
|