upload
This commit is contained in:
		@@ -0,0 +1,2 @@
 | 
			
		||||
# NOTE: Cannot import anything here as the top-level _init_ has to handle
 | 
			
		||||
# our dependencies
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -0,0 +1,426 @@
 | 
			
		||||
from .base import Submodule, UpdateProgress
 | 
			
		||||
from .util import find_first_remote_branch
 | 
			
		||||
from git.exc import InvalidGitRepositoryError
 | 
			
		||||
import git
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
# typing -------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
# ----------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
__all__ = ["RootModule", "RootUpdateProgress"]
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger("git.objects.submodule.root")
 | 
			
		||||
log.addHandler(logging.NullHandler())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RootUpdateProgress(UpdateProgress):
 | 
			
		||||
    """Utility class which adds more opcodes to the 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. It can be used
 | 
			
		||||
    to more easily traverse all submodules of the master repository"""
 | 
			
		||||
 | 
			
		||||
    __slots__ = ()
 | 
			
		||||
 | 
			
		||||
    k_root_name = "__ROOT__"
 | 
			
		||||
 | 
			
		||||
    def __init__(self, repo: "Repo"):
 | 
			
		||||
        # repo, binsha, mode=None, path=None, name = None, parent_commit=None, url=None, ref=None)
 | 
			
		||||
        super(RootModule, self).__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
 | 
			
		||||
 | 
			
		||||
    # { Interface
 | 
			
		||||
 | 
			
		||||
    def update(
 | 
			
		||||
        self,
 | 
			
		||||
        previous_commit: Union[Commit_ish, None] = None,  # type: ignore[override]
 | 
			
		||||
        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 submodules
 | 
			
		||||
        repository, next to changes to the to-be-checked-out commit or the branch to be
 | 
			
		||||
        checked out. This works if the submodules 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 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: 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, it can be useful to anticipate all errors when updating submodules
 | 
			
		||||
        :return: self"""
 | 
			
		||||
        if self.repo.bare:
 | 
			
		||||
            raise InvalidGitRepositoryError("Cannot update submodules in bare repositories")
 | 
			
		||||
        # END handle bare
 | 
			
		||||
 | 
			
		||||
        if progress is None:
 | 
			
		||||
            progress = RootUpdateProgress()
 | 
			
		||||
        # END assure progress is set
 | 
			
		||||
 | 
			
		||||
        prefix = ""
 | 
			
		||||
        if dry_run:
 | 
			
		||||
            prefix = "DRY-RUN: "
 | 
			
		||||
 | 
			
		||||
        repo = self.repo
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            # SETUP BASE COMMIT
 | 
			
		||||
            ###################
 | 
			
		||||
            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
 | 
			
		||||
                    # END handle initial commit
 | 
			
		||||
                except IndexError:
 | 
			
		||||
                    # in new repositories, there is no previous commit
 | 
			
		||||
                    previous_commit = cur_commit
 | 
			
		||||
                # END exception handling
 | 
			
		||||
            else:
 | 
			
		||||
                previous_commit = repo.commit(previous_commit)  # obtain commit object
 | 
			
		||||
            # END handle 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)
 | 
			
		||||
 | 
			
		||||
            # HANDLE REMOVALS
 | 
			
		||||
            ###################
 | 
			
		||||
            rrsm = spsms - ssms
 | 
			
		||||
            len_rrsm = len(rrsm)
 | 
			
		||||
 | 
			
		||||
            for i, rsm in enumerate(rrsm):
 | 
			
		||||
                op = REMOVE
 | 
			
		||||
                if i == 0:
 | 
			
		||||
                    op |= BEGIN
 | 
			
		||||
                # END handle begin
 | 
			
		||||
 | 
			
		||||
                # fake it into thinking its at the current commit to allow deletion
 | 
			
		||||
                # of previous module. Trigger the cache to be updated before that
 | 
			
		||||
                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
 | 
			
		||||
                # END handle end
 | 
			
		||||
                progress.update(op, i, len_rrsm, prefix + "Done removing submodule %r" % rsm.name)
 | 
			
		||||
            # END for each removed submodule
 | 
			
		||||
 | 
			
		||||
            # HANDLE PATH RENAMES
 | 
			
		||||
            #####################
 | 
			
		||||
            # url changes + branch changes
 | 
			
		||||
            csms = spsms & ssms
 | 
			
		||||
            len_csms = len(csms)
 | 
			
		||||
            for i, csm in enumerate(csms):
 | 
			
		||||
                psm: "Submodule" = psms[csm.name]
 | 
			
		||||
                sm: "Submodule" = sms[csm.name]
 | 
			
		||||
 | 
			
		||||
                # PATH CHANGES
 | 
			
		||||
                ##############
 | 
			
		||||
                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),
 | 
			
		||||
                    )
 | 
			
		||||
                    # move the module to the new path
 | 
			
		||||
                    if not dry_run:
 | 
			
		||||
                        psm.move(sm.path, module=True, configuration=False)
 | 
			
		||||
                    # END handle dry_run
 | 
			
		||||
                    progress.update(
 | 
			
		||||
                        END | PATHCHANGE,
 | 
			
		||||
                        i,
 | 
			
		||||
                        len_csms,
 | 
			
		||||
                        prefix + "Done moving repository of submodule %r" % sm.name,
 | 
			
		||||
                    )
 | 
			
		||||
                # END handle path changes
 | 
			
		||||
 | 
			
		||||
                if sm.module_exists():
 | 
			
		||||
                    # HANDLE URL CHANGE
 | 
			
		||||
                    ###################
 | 
			
		||||
                    if sm.url != psm.url:
 | 
			
		||||
                        # Add the new remote, remove the old one
 | 
			
		||||
                        # This way, if the url just changes, the commits will not
 | 
			
		||||
                        # have to be re-retrieved
 | 
			
		||||
                        nn = "__new_origin__"
 | 
			
		||||
                        smm = sm.module()
 | 
			
		||||
                        rmts = smm.remotes
 | 
			
		||||
 | 
			
		||||
                        # don't do anything if we already have the url we search in place
 | 
			
		||||
                        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 we have a tracking branch, it should be available
 | 
			
		||||
                                # in the new remote as well.
 | 
			
		||||
                                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)
 | 
			
		||||
                                    )
 | 
			
		||||
                                # END head is not detached
 | 
			
		||||
 | 
			
		||||
                                # now delete the changed one
 | 
			
		||||
                                rmt_for_deletion = None
 | 
			
		||||
                                for remote in rmts:
 | 
			
		||||
                                    if remote.url == psm.url:
 | 
			
		||||
                                        rmt_for_deletion = remote
 | 
			
		||||
                                        break
 | 
			
		||||
                                    # END if urls match
 | 
			
		||||
                                # END for each remote
 | 
			
		||||
 | 
			
		||||
                                # if we didn't find a matching remote, but have exactly one,
 | 
			
		||||
                                # we can safely use this one
 | 
			
		||||
                                if rmt_for_deletion is None:
 | 
			
		||||
                                    if len(rmts) == 1:
 | 
			
		||||
                                        rmt_for_deletion = rmts[0]
 | 
			
		||||
                                    else:
 | 
			
		||||
                                        # if we have not found any remote with the original url
 | 
			
		||||
                                        # we may not have a name. This is a special case,
 | 
			
		||||
                                        # and its okay to fail here
 | 
			
		||||
                                        # Alternatively we could just generate a unique name and leave all
 | 
			
		||||
                                        # existing ones in place
 | 
			
		||||
                                        raise InvalidGitRepositoryError(
 | 
			
		||||
                                            "Couldn't find original remote-repo at url %r" % psm.url
 | 
			
		||||
                                        )
 | 
			
		||||
                                    # END handle one single remote
 | 
			
		||||
                                # END handle check we found a remote
 | 
			
		||||
 | 
			
		||||
                                orig_name = rmt_for_deletion.name
 | 
			
		||||
                                smm.delete_remote(rmt_for_deletion)
 | 
			
		||||
                                # NOTE: Currently we leave tags from the deleted remotes
 | 
			
		||||
                                # as well as separate tracking branches in the possibly totally
 | 
			
		||||
                                # changed repository ( someone could have changed the url to
 | 
			
		||||
                                # another project ). At some point, one might want to clean
 | 
			
		||||
                                # it up, but the danger is high to remove stuff the user
 | 
			
		||||
                                # has added explicitly
 | 
			
		||||
 | 
			
		||||
                                # rename the new remote back to what it was
 | 
			
		||||
                                smr.rename(orig_name)
 | 
			
		||||
 | 
			
		||||
                                # early on, we verified that the our current tracking branch
 | 
			
		||||
                                # exists in the remote. Now we have to assure that the
 | 
			
		||||
                                # sha we point to is still contained in the new remote
 | 
			
		||||
                                # tracking branch.
 | 
			
		||||
                                smsha = sm.binsha
 | 
			
		||||
                                found = False
 | 
			
		||||
                                rref = smr.refs[self.branch_name]
 | 
			
		||||
                                for c in rref.commit.traverse():
 | 
			
		||||
                                    if c.binsha == smsha:
 | 
			
		||||
                                        found = True
 | 
			
		||||
                                        break
 | 
			
		||||
                                    # END traverse all commits in search for sha
 | 
			
		||||
                                # END for each commit
 | 
			
		||||
 | 
			
		||||
                                if not found:
 | 
			
		||||
                                    # adjust our internal binsha to use the one of the remote
 | 
			
		||||
                                    # this way, it will be checked out in the next step
 | 
			
		||||
                                    # This will change the submodule relative to us, so
 | 
			
		||||
                                    # the user will be able to commit the change easily
 | 
			
		||||
                                    log.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
 | 
			
		||||
                                # END reset binsha
 | 
			
		||||
 | 
			
		||||
                                # NOTE: All checkout is performed by the base implementation of update
 | 
			
		||||
                            # END handle dry_run
 | 
			
		||||
                            progress.update(
 | 
			
		||||
                                END | URLCHANGE,
 | 
			
		||||
                                i,
 | 
			
		||||
                                len_csms,
 | 
			
		||||
                                prefix + "Done adjusting url of submodule %r" % (sm.name),
 | 
			
		||||
                            )
 | 
			
		||||
                        # END skip remote handling if new url already exists in module
 | 
			
		||||
                    # END handle url
 | 
			
		||||
 | 
			
		||||
                    # HANDLE PATH CHANGES
 | 
			
		||||
                    #####################
 | 
			
		||||
                    if sm.branch_path != psm.branch_path:
 | 
			
		||||
                        # finally, create a new tracking branch which tracks the
 | 
			
		||||
                        # new remote branch
 | 
			
		||||
                        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
 | 
			
		||||
                            # As the branch might not exist yet, we will have to fetch all remotes to be sure ... .
 | 
			
		||||
                            for remote in smmr:
 | 
			
		||||
                                remote.fetch(progress=progress)
 | 
			
		||||
                            # end for each remote
 | 
			
		||||
 | 
			
		||||
                            try:
 | 
			
		||||
                                tbr = git.Head.create(
 | 
			
		||||
                                    smm,
 | 
			
		||||
                                    sm.branch_name,
 | 
			
		||||
                                    logmsg="branch: Created from HEAD",
 | 
			
		||||
                                )
 | 
			
		||||
                            except OSError:
 | 
			
		||||
                                # ... or reuse the existing one
 | 
			
		||||
                                tbr = git.Head(smm, sm.branch_path)
 | 
			
		||||
                            # END assure tracking branch exists
 | 
			
		||||
 | 
			
		||||
                            tbr.set_tracking_branch(find_first_remote_branch(smmr, sm.branch_name))
 | 
			
		||||
                            # NOTE: All head-resetting is done in the base implementation of update
 | 
			
		||||
                            # but we will have to checkout the new branch here. As it still points to the currently
 | 
			
		||||
                            # checkout out commit, we don't do any harm.
 | 
			
		||||
                            # As we don't want to update working-tree or index, changing the ref is all there is to do
 | 
			
		||||
                            smm.head.reference = tbr
 | 
			
		||||
                        # END handle dry_run
 | 
			
		||||
 | 
			
		||||
                        progress.update(
 | 
			
		||||
                            END | BRANCHCHANGE,
 | 
			
		||||
                            i,
 | 
			
		||||
                            len_csms,
 | 
			
		||||
                            prefix + "Done changing branch of submodule %r" % sm.name,
 | 
			
		||||
                        )
 | 
			
		||||
                    # END handle branch
 | 
			
		||||
                # END handle
 | 
			
		||||
            # END for each common submodule
 | 
			
		||||
        except Exception as err:
 | 
			
		||||
            if not keep_going:
 | 
			
		||||
                raise
 | 
			
		||||
            log.error(str(err))
 | 
			
		||||
        # end handle keep_going
 | 
			
		||||
 | 
			
		||||
        # FINALLY UPDATE ALL ACTUAL SUBMODULES
 | 
			
		||||
        ######################################
 | 
			
		||||
        for sm in sms:
 | 
			
		||||
            # update the submodule using the default method
 | 
			
		||||
            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,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # update recursively depth first - question is which inconsistent
 | 
			
		||||
            # state will be better in case it fails somewhere. Defective branch
 | 
			
		||||
            # or defective depth. The RootSubmodule type will never process itself,
 | 
			
		||||
            # which was done in the previous expression
 | 
			
		||||
            if recursive:
 | 
			
		||||
                # the module would exist by now if we are not in dry_run mode
 | 
			
		||||
                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,
 | 
			
		||||
                    )
 | 
			
		||||
                # END handle dry_run
 | 
			
		||||
            # END handle recursive
 | 
			
		||||
        # END for each submodule to update
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def module(self) -> "Repo":
 | 
			
		||||
        """:return: the actual repository containing the submodules"""
 | 
			
		||||
        return self.repo
 | 
			
		||||
 | 
			
		||||
    # } END interface
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# } END classes
 | 
			
		||||
@@ -0,0 +1,118 @@
 | 
			
		||||
import git
 | 
			
		||||
from git.exc import InvalidGitRepositoryError
 | 
			
		||||
from git.config import GitConfigParser
 | 
			
		||||
from io import BytesIO
 | 
			
		||||
import weakref
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# typing -----------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
from typing import Any, Sequence, TYPE_CHECKING, Union
 | 
			
		||||
 | 
			
		||||
from git.types import PathLike
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
    from .base import Submodule
 | 
			
		||||
    from weakref import ReferenceType
 | 
			
		||||
    from git.repo import Repo
 | 
			
		||||
    from git.refs import Head
 | 
			
		||||
    from git import Remote
 | 
			
		||||
    from git.refs import RemoteReference
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__all__ = (
 | 
			
		||||
    "sm_section",
 | 
			
		||||
    "sm_name",
 | 
			
		||||
    "mkhead",
 | 
			
		||||
    "find_first_remote_branch",
 | 
			
		||||
    "SubmoduleConfigParser",
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# { Utilities
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def sm_section(name: str) -> str:
 | 
			
		||||
    """:return: section title used in .gitmodules configuration file"""
 | 
			
		||||
    return f'submodule "{name}"'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def sm_name(section: str) -> str:
 | 
			
		||||
    """:return: name of the submodule as parsed from the section name"""
 | 
			
		||||
    section = section.strip()
 | 
			
		||||
    return section[11:-1]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def mkhead(repo: "Repo", path: PathLike) -> "Head":
 | 
			
		||||
    """:return: New branch/head instance"""
 | 
			
		||||
    return git.Head(repo, git.Head.to_full_path(path))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def find_first_remote_branch(remotes: Sequence["Remote"], branch_name: str) -> "RemoteReference":
 | 
			
		||||
    """Find the remote branch matching the name of the given branch or raise InvalidGitRepositoryError"""
 | 
			
		||||
    for remote in remotes:
 | 
			
		||||
        try:
 | 
			
		||||
            return remote.refs[branch_name]
 | 
			
		||||
        except IndexError:
 | 
			
		||||
            continue
 | 
			
		||||
        # END exception handling
 | 
			
		||||
    # END for remote
 | 
			
		||||
    raise InvalidGitRepositoryError("Didn't find remote branch '%r' in any of the given remotes" % branch_name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# } END utilities
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# { Classes
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SubmoduleConfigParser(GitConfigParser):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    Catches calls to _write, and updates the .gitmodules blob in the index
 | 
			
		||||
    with the new data, if we have written into a stream. Otherwise it will
 | 
			
		||||
    add the local file to the index to make it correspond with the working tree.
 | 
			
		||||
    Additionally, the cache must be cleared
 | 
			
		||||
 | 
			
		||||
    Please note that no mutating method will work in bare mode
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args: Any, **kwargs: Any) -> None:
 | 
			
		||||
        self._smref: Union["ReferenceType[Submodule]", None] = None
 | 
			
		||||
        self._index = None
 | 
			
		||||
        self._auto_write = True
 | 
			
		||||
        super(SubmoduleConfigParser, self).__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    # { Interface
 | 
			
		||||
    def set_submodule(self, submodule: "Submodule") -> None:
 | 
			
		||||
        """Set this instance's submodule. It must be called before
 | 
			
		||||
        the first write operation begins"""
 | 
			
		||||
        self._smref = weakref.ref(submodule)
 | 
			
		||||
 | 
			
		||||
    def flush_to_index(self) -> None:
 | 
			
		||||
        """Flush changes in our configuration file to the index"""
 | 
			
		||||
        assert self._smref is not None
 | 
			
		||||
        # should always have a file here
 | 
			
		||||
        assert not isinstance(self._file_or_files, BytesIO)
 | 
			
		||||
 | 
			
		||||
        sm = self._smref()
 | 
			
		||||
        if sm is not None:
 | 
			
		||||
            index = self._index
 | 
			
		||||
            if index is None:
 | 
			
		||||
                index = sm.repo.index
 | 
			
		||||
            # END handle index
 | 
			
		||||
            index.add([sm.k_modules_file], write=self._auto_write)
 | 
			
		||||
            sm._clear_cache()
 | 
			
		||||
        # END handle weakref
 | 
			
		||||
 | 
			
		||||
    # } END interface
 | 
			
		||||
 | 
			
		||||
    # { Overridden Methods
 | 
			
		||||
    def write(self) -> None:  # type: ignore[override]
 | 
			
		||||
        rval: None = super(SubmoduleConfigParser, self).write()
 | 
			
		||||
        self.flush_to_index()
 | 
			
		||||
        return rval
 | 
			
		||||
 | 
			
		||||
    # END overridden methods
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# } END classes
 | 
			
		||||
		Reference in New Issue
	
	Block a user