Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/git/objects/submodule/util.py: 47%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

104 statements  

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 "is_relative_url", 

10 "resolve_relative_url", 

11 "SubmoduleConfigParser", 

12] 

13 

14from io import BytesIO 

15import sys 

16import weakref 

17 

18import git 

19from git.config import GitConfigParser 

20from git.exc import InvalidGitRepositoryError 

21 

22# typing ----------------------------------------------------------------------- 

23 

24from typing import Any, Sequence, TYPE_CHECKING, Union 

25 

26from git.types import PathLike 

27 

28if TYPE_CHECKING: 

29 from weakref import ReferenceType 

30 

31 from git.refs import Head, RemoteReference 

32 from git.remote import Remote 

33 from git.repo import Repo 

34 

35 from .base import Submodule 

36 

37# { Utilities 

38 

39 

40def sm_section(name: str) -> str: 

41 """:return: Section title used in ``.gitmodules`` configuration file""" 

42 return f'submodule "{name}"' 

43 

44 

45def sm_name(section: str) -> str: 

46 """:return: Name of the submodule as parsed from the section name""" 

47 section = section.strip() 

48 return section[11:-1] 

49 

50 

51def mkhead(repo: "Repo", path: PathLike) -> "Head": 

52 """:return: New branch/head instance""" 

53 return git.Head(repo, git.Head.to_full_path(path)) 

54 

55 

56def find_first_remote_branch(remotes: Sequence["Remote"], branch_name: str) -> "RemoteReference": 

57 """Find the remote branch matching the name of the given branch or raise 

58 :exc:`~git.exc.InvalidGitRepositoryError`.""" 

59 for remote in remotes: 

60 try: 

61 return remote.refs[branch_name] 

62 except IndexError: 

63 continue 

64 # END exception handling 

65 # END for remote 

66 raise InvalidGitRepositoryError("Didn't find remote branch '%r' in any of the given remotes" % branch_name) 

67 

68 

69def _is_dir_sep(char: str) -> bool: 

70 return char == "/" or (sys.platform == "win32" and char == "\\") 

71 

72 

73def _starts_with_dot_slash(url: str) -> bool: 

74 return len(url) >= 2 and url[0] == "." and _is_dir_sep(url[1]) 

75 

76 

77def _starts_with_dot_dot_slash(url: str) -> bool: 

78 return len(url) >= 3 and url[0:2] == ".." and _is_dir_sep(url[2]) 

79 

80 

81def _has_dos_drive_prefix(url: str) -> bool: 

82 return sys.platform == "win32" and len(url) >= 2 and url[0].isalpha() and url[1] == ":" 

83 

84 

85def _is_absolute_path(url: str) -> bool: 

86 return (len(url) >= 1 and _is_dir_sep(url[0])) or _has_dos_drive_prefix(url) 

87 

88 

89def _url_is_local_not_ssh(url: str) -> bool: 

90 colon = url.find(":") 

91 slash = url.find("/") 

92 return colon == -1 or (slash != -1 and slash < colon) or _has_dos_drive_prefix(url) 

93 

94 

95def _find_last_dir_sep(url: str) -> int: 

96 for index in range(len(url) - 1, -1, -1): 

97 if _is_dir_sep(url[index]): 

98 return index 

99 return -1 

100 

101 

102def is_relative_url(url: str) -> bool: 

103 """:return: ``True`` if `url` starts with ``./`` or ``../``, which git resolves 

104 against the superproject's remote rather than treating as a path""" 

105 return _starts_with_dot_slash(url) or _starts_with_dot_dot_slash(url) 

106 

107 

108def resolve_relative_url(remote_url: str, url: str) -> str: 

109 """Resolve a submodule URL that starts with ``./`` or ``../`` against the 

110 superproject's remote URL, the way git's ``relative_url`` does. 

111 

112 Git treats a relative submodule URL like a relative directory: each leading 

113 ``../`` removes the last component of `remote_url`, whether that is a path 

114 component, the host of a URL, or the host of an scp-like ``host:path`` address. 

115 The remainder is appended with ``/``, or with ``:`` once an scp-like host was 

116 reached. A URL that is not relative is returned unchanged. 

117 

118 :raise ValueError: 

119 If more components are stripped than `remote_url` has, which git rejects with 

120 "cannot strip one component off url". 

121 """ 

122 if not is_relative_url(url): 

123 return url 

124 

125 remote = remote_url 

126 if remote and _is_dir_sep(remote[-1]): 

127 remote = remote[:-1] 

128 

129 is_relative = _url_is_local_not_ssh(remote) and not _is_absolute_path(remote) 

130 if is_relative and not _starts_with_dot_slash(remote) and not _starts_with_dot_dot_slash(remote): 

131 remote = "./" + remote 

132 

133 colonsep = False 

134 while url: 

135 if _starts_with_dot_dot_slash(url): 

136 url = url[3:] 

137 separator = _find_last_dir_sep(remote) 

138 if separator != -1: 

139 remote = remote[:separator] 

140 elif ":" in remote: 

141 remote = remote[: remote.rfind(":")] 

142 colonsep = True 

143 elif is_relative or remote == ".": 

144 raise ValueError("cannot strip one component off url %r" % remote) 

145 else: 

146 remote = "." 

147 elif _starts_with_dot_slash(url): 

148 url = url[2:] 

149 else: 

150 break 

151 

152 resolved = remote + (":" if colonsep else "/") + url 

153 if url.endswith("/"): 

154 resolved = resolved[:-1] 

155 if _starts_with_dot_slash(resolved): 

156 resolved = resolved[2:] 

157 return resolved 

158 

159 

160# } END utilities 

161 

162# { Classes 

163 

164 

165class SubmoduleConfigParser(GitConfigParser): 

166 """Catches calls to :meth:`~git.config.GitConfigParser.write`, and updates the 

167 ``.gitmodules`` blob in the index with the new data, if we have written into a 

168 stream. 

169 

170 Otherwise it would add the local file to the index to make it correspond with the 

171 working tree. Additionally, the cache must be cleared. 

172 

173 Please note that no mutating method will work in bare mode. 

174 """ 

175 

176 def __init__(self, *args: Any, **kwargs: Any) -> None: 

177 self._smref: Union["ReferenceType[Submodule]", None] = None 

178 self._index = None 

179 self._auto_write = True 

180 super().__init__(*args, **kwargs) 

181 

182 # { Interface 

183 def set_submodule(self, submodule: "Submodule") -> None: 

184 """Set this instance's submodule. It must be called before the first write 

185 operation begins.""" 

186 self._smref = weakref.ref(submodule) 

187 

188 def flush_to_index(self) -> None: 

189 """Flush changes in our configuration file to the index.""" 

190 assert self._smref is not None 

191 # Should always have a file here. 

192 assert not isinstance(self._file_or_files, BytesIO) 

193 

194 sm = self._smref() 

195 if sm is not None: 

196 index = self._index 

197 if index is None: 

198 index = sm.repo.index 

199 # END handle index 

200 index.add([sm.k_modules_file], write=self._auto_write) 

201 sm._clear_cache() 

202 # END handle weakref 

203 

204 # } END interface 

205 

206 # { Overridden Methods 

207 def write(self) -> None: # type: ignore[override] 

208 rval: None = super().write() 

209 self.flush_to_index() 

210 return rval 

211 

212 # END overridden methods 

213 

214 

215# } END classes