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__ = ["Submodule", "UpdateProgress"] 
    5 
    6import gc 
    7from io import BytesIO 
    8import logging 
    9import os 
    10import os.path as osp 
    11import stat 
    12import sys 
    13import uuid 
    14 
    15import git 
    16from git.cmd import Git 
    17from git.compat import defenc 
    18from git.config import GitConfigParser, SectionConstraint, cp 
    19from git.exc import ( 
    20    BadName, 
    21    InvalidGitRepositoryError, 
    22    NoSuchPathError, 
    23    RepositoryDirtyError, 
    24) 
    25from git.objects.base import IndexObject, Object 
    26from git.objects.util import TraversableIterableObj 
    27from git.util import ( 
    28    IterableList, 
    29    RemoteProgress, 
    30    join_path_native, 
    31    rmtree, 
    32    to_native_path_linux, 
    33    unbare_repo, 
    34) 
    35 
    36from .util import ( 
    37    SubmoduleConfigParser, 
    38    find_first_remote_branch, 
    39    mkhead, 
    40    sm_name, 
    41    sm_section, 
    42) 
    43 
    44# typing ---------------------------------------------------------------------- 
    45 
    46from typing import ( 
    47    Any, 
    48    Callable, 
    49    Dict, 
    50    Iterator, 
    51    Mapping, 
    52    Sequence, 
    53    TYPE_CHECKING, 
    54    Union, 
    55    cast, 
    56) 
    57 
    58if sys.version_info >= (3, 8): 
    59    from typing import Literal 
    60else: 
    61    from typing_extensions import Literal 
    62 
    63from git.types import Commit_ish, PathLike, TBD 
    64 
    65if TYPE_CHECKING: 
    66    from git.index import IndexFile 
    67    from git.objects.commit import Commit 
    68    from git.refs import Head 
    69    from git.repo import Repo 
    70 
    71# ----------------------------------------------------------------------------- 
    72 
    73_logger = logging.getLogger(__name__) 
    74 
    75 
    76class UpdateProgress(RemoteProgress): 
    77    """Class providing detailed progress information to the caller who should 
    78    derive from it and implement the 
    79    :meth:`update(...) <git.util.RemoteProgress.update>` message.""" 
    80 
    81    CLONE, FETCH, UPDWKTREE = [1 << x for x in range(RemoteProgress._num_op_codes, RemoteProgress._num_op_codes + 3)] 
    82    _num_op_codes: int = RemoteProgress._num_op_codes + 3 
    83 
    84    __slots__ = () 
    85 
    86 
    87BEGIN = UpdateProgress.BEGIN 
    88END = UpdateProgress.END 
    89CLONE = UpdateProgress.CLONE 
    90FETCH = UpdateProgress.FETCH 
    91UPDWKTREE = UpdateProgress.UPDWKTREE 
    92 
    93 
    94# IndexObject comes via the util module. It's a 'hacky' fix thanks to Python's import 
    95# mechanism, which causes plenty of trouble if the only reason for packages and modules 
    96# is refactoring - subpackages shouldn't depend on parent packages. 
    97class Submodule(IndexObject, TraversableIterableObj): 
    98    """Implements access to a git submodule. They are special in that their sha 
    99    represents a commit in the submodule's repository which is to be checked out 
    100    at the path of this instance. 
    101 
    102    The submodule type does not have a string type associated with it, as it exists 
    103    solely as a marker in the tree and index. 
    104 
    105    All methods work in bare and non-bare repositories. 
    106    """ 
    107 
    108    _id_attribute_ = "name" 
    109    k_modules_file = ".gitmodules" 
    110    k_head_option = "branch" 
    111    k_head_default = "master" 
    112    k_default_mode = stat.S_IFDIR | stat.S_IFLNK 
    113    """Submodule flags. Submodules are directories with link-status.""" 
    114 
    115    type: Literal["submodule"] = "submodule"  # type: ignore[assignment] 
    116    """This is a bogus type string for base class compatibility.""" 
    117 
    118    __slots__ = ("_parent_commit", "_url", "_branch_path", "_name", "__weakref__") 
    119 
    120    _cache_attrs = ("path", "_url", "_branch_path") 
    121 
    122    def __init__( 
    123        self, 
    124        repo: "Repo", 
    125        binsha: bytes, 
    126        mode: Union[int, None] = None, 
    127        path: Union[PathLike, None] = None, 
    128        name: Union[str, None] = None, 
    129        parent_commit: Union["Commit", None] = None, 
    130        url: Union[str, None] = None, 
    131        branch_path: Union[PathLike, None] = None, 
    132    ) -> None: 
    133        """Initialize this instance with its attributes. 
    134 
    135        We only document the parameters that differ from 
    136        :class:`~git.objects.base.IndexObject`. 
    137 
    138        :param repo: 
    139            Our parent repository. 
    140 
    141        :param binsha: 
    142            Binary sha referring to a commit in the remote repository. 
    143            See the `url` parameter. 
    144 
    145        :param parent_commit: 
    146            The :class:`~git.objects.commit.Commit` whose tree is supposed to contain 
    147            the ``.gitmodules`` blob, or ``None`` to always point to the most recent 
    148            commit. See :meth:`set_parent_commit` for details. 
    149 
    150        :param url: 
    151            The URL to the remote repository which is the submodule. 
    152 
    153        :param branch_path: 
    154            Full repository-relative path to ref to checkout when cloning the remote 
    155            repository. 
    156        """ 
    157        super().__init__(repo, binsha, mode, path) 
    158        self.size = 0 
    159        self._parent_commit = parent_commit 
    160        if url is not None: 
    161            self._url = url 
    162        if branch_path is not None: 
    163            self._branch_path = branch_path 
    164        if name is not None: 
    165            self._name = name 
    166 
    167    def _set_cache_(self, attr: str) -> None: 
    168        if attr in ("path", "_url", "_branch_path"): 
    169            reader: SectionConstraint = self.config_reader() 
    170            # Default submodule values. 
    171            try: 
    172                self.path = reader.get("path") 
    173            except cp.NoSectionError as e: 
    174                if self.repo.working_tree_dir is not None: 
    175                    raise ValueError( 
    176                        "This submodule instance does not exist anymore in '%s' file" 
    177                        % osp.join(self.repo.working_tree_dir, ".gitmodules") 
    178                    ) from e 
    179 
    180            self._url = reader.get("url") 
    181            # GitPython extension values - optional. 
    182            self._branch_path = reader.get_value(self.k_head_option, git.Head.to_full_path(self.k_head_default)) 
    183        elif attr == "_name": 
    184            raise AttributeError("Cannot retrieve the name of a submodule if it was not set initially") 
    185        else: 
    186            super()._set_cache_(attr) 
    187        # END handle attribute name 
    188 
    189    @classmethod 
    190    def _get_intermediate_items(cls, item: "Submodule") -> IterableList["Submodule"]: 
    191        """:return: All the submodules of our module repository""" 
    192        try: 
    193            return cls.list_items(item.module()) 
    194        except InvalidGitRepositoryError: 
    195            return IterableList("") 
    196        # END handle intermediate items 
    197 
    198    @classmethod 
    199    def _need_gitfile_submodules(cls, git: Git) -> bool: 
    200        return git.version_info[:3] >= (1, 7, 5) 
    201 
    202    def __eq__(self, other: Any) -> bool: 
    203        """Compare with another submodule.""" 
    204        # We may only compare by name as this should be the ID they are hashed with. 
    205        # Otherwise this type wouldn't be hashable. 
    206        # return self.path == other.path and self.url == other.url and super().__eq__(other) 
    207        return self._name == other._name 
    208 
    209    def __ne__(self, other: object) -> bool: 
    210        """Compare with another submodule for inequality.""" 
    211        return not (self == other) 
    212 
    213    def __hash__(self) -> int: 
    214        """Hash this instance using its logical id, not the sha.""" 
    215        return hash(self._name) 
    216 
    217    def __str__(self) -> str: 
    218        return self._name 
    219 
    220    def __repr__(self) -> str: 
    221        return "git.%s(name=%s, path=%s, url=%s, branch_path=%s)" % ( 
    222            type(self).__name__, 
    223            self._name, 
    224            self.path, 
    225            self.url, 
    226            self.branch_path, 
    227        ) 
    228 
    229    @classmethod 
    230    def _config_parser( 
    231        cls, repo: "Repo", parent_commit: Union["Commit", None], read_only: bool 
    232    ) -> SubmoduleConfigParser: 
    233        """ 
    234        :return: 
    235            Config parser constrained to our submodule in read or write mode 
    236 
    237        :raise IOError: 
    238            If the ``.gitmodules`` file cannot be found, either locally or in the 
    239            repository at the given parent commit. Otherwise the exception would be 
    240            delayed until the first access of the config parser. 
    241        """ 
    242        parent_matches_head = True 
    243        if parent_commit is not None: 
    244            try: 
    245                parent_matches_head = repo.head.commit == parent_commit 
    246            except ValueError: 
    247                # We are most likely in an empty repository, so the HEAD doesn't point 
    248                # to a valid ref. 
    249                pass 
    250        # END handle parent_commit 
    251        fp_module: Union[str, BytesIO] 
    252        if not repo.bare and parent_matches_head and repo.working_tree_dir: 
    253            fp_module = osp.join(repo.working_tree_dir, cls.k_modules_file) 
    254        else: 
    255            assert parent_commit is not None, "need valid parent_commit in bare repositories" 
    256            try: 
    257                fp_module = cls._sio_modules(parent_commit) 
    258            except KeyError as e: 
    259                raise IOError( 
    260                    "Could not find %s file in the tree of parent commit %s" % (cls.k_modules_file, parent_commit) 
    261                ) from e 
    262            # END handle exceptions 
    263        # END handle non-bare working tree 
    264 
    265        if not read_only and (repo.bare or not parent_matches_head): 
    266            raise ValueError("Cannot write blobs of 'historical' submodule configurations") 
    267        # END handle writes of historical submodules 
    268 
    269        return SubmoduleConfigParser(fp_module, read_only=read_only) 
    270 
    271    def _clear_cache(self) -> None: 
    272        """Clear the possibly changed values.""" 
    273        for name in self._cache_attrs: 
    274            try: 
    275                delattr(self, name) 
    276            except AttributeError: 
    277                pass 
    278            # END try attr deletion 
    279        # END for each name to delete 
    280 
    281    @classmethod 
    282    def _sio_modules(cls, parent_commit: "Commit") -> BytesIO: 
    283        """ 
    284        :return: 
    285            Configuration file as :class:`~io.BytesIO` - we only access it through the 
    286            respective blob's data 
    287        """ 
    288        sio = BytesIO(parent_commit.tree[cls.k_modules_file].data_stream.read()) 
    289        sio.name = cls.k_modules_file 
    290        return sio 
    291 
    292    def _config_parser_constrained(self, read_only: bool) -> SectionConstraint: 
    293        """:return: Config parser constrained to our submodule in read or write mode""" 
    294        try: 
    295            pc = self.parent_commit 
    296        except ValueError: 
    297            pc = None 
    298        # END handle empty parent repository 
    299        parser = self._config_parser(self.repo, pc, read_only) 
    300        parser.set_submodule(self) 
    301        return SectionConstraint(parser, sm_section(self.name)) 
    302 
    303    @classmethod 
    304    def _module_abspath(cls, parent_repo: "Repo", path: PathLike, name: str) -> PathLike: 
    305        if cls._need_gitfile_submodules(parent_repo.git): 
    306            return osp.join(parent_repo.git_dir, "modules", name) 
    307        if parent_repo.working_tree_dir: 
    308            return osp.join(parent_repo.working_tree_dir, path) 
    309        raise NotADirectoryError() 
    310 
    311    @classmethod 
    312    def _clone_repo( 
    313        cls, 
    314        repo: "Repo", 
    315        url: str, 
    316        path: PathLike, 
    317        name: str, 
    318        allow_unsafe_options: bool = False, 
    319        allow_unsafe_protocols: bool = False, 
    320        **kwargs: Any, 
    321    ) -> "Repo": 
    322        """ 
    323        :return: 
    324            :class:`~git.repo.base.Repo` instance of newly cloned repository. 
    325 
    326        :param repo: 
    327            Our parent repository. 
    328 
    329        :param url: 
    330            URL to clone from. 
    331 
    332        :param path: 
    333            Repository-relative path to the submodule checkout location. 
    334 
    335        :param name: 
    336            Canonical name of the submodule. 
    337 
    338        :param allow_unsafe_protocols: 
    339            Allow unsafe protocols to be used, like ``ext``. 
    340 
    341        :param allow_unsafe_options: 
    342            Allow unsafe options to be used, like ``--upload-pack``. 
    343 
    344        :param kwargs: 
    345            Additional arguments given to :manpage:`git-clone(1)`. 
    346        """ 
    347        module_abspath = cls._module_abspath(repo, path, name) 
    348        module_checkout_path = module_abspath 
    349        if cls._need_gitfile_submodules(repo.git): 
    350            kwargs["separate_git_dir"] = module_abspath 
    351            module_abspath_dir = osp.dirname(module_abspath) 
    352            if not osp.isdir(module_abspath_dir): 
    353                os.makedirs(module_abspath_dir) 
    354            module_checkout_path = osp.join(str(repo.working_tree_dir), path) 
    355 
    356        clone = git.Repo.clone_from( 
    357            url, 
    358            module_checkout_path, 
    359            allow_unsafe_options=allow_unsafe_options, 
    360            allow_unsafe_protocols=allow_unsafe_protocols, 
    361            **kwargs, 
    362        ) 
    363        if cls._need_gitfile_submodules(repo.git): 
    364            cls._write_git_file_and_module_config(module_checkout_path, module_abspath) 
    365 
    366        return clone 
    367 
    368    @classmethod 
    369    def _to_relative_path(cls, parent_repo: "Repo", path: PathLike) -> PathLike: 
    370        """:return: A path guaranteed to be relative to the given parent repository 
    371 
    372        :raise ValueError: 
    373            If path is not contained in the parent repository's working tree. 
    374        """ 
    375        path = to_native_path_linux(path) 
    376        if path.endswith("/"): 
    377            path = path[:-1] 
    378        # END handle trailing slash 
    379 
    380        if osp.isabs(path) and parent_repo.working_tree_dir: 
    381            working_tree_linux = to_native_path_linux(parent_repo.working_tree_dir) 
    382            if not path.startswith(working_tree_linux): 
    383                raise ValueError( 
    384                    "Submodule checkout path '%s' needs to be within the parents repository at '%s'" 
    385                    % (working_tree_linux, path) 
    386                ) 
    387            path = path[len(working_tree_linux.rstrip("/")) + 1 :] 
    388            if not path: 
    389                raise ValueError("Absolute submodule path '%s' didn't yield a valid relative path" % path) 
    390            # END verify converted relative path makes sense 
    391        # END convert to a relative path 
    392 
    393        return path 
    394 
    395    @classmethod 
    396    def _write_git_file_and_module_config(cls, working_tree_dir: PathLike, module_abspath: PathLike) -> None: 
    397        """Write a ``.git`` file containing a (preferably) relative path to the actual 
    398        git module repository. 
    399 
    400        It is an error if the `module_abspath` cannot be made into a relative path, 
    401        relative to the `working_tree_dir`. 
    402 
    403        :note: 
    404            This will overwrite existing files! 
    405 
    406        :note: 
    407            As we rewrite both the git file as well as the module configuration, we 
    408            might fail on the configuration and will not roll back changes done to the 
    409            git file. This should be a non-issue, but may easily be fixed if it becomes 
    410            one. 
    411 
    412        :param working_tree_dir: 
    413            Directory to write the ``.git`` file into. 
    414 
    415        :param module_abspath: 
    416            Absolute path to the bare repository. 
    417        """ 
    418        git_file = osp.join(working_tree_dir, ".git") 
    419        rela_path = osp.relpath(module_abspath, start=working_tree_dir) 
    420        if sys.platform == "win32" and osp.isfile(git_file): 
    421            os.remove(git_file) 
    422        with open(git_file, "wb") as fp: 
    423            fp.write(("gitdir: %s" % rela_path).encode(defenc)) 
    424 
    425        with GitConfigParser(osp.join(module_abspath, "config"), read_only=False, merge_includes=False) as writer: 
    426            writer.set_value( 
    427                "core", 
    428                "worktree", 
    429                to_native_path_linux(osp.relpath(working_tree_dir, start=module_abspath)), 
    430            ) 
    431 
    432    # { Edit Interface 
    433 
    434    @classmethod 
    435    def add( 
    436        cls, 
    437        repo: "Repo", 
    438        name: str, 
    439        path: PathLike, 
    440        url: Union[str, None] = None, 
    441        branch: Union[str, None] = None, 
    442        no_checkout: bool = False, 
    443        depth: Union[int, None] = None, 
    444        env: Union[Mapping[str, str], None] = None, 
    445        clone_multi_options: Union[Sequence[TBD], None] = None, 
    446        allow_unsafe_options: bool = False, 
    447        allow_unsafe_protocols: bool = False, 
    448    ) -> "Submodule": 
    449        """Add a new submodule to the given repository. This will alter the index as 
    450        well as the ``.gitmodules`` file, but will not create a new commit. If the 
    451        submodule already exists, no matter if the configuration differs from the one 
    452        provided, the existing submodule will be returned. 
    453 
    454        :param repo: 
    455            Repository instance which should receive the submodule. 
    456 
    457        :param name: 
    458            The name/identifier for the submodule. 
    459 
    460        :param path: 
    461            Repository-relative or absolute path at which the submodule should be 
    462            located. 
    463            It will be created as required during the repository initialization. 
    464 
    465        :param url: 
    466            ``git clone ...``-compatible URL. See :manpage:`git-clone(1)` for more 
    467            information. If ``None``, the repository is assumed to exist, and the URL of 
    468            the first remote is taken instead. This is useful if you want to make an 
    469            existing repository a submodule of another one. 
    470 
    471        :param branch: 
    472            Name of branch at which the submodule should (later) be checked out. The 
    473            given branch must exist in the remote repository, and will be checked out 
    474            locally as a tracking branch. 
    475            It will only be written into the configuration if it not ``None``, which is 
    476            when the checked out branch will be the one the remote HEAD pointed to. 
    477            The result you get in these situation is somewhat fuzzy, and it is 
    478            recommended to specify at least ``master`` here. 
    479            Examples are ``master`` or ``feature/new``. 
    480 
    481        :param no_checkout: 
    482            If ``True``, and if the repository has to be cloned manually, no checkout 
    483            will be performed. 
    484 
    485        :param depth: 
    486            Create a shallow clone with a history truncated to the specified number of 
    487            commits. 
    488 
    489        :param env: 
    490            Optional dictionary containing the desired environment variables. 
    491 
    492            Note: Provided variables will be used to update the execution environment 
    493            for ``git``. If some variable is not specified in `env` and is defined in 
    494            attr:`os.environ`, the value from attr:`os.environ` will be used. If you 
    495            want to unset some variable, consider providing an empty string as its 
    496            value. 
    497 
    498        :param clone_multi_options: 
    499            A list of clone options. Please see 
    500            :meth:`Repo.clone <git.repo.base.Repo.clone>` for details. 
    501 
    502        :param allow_unsafe_protocols: 
    503            Allow unsafe protocols to be used, like ``ext``. 
    504 
    505        :param allow_unsafe_options: 
    506            Allow unsafe options to be used, like ``--upload-pack``. 
    507 
    508        :return: 
    509            The newly created :class:`Submodule` instance. 
    510 
    511        :note: 
    512            Works atomically, such that no change will be done if, for example, the 
    513            repository update fails. 
    514        """ 
    515        if repo.bare: 
    516            raise InvalidGitRepositoryError("Cannot add submodules to bare repositories") 
    517        # END handle bare repos 
    518 
    519        path = cls._to_relative_path(repo, path) 
    520 
    521        # Ensure we never put backslashes into the URL, as might happen on Windows. 
    522        if url is not None: 
    523            url = to_native_path_linux(url) 
    524        # END ensure URL correctness 
    525 
    526        # INSTANTIATE INTERMEDIATE SM 
    527        sm = cls( 
    528            repo, 
    529            cls.NULL_BIN_SHA, 
    530            cls.k_default_mode, 
    531            path, 
    532            name, 
    533            url="invalid-temporary", 
    534        ) 
    535        if sm.exists(): 
    536            # Reretrieve submodule from tree. 
    537            try: 
    538                sm = repo.head.commit.tree[str(path)] 
    539                sm._name = name 
    540                return sm 
    541            except KeyError: 
    542                # Could only be in index. 
    543                index = repo.index 
    544                entry = index.entries[index.entry_key(path, 0)] 
    545                sm.binsha = entry.binsha 
    546                return sm 
    547            # END handle exceptions 
    548        # END handle existing 
    549 
    550        # fake-repo - we only need the functionality on the branch instance. 
    551        br = git.Head(repo, git.Head.to_full_path(str(branch) or cls.k_head_default)) 
    552        has_module = sm.module_exists() 
    553        branch_is_default = branch is None 
    554        if has_module and url is not None: 
    555            if url not in [r.url for r in sm.module().remotes]: 
    556                raise ValueError( 
    557                    "Specified URL '%s' does not match any remote url of the repository at '%s'" % (url, sm.abspath) 
    558                ) 
    559            # END check url 
    560        # END verify urls match 
    561 
    562        mrepo: Union[Repo, None] = None 
    563 
    564        if url is None: 
    565            if not has_module: 
    566                raise ValueError("A URL was not given and a repository did not exist at %s" % path) 
    567            # END check url 
    568            mrepo = sm.module() 
    569            # assert isinstance(mrepo, git.Repo) 
    570            urls = [r.url for r in mrepo.remotes] 
    571            if not urls: 
    572                raise ValueError("Didn't find any remote url in repository at %s" % sm.abspath) 
    573            # END verify we have url 
    574            url = urls[0] 
    575        else: 
    576            # Clone new repo. 
    577            kwargs: Dict[str, Union[bool, int, str, Sequence[TBD]]] = {"n": no_checkout} 
    578            if not branch_is_default: 
    579                kwargs["b"] = br.name 
    580            # END setup checkout-branch 
    581 
    582            if depth: 
    583                if isinstance(depth, int): 
    584                    kwargs["depth"] = depth 
    585                else: 
    586                    raise ValueError("depth should be an integer") 
    587            if clone_multi_options: 
    588                kwargs["multi_options"] = clone_multi_options 
    589 
    590            # _clone_repo(cls, repo, url, path, name, **kwargs): 
    591            mrepo = cls._clone_repo( 
    592                repo, 
    593                url, 
    594                path, 
    595                name, 
    596                env=env, 
    597                allow_unsafe_options=allow_unsafe_options, 
    598                allow_unsafe_protocols=allow_unsafe_protocols, 
    599                **kwargs, 
    600            ) 
    601        # END verify url 
    602 
    603        ## See #525 for ensuring git URLs in config-files are valid under Windows. 
    604        url = Git.polish_url(url) 
    605 
    606        # It's important to add the URL to the parent config, to let `git submodule` know. 
    607        # Otherwise there is a '-' character in front of the submodule listing: 
    608        #  a38efa84daef914e4de58d1905a500d8d14aaf45 mymodule (v0.9.0-1-ga38efa8) 
    609        # -a38efa84daef914e4de58d1905a500d8d14aaf45 submodules/intermediate/one 
    610        writer: Union[GitConfigParser, SectionConstraint] 
    611 
    612        with sm.repo.config_writer() as writer: 
    613            writer.set_value(sm_section(name), "url", url) 
    614 
    615        # Update configuration and index. 
    616        index = sm.repo.index 
    617        with sm.config_writer(index=index, write=False) as writer: 
    618            writer.set_value("url", url) 
    619            writer.set_value("path", path) 
    620 
    621            sm._url = url 
    622            if not branch_is_default: 
    623                # Store full path. 
    624                writer.set_value(cls.k_head_option, br.path) 
    625                sm._branch_path = br.path 
    626 
    627        # We deliberately assume that our head matches our index! 
    628        if mrepo: 
    629            sm.binsha = mrepo.head.commit.binsha 
    630        index.add([sm], write=True) 
    631 
    632        return sm 
    633 
    634    def update( 
    635        self, 
    636        recursive: bool = False, 
    637        init: bool = True, 
    638        to_latest_revision: bool = False, 
    639        progress: Union["UpdateProgress", None] = None, 
    640        dry_run: bool = False, 
    641        force: bool = False, 
    642        keep_going: bool = False, 
    643        env: Union[Mapping[str, str], None] = None, 
    644        clone_multi_options: Union[Sequence[TBD], None] = None, 
    645        allow_unsafe_options: bool = False, 
    646        allow_unsafe_protocols: bool = False, 
    647    ) -> "Submodule": 
    648        """Update the repository of this submodule to point to the checkout we point at 
    649        with the binsha of this instance. 
    650 
    651        :param recursive: 
    652            If ``True``, we will operate recursively and update child modules as well. 
    653 
    654        :param init: 
    655            If ``True``, the module repository will be cloned into place if necessary. 
    656 
    657        :param to_latest_revision: 
    658            If ``True``, the submodule's sha will be ignored during checkout. Instead, 
    659            the remote will be fetched, and the local tracking branch updated. This only 
    660            works if we have a local tracking branch, which is the case if the remote 
    661            repository had a master branch, or if the ``branch`` option was specified 
    662            for this submodule and the branch existed remotely. 
    663 
    664        :param progress: 
    665            :class:`UpdateProgress` instance, or ``None`` if no progress should be 
    666            shown. 
    667 
    668        :param dry_run: 
    669            If ``True``, the operation will only be simulated, but not performed. 
    670            All performed operations are read-only. 
    671 
    672        :param force: 
    673            If ``True``, we may reset heads even if the repository in question is dirty. 
    674            Additionally we will be allowed to set a tracking branch which is ahead of 
    675            its remote branch back into the past or the location of the remote branch. 
    676            This will essentially 'forget' commits. 
    677 
    678            If ``False``, local tracking branches that are in the future of their 
    679            respective remote branches will simply not be moved. 
    680 
    681        :param keep_going: 
    682            If ``True``, we will ignore but log all errors, and keep going recursively. 
    683            Unless `dry_run` is set as well, `keep_going` could cause 
    684            subsequent/inherited errors you wouldn't see otherwise. 
    685            In conjunction with `dry_run`, it can be useful to anticipate all errors 
    686            when updating submodules. 
    687 
    688        :param env: 
    689            Optional dictionary containing the desired environment variables. 
    690 
    691            Note: Provided variables will be used to update the execution environment 
    692            for ``git``. If some variable is not specified in `env` and is defined in 
    693            attr:`os.environ`, value from attr:`os.environ` will be used. 
    694 
    695            If you want to unset some variable, consider providing the empty string as 
    696            its value. 
    697 
    698        :param clone_multi_options: 
    699            List of :manpage:`git-clone(1)` options. 
    700            Please see :meth:`Repo.clone <git.repo.base.Repo.clone>` for details. 
    701            They only take effect with the `init` option. 
    702 
    703        :param allow_unsafe_protocols: 
    704            Allow unsafe protocols to be used, like ``ext``. 
    705 
    706        :param allow_unsafe_options: 
    707            Allow unsafe options to be used, like ``--upload-pack``. 
    708 
    709        :note: 
    710            Does nothing in bare repositories. 
    711 
    712        :note: 
    713            This method is definitely not atomic if `recursive` is ``True``. 
    714 
    715        :return: 
    716            self 
    717        """ 
    718        if self.repo.bare: 
    719            return self 
    720        # END pass in bare mode 
    721 
    722        if progress is None: 
    723            progress = UpdateProgress() 
    724        # END handle progress 
    725        prefix = "" 
    726        if dry_run: 
    727            prefix = "DRY-RUN: " 
    728        # END handle prefix 
    729 
    730        # To keep things plausible in dry-run mode. 
    731        if dry_run: 
    732            mrepo = None 
    733        # END init mrepo 
    734 
    735        try: 
    736            # ENSURE REPO IS PRESENT AND UP-TO-DATE 
    737            ####################################### 
    738            try: 
    739                mrepo = self.module() 
    740                rmts = mrepo.remotes 
    741                len_rmts = len(rmts) 
    742                for i, remote in enumerate(rmts): 
    743                    op = FETCH 
    744                    if i == 0: 
    745                        op |= BEGIN 
    746                    # END handle start 
    747 
    748                    progress.update( 
    749                        op, 
    750                        i, 
    751                        len_rmts, 
    752                        prefix + "Fetching remote %s of submodule %r" % (remote, self.name), 
    753                    ) 
    754                    # =============================== 
    755                    if not dry_run: 
    756                        remote.fetch(progress=progress) 
    757                    # END handle dry-run 
    758                    # =============================== 
    759                    if i == len_rmts - 1: 
    760                        op |= END 
    761                    # END handle end 
    762                    progress.update( 
    763                        op, 
    764                        i, 
    765                        len_rmts, 
    766                        prefix + "Done fetching remote of submodule %r" % self.name, 
    767                    ) 
    768                # END fetch new data 
    769            except InvalidGitRepositoryError: 
    770                mrepo = None 
    771                if not init: 
    772                    return self 
    773                # END early abort if init is not allowed 
    774 
    775                # There is no git-repository yet - but delete empty paths. 
    776                checkout_module_abspath = self.abspath 
    777                if not dry_run and osp.isdir(checkout_module_abspath): 
    778                    try: 
    779                        os.rmdir(checkout_module_abspath) 
    780                    except OSError as e: 
    781                        raise OSError( 
    782                            "Module directory at %r does already exist and is non-empty" % checkout_module_abspath 
    783                        ) from e 
    784                    # END handle OSError 
    785                # END handle directory removal 
    786 
    787                # Don't check it out at first - nonetheless it will create a local 
    788                # branch according to the remote-HEAD if possible. 
    789                progress.update( 
    790                    BEGIN | CLONE, 
    791                    0, 
    792                    1, 
    793                    prefix 
    794                    + "Cloning url '%s' to '%s' in submodule %r" % (self.url, checkout_module_abspath, self.name), 
    795                ) 
    796                if not dry_run: 
    797                    mrepo = self._clone_repo( 
    798                        self.repo, 
    799                        self.url, 
    800                        self.path, 
    801                        self.name, 
    802                        n=True, 
    803                        env=env, 
    804                        multi_options=clone_multi_options, 
    805                        allow_unsafe_options=allow_unsafe_options, 
    806                        allow_unsafe_protocols=allow_unsafe_protocols, 
    807                    ) 
    808                # END handle dry-run 
    809                progress.update( 
    810                    END | CLONE, 
    811                    0, 
    812                    1, 
    813                    prefix + "Done cloning to %s" % checkout_module_abspath, 
    814                ) 
    815 
    816                if not dry_run: 
    817                    # See whether we have a valid branch to check out. 
    818                    try: 
    819                        mrepo = cast("Repo", mrepo) 
    820                        # Find a remote which has our branch - we try to be flexible. 
    821                        remote_branch = find_first_remote_branch(mrepo.remotes, self.branch_name) 
    822                        local_branch = mkhead(mrepo, self.branch_path) 
    823 
    824                        # Have a valid branch, but no checkout - make sure we can figure 
    825                        # that out by marking the commit with a null_sha. 
    826                        local_branch.set_object(Object(mrepo, self.NULL_BIN_SHA)) 
    827                        # END initial checkout + branch creation 
    828 
    829                        # Make sure HEAD is not detached. 
    830                        mrepo.head.set_reference( 
    831                            local_branch, 
    832                            logmsg="submodule: attaching head to %s" % local_branch, 
    833                        ) 
    834                        mrepo.head.reference.set_tracking_branch(remote_branch) 
    835                    except (IndexError, InvalidGitRepositoryError): 
    836                        _logger.warning("Failed to checkout tracking branch %s", self.branch_path) 
    837                    # END handle tracking branch 
    838 
    839                    # NOTE: Have to write the repo config file as well, otherwise the 
    840                    # default implementation will be offended and not update the 
    841                    # repository. Maybe this is a good way to ensure it doesn't get into 
    842                    # our way, but we want to stay backwards compatible too... It's so 
    843                    # redundant! 
    844                    with self.repo.config_writer() as writer: 
    845                        writer.set_value(sm_section(self.name), "url", self.url) 
    846                # END handle dry_run 
    847            # END handle initialization 
    848 
    849            # DETERMINE SHAS TO CHECK OUT 
    850            ############################# 
    851            binsha = self.binsha 
    852            hexsha = self.hexsha 
    853            if mrepo is not None: 
    854                # mrepo is only set if we are not in dry-run mode or if the module 
    855                # existed. 
    856                is_detached = mrepo.head.is_detached 
    857            # END handle dry_run 
    858 
    859            if mrepo is not None and to_latest_revision: 
    860                msg_base = "Cannot update to latest revision in repository at %r as " % mrepo.working_dir 
    861                if not is_detached: 
    862                    rref = mrepo.head.reference.tracking_branch() 
    863                    if rref is not None: 
    864                        rcommit = rref.commit 
    865                        binsha = rcommit.binsha 
    866                        hexsha = rcommit.hexsha 
    867                    else: 
    868                        _logger.error( 
    869                            "%s a tracking branch was not set for local branch '%s'", 
    870                            msg_base, 
    871                            mrepo.head.reference, 
    872                        ) 
    873                    # END handle remote ref 
    874                else: 
    875                    _logger.error("%s there was no local tracking branch", msg_base) 
    876                # END handle detached head 
    877            # END handle to_latest_revision option 
    878 
    879            # Update the working tree. 
    880            # Handles dry_run. 
    881            if mrepo is not None and mrepo.head.commit.binsha != binsha: 
    882                # We must ensure that our destination sha (the one to point to) is in 
    883                # the future of our current head. Otherwise, we will reset changes that 
    884                # might have been done on the submodule, but were not yet pushed. We 
    885                # also handle the case that history has been rewritten, leaving no 
    886                # merge-base. In that case we behave conservatively, protecting possible 
    887                # changes the user had done. 
    888                may_reset = True 
    889                if mrepo.head.commit.binsha != self.NULL_BIN_SHA: 
    890                    base_commit = mrepo.merge_base(mrepo.head.commit, hexsha) 
    891                    if len(base_commit) == 0 or (base_commit[0] is not None and base_commit[0].hexsha == hexsha): 
    892                        if force: 
    893                            msg = "Will force checkout or reset on local branch that is possibly in the future of" 
    894                            msg += " the commit it will be checked out to, effectively 'forgetting' new commits" 
    895                            _logger.debug(msg) 
    896                        else: 
    897                            msg = "Skipping %s on branch '%s' of submodule repo '%s' as it contains un-pushed commits" 
    898                            msg %= ( 
    899                                is_detached and "checkout" or "reset", 
    900                                mrepo.head, 
    901                                mrepo, 
    902                            ) 
    903                            _logger.info(msg) 
    904                            may_reset = False 
    905                        # END handle force 
    906                    # END handle if we are in the future 
    907 
    908                    if may_reset and not force and mrepo.is_dirty(index=True, working_tree=True, untracked_files=True): 
    909                        raise RepositoryDirtyError(mrepo, "Cannot reset a dirty repository") 
    910                    # END handle force and dirty state 
    911                # END handle empty repo 
    912 
    913                # END verify future/past 
    914                progress.update( 
    915                    BEGIN | UPDWKTREE, 
    916                    0, 
    917                    1, 
    918                    prefix 
    919                    + "Updating working tree at %s for submodule %r to revision %s" % (self.path, self.name, hexsha), 
    920                ) 
    921 
    922                if not dry_run and may_reset: 
    923                    if is_detached: 
    924                        # NOTE: For now we force. The user is not supposed to change 
    925                        # detached submodules anyway. Maybe at some point this becomes 
    926                        # an option, to properly handle user modifications - see below 
    927                        # for future options regarding rebase and merge. 
    928                        mrepo.git.checkout(hexsha, force=force) 
    929                    else: 
    930                        mrepo.head.reset(hexsha, index=True, working_tree=True) 
    931                    # END handle checkout 
    932                # If we may reset/checkout. 
    933                progress.update( 
    934                    END | UPDWKTREE, 
    935                    0, 
    936                    1, 
    937                    prefix + "Done updating working tree for submodule %r" % self.name, 
    938                ) 
    939            # END update to new commit only if needed 
    940        except Exception as err: 
    941            if not keep_going: 
    942                raise 
    943            _logger.error(str(err)) 
    944        # END handle keep_going 
    945 
    946        # HANDLE RECURSION 
    947        ################## 
    948        if recursive: 
    949            # In dry_run mode, the module might not exist. 
    950            if mrepo is not None: 
    951                for submodule in self.iter_items(self.module()): 
    952                    submodule.update( 
    953                        recursive, 
    954                        init, 
    955                        to_latest_revision, 
    956                        progress=progress, 
    957                        dry_run=dry_run, 
    958                        force=force, 
    959                        keep_going=keep_going, 
    960                    ) 
    961                # END handle recursive update 
    962            # END handle dry run 
    963        # END for each submodule 
    964 
    965        return self 
    966 
    967    @unbare_repo 
    968    def move(self, module_path: PathLike, configuration: bool = True, module: bool = True) -> "Submodule": 
    969        """Move the submodule to a another module path. This involves physically moving 
    970        the repository at our current path, changing the configuration, as well as 
    971        adjusting our index entry accordingly. 
    972 
    973        :param module_path: 
    974            The path to which to move our module in the parent repository's working 
    975            tree, given as repository-relative or absolute path. Intermediate 
    976            directories will be created accordingly. If the path already exists, it must 
    977            be empty. Trailing (back)slashes are removed automatically. 
    978 
    979        :param configuration: 
    980            If ``True``, the configuration will be adjusted to let the submodule point 
    981            to the given path. 
    982 
    983        :param module: 
    984            If ``True``, the repository managed by this submodule will be moved as well. 
    985            If ``False``, we don't move the submodule's checkout, which may leave the 
    986            parent repository in an inconsistent state. 
    987 
    988        :return: 
    989            self 
    990 
    991        :raise ValueError: 
    992            If the module path existed and was not empty, or was a file. 
    993 
    994        :note: 
    995            Currently the method is not atomic, and it could leave the repository in an 
    996            inconsistent state if a sub-step fails for some reason. 
    997        """ 
    998        if module + configuration < 1: 
    999            raise ValueError("You must specify to move at least the module or the configuration of the submodule") 
    1000        # END handle input 
    1001 
    1002        module_checkout_path = self._to_relative_path(self.repo, module_path) 
    1003 
    1004        # VERIFY DESTINATION 
    1005        if module_checkout_path == self.path: 
    1006            return self 
    1007        # END handle no change 
    1008 
    1009        module_checkout_abspath = join_path_native(str(self.repo.working_tree_dir), module_checkout_path) 
    1010        if osp.isfile(module_checkout_abspath): 
    1011            raise ValueError("Cannot move repository onto a file: %s" % module_checkout_abspath) 
    1012        # END handle target files 
    1013 
    1014        index = self.repo.index 
    1015        tekey = index.entry_key(module_checkout_path, 0) 
    1016        # if the target item already exists, fail 
    1017        if configuration and tekey in index.entries: 
    1018            raise ValueError("Index entry for target path did already exist") 
    1019        # END handle index key already there 
    1020 
    1021        # Remove existing destination. 
    1022        if module: 
    1023            if osp.exists(module_checkout_abspath): 
    1024                if len(os.listdir(module_checkout_abspath)): 
    1025                    raise ValueError("Destination module directory was not empty") 
    1026                # END handle non-emptiness 
    1027 
    1028                if osp.islink(module_checkout_abspath): 
    1029                    os.remove(module_checkout_abspath) 
    1030                else: 
    1031                    os.rmdir(module_checkout_abspath) 
    1032                # END handle link 
    1033            else: 
    1034                # Recreate parent directories. 
    1035                # NOTE: renames() does that now. 
    1036                pass 
    1037            # END handle existence 
    1038        # END handle module 
    1039 
    1040        # Move the module into place if possible. 
    1041        cur_path = self.abspath 
    1042        renamed_module = False 
    1043        if module and osp.exists(cur_path): 
    1044            os.renames(cur_path, module_checkout_abspath) 
    1045            renamed_module = True 
    1046 
    1047            if osp.isfile(osp.join(module_checkout_abspath, ".git")): 
    1048                module_abspath = self._module_abspath(self.repo, self.path, self.name) 
    1049                self._write_git_file_and_module_config(module_checkout_abspath, module_abspath) 
    1050            # END handle git file rewrite 
    1051        # END move physical module 
    1052 
    1053        # Rename the index entry - we have to manipulate the index directly as git-mv 
    1054        # cannot be used on submodules... yeah. 
    1055        previous_sm_path = self.path 
    1056        try: 
    1057            if configuration: 
    1058                try: 
    1059                    ekey = index.entry_key(self.path, 0) 
    1060                    entry = index.entries[ekey] 
    1061                    del index.entries[ekey] 
    1062                    nentry = git.IndexEntry(entry[:3] + (module_checkout_path,) + entry[4:]) 
    1063                    index.entries[tekey] = nentry 
    1064                except KeyError as e: 
    1065                    raise InvalidGitRepositoryError("Submodule's entry at %r did not exist" % (self.path)) from e 
    1066                # END handle submodule doesn't exist 
    1067 
    1068                # Update configuration. 
    1069                with self.config_writer(index=index) as writer:  # Auto-write. 
    1070                    writer.set_value("path", module_checkout_path) 
    1071                    self.path = module_checkout_path 
    1072            # END handle configuration flag 
    1073        except Exception: 
    1074            if renamed_module: 
    1075                os.renames(module_checkout_abspath, cur_path) 
    1076            # END undo module renaming 
    1077            raise 
    1078        # END handle undo rename 
    1079 
    1080        # Auto-rename submodule if its name was 'default', that is, the checkout 
    1081        # directory. 
    1082        if previous_sm_path == self.name: 
    1083            self.rename(module_checkout_path) 
    1084 
    1085        return self 
    1086 
    1087    @unbare_repo 
    1088    def remove( 
    1089        self, 
    1090        module: bool = True, 
    1091        force: bool = False, 
    1092        configuration: bool = True, 
    1093        dry_run: bool = False, 
    1094    ) -> "Submodule": 
    1095        """Remove this submodule from the repository. This will remove our entry 
    1096        from the ``.gitmodules`` file and the entry in the ``.git/config`` file. 
    1097 
    1098        :param module: 
    1099            If ``True``, the checked out module we point to will be deleted as well. If 
    1100            that module is currently on a commit outside any branch in the remote, or if 
    1101            it is ahead of its tracking branch, or if there are modified or untracked 
    1102            files in its working tree, then the removal will fail. In case the removal 
    1103            of the repository fails for these reasons, the submodule status will not 
    1104            have been altered. 
    1105 
    1106            If this submodule has child modules of its own, these will be deleted prior 
    1107            to touching the direct submodule. 
    1108 
    1109        :param force: 
    1110            Enforces the deletion of the module even though it contains modifications. 
    1111            This basically enforces a brute-force file system based deletion. 
    1112 
    1113        :param configuration: 
    1114            If ``True``, the submodule is deleted from the configuration, otherwise it 
    1115            isn't. Although this should be enabled most of the time, this flag enables 
    1116            you to safely delete the repository of your submodule. 
    1117 
    1118        :param dry_run: 
    1119            If ``True``, we will not actually do anything, but throw the errors we would 
    1120            usually throw. 
    1121 
    1122        :return: 
    1123            self 
    1124 
    1125        :note: 
    1126            Doesn't work in bare repositories. 
    1127 
    1128        :note: 
    1129            Doesn't work atomically, as failure to remove any part of the submodule will 
    1130            leave an inconsistent state. 
    1131 
    1132        :raise git.exc.InvalidGitRepositoryError: 
    1133            Thrown if the repository cannot be deleted. 
    1134 
    1135        :raise OSError: 
    1136            If directories or files could not be removed. 
    1137        """ 
    1138        if not (module or configuration): 
    1139            raise ValueError("Need to specify to delete at least the module, or the configuration") 
    1140        # END handle parameters 
    1141 
    1142        # Recursively remove children of this submodule. 
    1143        nc = 0 
    1144        for csm in self.children(): 
    1145            nc += 1 
    1146            csm.remove(module, force, configuration, dry_run) 
    1147            del csm 
    1148 
    1149        if configuration and not dry_run and nc > 0: 
    1150            # Ensure we don't leave the parent repository in a dirty state, and commit 
    1151            # our changes. It's important for recursive, unforced, deletions to work as 
    1152            # expected. 
    1153            self.module().index.commit("Removed at least one of child-modules of '%s'" % self.name) 
    1154        # END handle recursion 
    1155 
    1156        # DELETE REPOSITORY WORKING TREE 
    1157        ################################ 
    1158        if module and self.module_exists(): 
    1159            mod = self.module() 
    1160            git_dir = mod.git_dir 
    1161            if force: 
    1162                # Take the fast lane and just delete everything in our module path. 
    1163                # TODO: If we run into permission problems, we have a highly 
    1164                # inconsistent state. Delete the .git folders last, start with the 
    1165                # submodules first. 
    1166                mp = self.abspath 
    1167                method: Union[None, Callable[[PathLike], None]] = None 
    1168                if osp.islink(mp): 
    1169                    method = os.remove 
    1170                elif osp.isdir(mp): 
    1171                    method = rmtree 
    1172                elif osp.exists(mp): 
    1173                    raise AssertionError("Cannot forcibly delete repository as it was neither a link, nor a directory") 
    1174                # END handle brutal deletion 
    1175                if not dry_run: 
    1176                    assert method 
    1177                    method(mp) 
    1178                # END apply deletion method 
    1179            else: 
    1180                # Verify we may delete our module. 
    1181                if mod.is_dirty(index=True, working_tree=True, untracked_files=True): 
    1182                    raise InvalidGitRepositoryError( 
    1183                        "Cannot delete module at %s with any modifications, unless force is specified" 
    1184                        % mod.working_tree_dir 
    1185                    ) 
    1186                # END check for dirt 
    1187 
    1188                # Figure out whether we have new commits compared to the remotes. 
    1189                # NOTE: If the user pulled all the time, the remote heads might not have 
    1190                # been updated, so commits coming from the remote look as if they come 
    1191                # from us. But we stay strictly read-only and don't fetch beforehand. 
    1192                for remote in mod.remotes: 
    1193                    num_branches_with_new_commits = 0 
    1194                    rrefs = remote.refs 
    1195                    for rref in rrefs: 
    1196                        num_branches_with_new_commits += len(mod.git.cherry(rref)) != 0 
    1197                    # END for each remote ref 
    1198                    # Not a single remote branch contained all our commits. 
    1199                    if len(rrefs) and num_branches_with_new_commits == len(rrefs): 
    1200                        raise InvalidGitRepositoryError( 
    1201                            "Cannot delete module at %s as there are new commits" % mod.working_tree_dir 
    1202                        ) 
    1203                    # END handle new commits 
    1204                    # We have to manually delete some references to allow resources to 
    1205                    # be cleaned up immediately when we are done with them, because 
    1206                    # Python's scoping is no more granular than the whole function (loop 
    1207                    # bodies are not scopes). When the objects stay alive longer, they 
    1208                    # can keep handles open. On Windows, this is a problem. 
    1209                    if len(rrefs): 
    1210                        del rref  # skipcq: PYL-W0631 
    1211                    # END handle remotes 
    1212                    del rrefs 
    1213                    del remote 
    1214                # END for each remote 
    1215 
    1216                # Finally delete our own submodule. 
    1217                if not dry_run: 
    1218                    self._clear_cache() 
    1219                    wtd = mod.working_tree_dir 
    1220                    del mod  # Release file-handles (Windows). 
    1221                    gc.collect() 
    1222                    rmtree(str(wtd)) 
    1223                # END delete tree if possible 
    1224            # END handle force 
    1225 
    1226            if not dry_run and osp.isdir(git_dir): 
    1227                self._clear_cache() 
    1228                rmtree(git_dir) 
    1229            # END handle separate bare repository 
    1230        # END handle module deletion 
    1231 
    1232        # Void our data so as not to delay invalid access. 
    1233        if not dry_run: 
    1234            self._clear_cache() 
    1235 
    1236        # DELETE CONFIGURATION 
    1237        ###################### 
    1238        if configuration and not dry_run: 
    1239            # First the index-entry. 
    1240            parent_index = self.repo.index 
    1241            try: 
    1242                del parent_index.entries[parent_index.entry_key(self.path, 0)] 
    1243            except KeyError: 
    1244                pass 
    1245            # END delete entry 
    1246            parent_index.write() 
    1247 
    1248            # Now git config - we need the config intact, otherwise we can't query 
    1249            # information anymore. 
    1250 
    1251            with self.repo.config_writer() as gcp_writer: 
    1252                gcp_writer.remove_section(sm_section(self.name)) 
    1253 
    1254            with self.config_writer() as sc_writer: 
    1255                sc_writer.remove_section() 
    1256        # END delete configuration 
    1257 
    1258        return self 
    1259 
    1260    def set_parent_commit(self, commit: Union[Commit_ish, str, None], check: bool = True) -> "Submodule": 
    1261        """Set this instance to use the given commit whose tree is supposed to 
    1262        contain the ``.gitmodules`` blob. 
    1263 
    1264        :param commit: 
    1265            Commit-ish reference pointing at the root tree, or ``None`` to always point 
    1266            to the most recent commit. 
    1267 
    1268        :param check: 
    1269            If ``True``, relatively expensive checks will be performed to verify 
    1270            validity of the submodule. 
    1271 
    1272        :raise ValueError: 
    1273            If the commit's tree didn't contain the ``.gitmodules`` blob. 
    1274 
    1275        :raise ValueError: 
    1276            If the parent commit didn't store this submodule under the current path. 
    1277 
    1278        :return: 
    1279            self 
    1280        """ 
    1281        if commit is None: 
    1282            self._parent_commit = None 
    1283            return self 
    1284        # END handle None 
    1285        pcommit = self.repo.commit(commit) 
    1286        pctree = pcommit.tree 
    1287        if self.k_modules_file not in pctree: 
    1288            raise ValueError("Tree of commit %s did not contain the %s file" % (commit, self.k_modules_file)) 
    1289        # END handle exceptions 
    1290 
    1291        prev_pc = self._parent_commit 
    1292        self._parent_commit = pcommit 
    1293 
    1294        if check: 
    1295            parser = self._config_parser(self.repo, self._parent_commit, read_only=True) 
    1296            if not parser.has_section(sm_section(self.name)): 
    1297                self._parent_commit = prev_pc 
    1298                raise ValueError("Submodule at path %r did not exist in parent commit %s" % (self.path, commit)) 
    1299            # END handle submodule did not exist 
    1300        # END handle checking mode 
    1301 
    1302        # Update our sha, it could have changed. 
    1303        # If check is False, we might see a parent-commit that doesn't even contain the 
    1304        # submodule anymore. in that case, mark our sha as being NULL. 
    1305        try: 
    1306            self.binsha = pctree[str(self.path)].binsha 
    1307        except KeyError: 
    1308            self.binsha = self.NULL_BIN_SHA 
    1309 
    1310        self._clear_cache() 
    1311        return self 
    1312 
    1313    @unbare_repo 
    1314    def config_writer( 
    1315        self, index: Union["IndexFile", None] = None, write: bool = True 
    1316    ) -> SectionConstraint["SubmoduleConfigParser"]: 
    1317        """ 
    1318        :return: 
    1319            A config writer instance allowing you to read and write the data belonging 
    1320            to this submodule into the ``.gitmodules`` file. 
    1321 
    1322        :param index: 
    1323            If not ``None``, an :class:`~git.index.base.IndexFile` instance which should 
    1324            be written. Defaults to the index of the :class:`Submodule`'s parent 
    1325            repository. 
    1326 
    1327        :param write: 
    1328            If ``True``, the index will be written each time a configuration value changes. 
    1329 
    1330        :note: 
    1331            The parameters allow for a more efficient writing of the index, as you can 
    1332            pass in a modified index on your own, prevent automatic writing, and write 
    1333            yourself once the whole operation is complete. 
    1334 
    1335        :raise ValueError: 
    1336            If trying to get a writer on a parent_commit which does not match the 
    1337            current head commit. 
    1338 
    1339        :raise IOError: 
    1340            If the ``.gitmodules`` file/blob could not be read. 
    1341        """ 
    1342        writer = self._config_parser_constrained(read_only=False) 
    1343        if index is not None: 
    1344            writer.config._index = index 
    1345        writer.config._auto_write = write 
    1346        return writer 
    1347 
    1348    @unbare_repo 
    1349    def rename(self, new_name: str) -> "Submodule": 
    1350        """Rename this submodule. 
    1351 
    1352        :note: 
    1353            This method takes care of renaming the submodule in various places, such as: 
    1354 
    1355            * ``$parent_git_dir / config`` 
    1356            * ``$working_tree_dir / .gitmodules`` 
    1357            * (git >= v1.8.0: move submodule repository to new name) 
    1358 
    1359        As ``.gitmodules`` will be changed, you would need to make a commit afterwards. 
    1360        The changed ``.gitmodules`` file will already be added to the index. 
    1361 
    1362        :return: 
    1363            This :class:`Submodule` instance 
    1364        """ 
    1365        if self.name == new_name: 
    1366            return self 
    1367 
    1368        # .git/config 
    1369        with self.repo.config_writer() as pw: 
    1370            # As we ourselves didn't write anything about submodules into the parent 
    1371            # .git/config, we will not require it to exist, and just ignore missing 
    1372            # entries. 
    1373            if pw.has_section(sm_section(self.name)): 
    1374                pw.rename_section(sm_section(self.name), sm_section(new_name)) 
    1375 
    1376        # .gitmodules 
    1377        with self.config_writer(write=True).config as cw: 
    1378            cw.rename_section(sm_section(self.name), sm_section(new_name)) 
    1379 
    1380        self._name = new_name 
    1381 
    1382        # .git/modules 
    1383        mod = self.module() 
    1384        if mod.has_separate_working_tree(): 
    1385            destination_module_abspath = self._module_abspath(self.repo, self.path, new_name) 
    1386            source_dir = mod.git_dir 
    1387            # Let's be sure the submodule name is not so obviously tied to a directory. 
    1388            if str(destination_module_abspath).startswith(str(mod.git_dir)): 
    1389                tmp_dir = self._module_abspath(self.repo, self.path, str(uuid.uuid4())) 
    1390                os.renames(source_dir, tmp_dir) 
    1391                source_dir = tmp_dir 
    1392            # END handle self-containment 
    1393            os.renames(source_dir, destination_module_abspath) 
    1394            if mod.working_tree_dir: 
    1395                self._write_git_file_and_module_config(mod.working_tree_dir, destination_module_abspath) 
    1396        # END move separate git repository 
    1397 
    1398        return self 
    1399 
    1400    # } END edit interface 
    1401 
    1402    # { Query Interface 
    1403 
    1404    @unbare_repo 
    1405    def module(self) -> "Repo": 
    1406        """ 
    1407        :return: 
    1408            :class:`~git.repo.base.Repo` instance initialized from the repository at our 
    1409            submodule path 
    1410 
    1411        :raise git.exc.InvalidGitRepositoryError: 
    1412            If a repository was not available. 
    1413            This could also mean that it was not yet initialized. 
    1414        """ 
    1415        module_checkout_abspath = self.abspath 
    1416        try: 
    1417            repo = git.Repo(module_checkout_abspath) 
    1418            if repo != self.repo: 
    1419                return repo 
    1420            # END handle repo uninitialized 
    1421        except (InvalidGitRepositoryError, NoSuchPathError) as e: 
    1422            raise InvalidGitRepositoryError("No valid repository at %s" % module_checkout_abspath) from e 
    1423        else: 
    1424            raise InvalidGitRepositoryError("Repository at %r was not yet checked out" % module_checkout_abspath) 
    1425        # END handle exceptions 
    1426 
    1427    def module_exists(self) -> bool: 
    1428        """ 
    1429        :return: 
    1430            ``True`` if our module exists and is a valid git repository. 
    1431            See the :meth:`module` method. 
    1432        """ 
    1433        try: 
    1434            self.module() 
    1435            return True 
    1436        except Exception: 
    1437            return False 
    1438        # END handle exception 
    1439 
    1440    def exists(self) -> bool: 
    1441        """ 
    1442        :return: 
    1443            ``True`` if the submodule exists, ``False`` otherwise. 
    1444            Please note that a submodule may exist (in the ``.gitmodules`` file) even 
    1445            though its module doesn't exist on disk. 
    1446        """ 
    1447        # Keep attributes for later, and restore them if we have no valid data. 
    1448        # This way we do not actually alter the state of the object. 
    1449        loc = locals() 
    1450        for attr in self._cache_attrs: 
    1451            try: 
    1452                if hasattr(self, attr): 
    1453                    loc[attr] = getattr(self, attr) 
    1454                # END if we have the attribute cache 
    1455            except (cp.NoSectionError, ValueError): 
    1456                # On PY3, this can happen apparently... don't know why this doesn't 
    1457                # happen on PY2. 
    1458                pass 
    1459        # END for each attr 
    1460        self._clear_cache() 
    1461 
    1462        try: 
    1463            try: 
    1464                self.path  # noqa: B018 
    1465                return True 
    1466            except Exception: 
    1467                return False 
    1468            # END handle exceptions 
    1469        finally: 
    1470            for attr in self._cache_attrs: 
    1471                if attr in loc: 
    1472                    setattr(self, attr, loc[attr]) 
    1473                # END if we have a cache 
    1474            # END reapply each attribute 
    1475        # END handle object state consistency 
    1476 
    1477    @property 
    1478    def branch(self) -> "Head": 
    1479        """ 
    1480        :return: 
    1481            The branch instance that we are to checkout 
    1482 
    1483        :raise git.exc.InvalidGitRepositoryError: 
    1484            If our module is not yet checked out. 
    1485        """ 
    1486        return mkhead(self.module(), self._branch_path) 
    1487 
    1488    @property 
    1489    def branch_path(self) -> PathLike: 
    1490        """ 
    1491        :return: 
    1492            Full repository-relative path as string to the branch we would checkout from 
    1493            the remote and track 
    1494        """ 
    1495        return self._branch_path 
    1496 
    1497    @property 
    1498    def branch_name(self) -> str: 
    1499        """ 
    1500        :return: 
    1501            The name of the branch, which is the shortest possible branch name 
    1502        """ 
    1503        # Use an instance method, for this we create a temporary Head instance which 
    1504        # uses a repository that is available at least (it makes no difference). 
    1505        return git.Head(self.repo, self._branch_path).name 
    1506 
    1507    @property 
    1508    def url(self) -> str: 
    1509        """:return: The url to the repository our submodule's repository refers to""" 
    1510        return self._url 
    1511 
    1512    @property 
    1513    def parent_commit(self) -> "Commit": 
    1514        """ 
    1515        :return: 
    1516            :class:`~git.objects.commit.Commit` instance with the tree containing the 
    1517            ``.gitmodules`` file 
    1518 
    1519        :note: 
    1520            Will always point to the current head's commit if it was not set explicitly. 
    1521        """ 
    1522        if self._parent_commit is None: 
    1523            return self.repo.commit() 
    1524        return self._parent_commit 
    1525 
    1526    @property 
    1527    def name(self) -> str: 
    1528        """ 
    1529        :return: 
    1530            The name of this submodule. It is used to identify it within the 
    1531            ``.gitmodules`` file. 
    1532 
    1533        :note: 
    1534            By default, this is the name is the path at which to find the submodule, but 
    1535            in GitPython it should be a unique identifier similar to the identifiers 
    1536            used for remotes, which allows to change the path of the submodule easily. 
    1537        """ 
    1538        return self._name 
    1539 
    1540    def config_reader(self) -> SectionConstraint[SubmoduleConfigParser]: 
    1541        """ 
    1542        :return: 
    1543            ConfigReader instance which allows you to query the configuration values of 
    1544            this submodule, as provided by the ``.gitmodules`` file. 
    1545 
    1546        :note: 
    1547            The config reader will actually read the data directly from the repository 
    1548            and thus does not need nor care about your working tree. 
    1549 
    1550        :note: 
    1551            Should be cached by the caller and only kept as long as needed. 
    1552 
    1553        :raise IOError: 
    1554            If the ``.gitmodules`` file/blob could not be read. 
    1555        """ 
    1556        return self._config_parser_constrained(read_only=True) 
    1557 
    1558    def children(self) -> IterableList["Submodule"]: 
    1559        """ 
    1560        :return: 
    1561            IterableList(Submodule, ...) An iterable list of :class:`Submodule` 
    1562            instances which are children of this submodule or 0 if the submodule is not 
    1563            checked out. 
    1564        """ 
    1565        return self._get_intermediate_items(self) 
    1566 
    1567    # } END query interface 
    1568 
    1569    # { Iterable Interface 
    1570 
    1571    @classmethod 
    1572    def iter_items( 
    1573        cls, 
    1574        repo: "Repo", 
    1575        parent_commit: Union[Commit_ish, str] = "HEAD", 
    1576        *args: Any, 
    1577        **kwargs: Any, 
    1578    ) -> Iterator["Submodule"]: 
    1579        """ 
    1580        :return: 
    1581            Iterator yielding :class:`Submodule` instances available in the given 
    1582            repository 
    1583        """ 
    1584        try: 
    1585            pc = repo.commit(parent_commit)  # Parent commit instance 
    1586            parser = cls._config_parser(repo, pc, read_only=True) 
    1587        except (IOError, BadName): 
    1588            return 
    1589        # END handle empty iterator 
    1590 
    1591        for sms in parser.sections(): 
    1592            n = sm_name(sms) 
    1593            p = parser.get(sms, "path") 
    1594            u = parser.get(sms, "url") 
    1595            b = cls.k_head_default 
    1596            if parser.has_option(sms, cls.k_head_option): 
    1597                b = str(parser.get(sms, cls.k_head_option)) 
    1598            # END handle optional information 
    1599 
    1600            # Get the binsha. 
    1601            index = repo.index 
    1602            try: 
    1603                rt = pc.tree  # Root tree 
    1604                sm = rt[p] 
    1605            except KeyError: 
    1606                # Try the index, maybe it was just added. 
    1607                try: 
    1608                    entry = index.entries[index.entry_key(p, 0)] 
    1609                    sm = Submodule(repo, entry.binsha, entry.mode, entry.path) 
    1610                except KeyError: 
    1611                    # The submodule doesn't exist, probably it wasn't removed from the 
    1612                    # .gitmodules file. 
    1613                    continue 
    1614                # END handle keyerror 
    1615            # END handle critical error 
    1616 
    1617            # Make sure we are looking at a submodule object. 
    1618            if type(sm) is not git.objects.submodule.base.Submodule: 
    1619                continue 
    1620 
    1621            # Fill in remaining info - saves time as it doesn't have to be parsed again. 
    1622            sm._name = n 
    1623            if pc != repo.commit(): 
    1624                sm._parent_commit = pc 
    1625            # END set only if not most recent! 
    1626            sm._branch_path = git.Head.to_full_path(b) 
    1627            sm._url = u 
    1628 
    1629            yield sm 
    1630        # END for each section 
    1631 
    1632    # } END iterable interface