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__ = [
5 "sm_section",
6 "sm_name",
7 "mkhead",
8 "find_first_remote_branch",
9 "SubmoduleConfigParser",
10]
11
12from io import BytesIO
13import weakref
14
15import git
16from git.config import GitConfigParser
17from git.exc import InvalidGitRepositoryError
18
19# typing -----------------------------------------------------------------------
20
21from typing import Any, Sequence, TYPE_CHECKING, Union
22
23from git.types import PathLike
24
25if TYPE_CHECKING:
26 from weakref import ReferenceType
27
28 from git.refs import Head, RemoteReference
29 from git.remote import Remote
30 from git.repo import Repo
31
32 from .base import Submodule
33
34# { Utilities
35
36
37def sm_section(name: str) -> str:
38 """:return: Section title used in ``.gitmodules`` configuration file"""
39 return f'submodule "{name}"'
40
41
42def sm_name(section: str) -> str:
43 """:return: Name of the submodule as parsed from the section name"""
44 section = section.strip()
45 return section[11:-1]
46
47
48def mkhead(repo: "Repo", path: PathLike) -> "Head":
49 """:return: New branch/head instance"""
50 return git.Head(repo, git.Head.to_full_path(path))
51
52
53def find_first_remote_branch(remotes: Sequence["Remote"], branch_name: str) -> "RemoteReference":
54 """Find the remote branch matching the name of the given branch or raise
55 :exc:`~git.exc.InvalidGitRepositoryError`."""
56 for remote in remotes:
57 try:
58 return remote.refs[branch_name]
59 except IndexError:
60 continue
61 # END exception handling
62 # END for remote
63 raise InvalidGitRepositoryError("Didn't find remote branch '%r' in any of the given remotes" % branch_name)
64
65
66# } END utilities
67
68# { Classes
69
70
71class SubmoduleConfigParser(GitConfigParser):
72 """Catches calls to :meth:`~git.config.GitConfigParser.write`, and updates the
73 ``.gitmodules`` blob in the index with the new data, if we have written into a
74 stream.
75
76 Otherwise it would add the local file to the index to make it correspond with the
77 working tree. Additionally, the cache must be cleared.
78
79 Please note that no mutating method will work in bare mode.
80 """
81
82 def __init__(self, *args: Any, **kwargs: Any) -> None:
83 self._smref: Union["ReferenceType[Submodule]", None] = None
84 self._index = None
85 self._auto_write = True
86 super().__init__(*args, **kwargs)
87
88 # { Interface
89 def set_submodule(self, submodule: "Submodule") -> None:
90 """Set this instance's submodule. It must be called before the first write
91 operation begins."""
92 self._smref = weakref.ref(submodule)
93
94 def flush_to_index(self) -> None:
95 """Flush changes in our configuration file to the index."""
96 assert self._smref is not None
97 # Should always have a file here.
98 assert not isinstance(self._file_or_files, BytesIO)
99
100 sm = self._smref()
101 if sm is not None:
102 index = self._index
103 if index is None:
104 index = sm.repo.index
105 # END handle index
106 index.add([sm.k_modules_file], write=self._auto_write)
107 sm._clear_cache()
108 # END handle weakref
109
110 # } END interface
111
112 # { Overridden Methods
113 def write(self) -> None: # type: ignore[override]
114 rval: None = super().write()
115 self.flush_to_index()
116 return rval
117
118 # END overridden methods
119
120
121# } END classes