Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/git/refs/head.py: 34%

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

103 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"""Some ref-based objects. 

5 

6Note the distinction between the :class:`HEAD` and :class:`Head` classes. 

7""" 

8 

9__all__ = ["HEAD", "Head"] 

10 

11from git.config import GitConfigParser, SectionConstraint 

12from git.exc import GitCommandError 

13from git.util import join_path 

14 

15from .reference import Reference 

16from .symbolic import SymbolicReference 

17 

18# typing --------------------------------------------------- 

19 

20from typing import Any, Sequence, TYPE_CHECKING, Union 

21 

22from git.cmd import Git 

23from git.types import Commit_ish, PathLike 

24 

25if TYPE_CHECKING: 

26 from git.refs import RemoteReference 

27 from git.repo import Repo 

28 

29# ------------------------------------------------------------------- 

30 

31 

32def strip_quotes(string: str) -> str: 

33 if string.startswith('"') and string.endswith('"'): 

34 return string[1:-1] 

35 return string 

36 

37 

38class HEAD(SymbolicReference): 

39 """Special case of a :class:`~git.refs.symbolic.SymbolicReference` representing the 

40 repository's HEAD reference.""" 

41 

42 _HEAD_NAME = "HEAD" 

43 _ORIG_HEAD_NAME = "ORIG_HEAD" 

44 

45 __slots__ = () 

46 

47 def __init__(self, repo: "Repo", path: PathLike = _HEAD_NAME) -> None: 

48 if path != self._HEAD_NAME: 

49 raise ValueError("HEAD instance must point to %r, got %r" % (self._HEAD_NAME, path)) 

50 super().__init__(repo, path) 

51 

52 def orig_head(self) -> SymbolicReference: 

53 """ 

54 :return: 

55 :class:`~git.refs.symbolic.SymbolicReference` pointing at the ORIG_HEAD, 

56 which is maintained to contain the previous value of HEAD. 

57 """ 

58 return SymbolicReference(self.repo, self._ORIG_HEAD_NAME) 

59 

60 def reset( 

61 self, 

62 commit: Union[Commit_ish, SymbolicReference, str] = "HEAD", 

63 index: bool = True, 

64 working_tree: bool = False, 

65 paths: Union[PathLike, Sequence[PathLike], None] = None, 

66 allow_unsafe_options: bool = False, 

67 **kwargs: Any, 

68 ) -> "HEAD": 

69 """Reset our HEAD to the given commit optionally synchronizing the index and 

70 working tree. The reference we refer to will be set to commit as well. 

71 

72 :param commit: 

73 :class:`~git.objects.commit.Commit`, :class:`~git.refs.reference.Reference`, 

74 or string identifying a revision we should reset HEAD to. 

75 

76 :param index: 

77 If ``True``, the index will be set to match the given commit. 

78 Otherwise it will not be touched. 

79 

80 :param working_tree: 

81 If ``True``, the working tree will be forcefully adjusted to match the given 

82 commit, possibly overwriting uncommitted changes without warning. 

83 If `working_tree` is ``True``, `index` must be ``True`` as well. 

84 

85 :param paths: 

86 Single path or list of paths relative to the git root directory 

87 that are to be reset. This allows to partially reset individual files. 

88 

89 :param allow_unsafe_options: 

90 Allow unsafe options such as ``--pathspec-from-file`` to be passed to 

91 :manpage:`git-reset(1)`. 

92 

93 :param kwargs: 

94 Additional arguments passed to :manpage:`git-reset(1)`. 

95 

96 :return: 

97 self 

98 """ 

99 if not allow_unsafe_options: 

100 Git.check_unsafe_options( 

101 options=Git._option_candidates([commit], kwargs), 

102 unsafe_options=Git.unsafe_git_pathspec_from_file_options, 

103 ) 

104 mode: Union[str, None] 

105 mode = "--soft" 

106 if index: 

107 mode = "--mixed" 

108 

109 # Explicit "--mixed" when passing paths is deprecated since git 1.5.4. 

110 # See https://github.com/gitpython-developers/GitPython/discussions/1876. 

111 if paths: 

112 mode = None 

113 # END special case 

114 # END handle index 

115 

116 if working_tree: 

117 mode = "--hard" 

118 if not index: 

119 raise ValueError("Cannot reset the working tree if the index is not reset as well") 

120 

121 # END working tree handling 

122 

123 try: 

124 self.repo.git.reset(mode, commit, "--", paths, **kwargs) 

125 except GitCommandError as e: 

126 # git nowadays may use 1 as status to indicate there are still unstaged 

127 # modifications after the reset. 

128 if e.status != 1: 

129 raise 

130 # END handle exception 

131 

132 return self 

133 

134 

135class Head(Reference): 

136 """A Head is a named reference to a :class:`~git.objects.commit.Commit`. Every Head 

137 instance contains a name and a :class:`~git.objects.commit.Commit` object. 

138 

139 Examples:: 

140 

141 >>> repo = Repo("/path/to/repo") 

142 >>> head = repo.heads[0] 

143 

144 >>> head.name 

145 'master' 

146 

147 >>> head.commit 

148 <git.Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455"> 

149 

150 >>> head.commit.hexsha 

151 '1c09f116cbc2cb4100fb6935bb162daa4723f455' 

152 """ 

153 

154 _common_path_default = "refs/heads" 

155 k_config_remote = "remote" 

156 k_config_remote_ref = "merge" # Branch to merge from remote. 

157 

158 @classmethod 

159 def delete(cls, repo: "Repo", *heads: "Union[Head, str]", force: bool = False, **kwargs: Any) -> None: # type: ignore[override] 

160 """Delete the given heads. 

161 

162 :param force: 

163 If ``True``, the heads will be deleted even if they are not yet merged into 

164 the main development stream. Default ``False``. 

165 """ 

166 flag = "-d" 

167 if force: 

168 flag = "-D" 

169 repo.git.branch(flag, *heads) 

170 

171 def set_tracking_branch(self, remote_reference: Union["RemoteReference", None]) -> "Head": 

172 """Configure this branch to track the given remote reference. This will 

173 alter this branch's configuration accordingly. 

174 

175 :param remote_reference: 

176 The remote reference to track or None to untrack any references. 

177 

178 :return: 

179 self 

180 """ 

181 from .remote import RemoteReference 

182 

183 if remote_reference is not None and not isinstance(remote_reference, RemoteReference): 

184 raise ValueError("Incorrect parameter type: %r" % remote_reference) 

185 # END handle type 

186 

187 with self.config_writer() as writer: 

188 if remote_reference is None: 

189 writer.remove_option(self.k_config_remote) 

190 writer.remove_option(self.k_config_remote_ref) 

191 if len(writer.options()) == 0: 

192 writer.remove_section() 

193 else: 

194 writer.set_value(self.k_config_remote, remote_reference.remote_name) 

195 writer.set_value( 

196 self.k_config_remote_ref, 

197 Head.to_full_path(remote_reference.remote_head), 

198 ) 

199 

200 return self 

201 

202 def tracking_branch(self) -> Union["RemoteReference", None]: 

203 """ 

204 :return: 

205 The remote reference we are tracking, or ``None`` if we are not a tracking 

206 branch. 

207 """ 

208 from .remote import RemoteReference 

209 

210 reader = self.config_reader() 

211 if reader.has_option(self.k_config_remote) and reader.has_option(self.k_config_remote_ref): 

212 ref = Head( 

213 self.repo, 

214 Head.to_full_path(strip_quotes(reader.get_value(self.k_config_remote_ref))), 

215 ) 

216 remote_refpath = RemoteReference.to_full_path(join_path(reader.get_value(self.k_config_remote), ref.name)) 

217 return RemoteReference(self.repo, remote_refpath) 

218 # END handle have tracking branch 

219 

220 # We are not a tracking branch. 

221 return None 

222 

223 def rename(self, new_path: PathLike, force: bool = False) -> "Head": 

224 """Rename self to a new path. 

225 

226 :param new_path: 

227 Either a simple name or a path, e.g. ``new_name`` or ``features/new_name``. 

228 The prefix ``refs/heads`` is implied. 

229 

230 :param force: 

231 If ``True``, the rename will succeed even if a head with the target name 

232 already exists. 

233 

234 :return: 

235 self 

236 

237 :note: 

238 Respects the ref log, as git commands are used. 

239 """ 

240 flag = "-m" 

241 if force: 

242 flag = "-M" 

243 

244 self.repo.git.branch(flag, self, new_path) 

245 self.path = "%s/%s" % (self._common_path_default, new_path) 

246 return self 

247 

248 def checkout( 

249 self, 

250 force: bool = False, 

251 allow_unsafe_options: bool = False, 

252 **kwargs: Any, 

253 ) -> Union["HEAD", "Head"]: 

254 """Check out this head by setting the HEAD to this reference, by updating the 

255 index to reflect the tree we point to and by updating the working tree to 

256 reflect the latest index. 

257 

258 The command will fail if changed working tree files would be overwritten. 

259 

260 :param force: 

261 If ``True``, changes to the index and the working tree will be discarded. 

262 If ``False``, :exc:`~git.exc.GitCommandError` will be raised in that 

263 situation. 

264 

265 :param allow_unsafe_options: 

266 Allow unsafe options such as ``--pathspec-from-file`` to be passed to 

267 :manpage:`git-checkout(1)`. 

268 

269 :param kwargs: 

270 Additional keyword arguments to be passed to git checkout, e.g. 

271 ``b="new_branch"`` to create a new branch at the given spot. 

272 

273 :return: 

274 The active branch after the checkout operation, usually self unless a new 

275 branch has been created. 

276 If there is no active branch, as the HEAD is now detached, the HEAD 

277 reference will be returned instead. 

278 

279 :note: 

280 By default it is only allowed to checkout heads - everything else will leave 

281 the HEAD detached which is allowed and possible, but remains a special state 

282 that some tools might not be able to handle. 

283 """ 

284 if not allow_unsafe_options: 

285 Git.check_unsafe_options( 

286 options=Git._option_candidates([], kwargs), 

287 unsafe_options=Git.unsafe_git_pathspec_from_file_options, 

288 ) 

289 kwargs["f"] = force 

290 if kwargs["f"] is False: 

291 kwargs.pop("f") 

292 

293 self.repo.git.checkout(self, **kwargs) 

294 if self.repo.head.is_detached: 

295 return self.repo.head 

296 else: 

297 return self.repo.active_branch 

298 

299 # { Configuration 

300 def _config_parser(self, read_only: bool) -> SectionConstraint[GitConfigParser]: 

301 if read_only: 

302 parser = self.repo.config_reader() 

303 else: 

304 parser = self.repo.config_writer() 

305 # END handle parser instance 

306 

307 return SectionConstraint(parser, 'branch "%s"' % self.name) 

308 

309 def config_reader(self) -> SectionConstraint[GitConfigParser]: 

310 """ 

311 :return: 

312 A configuration parser instance constrained to only read this instance's 

313 values. 

314 """ 

315 return self._config_parser(read_only=True) 

316 

317 def config_writer(self) -> SectionConstraint[GitConfigParser]: 

318 """ 

319 :return: 

320 A configuration writer instance with read-and write access to options of 

321 this head. 

322 """ 

323 return self._config_parser(read_only=False) 

324 

325 # } END configuration