symbolic.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767
  1. from git.types import PathLike
  2. import os
  3. from git.compat import defenc
  4. from git.objects import Object
  5. from git.objects.commit import Commit
  6. from git.util import (
  7. join_path,
  8. join_path_native,
  9. to_native_path_linux,
  10. assure_directory_exists,
  11. hex_to_bin,
  12. LockedFD,
  13. )
  14. from gitdb.exc import BadObject, BadName
  15. from .log import RefLog
  16. # typing ------------------------------------------------------------------
  17. from typing import (
  18. Any,
  19. Iterator,
  20. List,
  21. Tuple,
  22. Type,
  23. TypeVar,
  24. Union,
  25. TYPE_CHECKING,
  26. cast,
  27. ) # NOQA
  28. from git.types import Commit_ish, PathLike # NOQA
  29. if TYPE_CHECKING:
  30. from git.repo import Repo
  31. from git.refs import Head, TagReference, RemoteReference, Reference
  32. from .log import RefLogEntry
  33. from git.config import GitConfigParser
  34. from git.objects.commit import Actor
  35. T_References = TypeVar("T_References", bound="SymbolicReference")
  36. # ------------------------------------------------------------------------------
  37. __all__ = ["SymbolicReference"]
  38. def _git_dir(repo: "Repo", path: Union[PathLike, None]) -> PathLike:
  39. """Find the git dir that's appropriate for the path"""
  40. name = f"{path}"
  41. if name in ["HEAD", "ORIG_HEAD", "FETCH_HEAD", "index", "logs"]:
  42. return repo.git_dir
  43. return repo.common_dir
  44. class SymbolicReference(object):
  45. """Represents a special case of a reference such that this reference is symbolic.
  46. It does not point to a specific commit, but to another Head, which itself
  47. specifies a commit.
  48. A typical example for a symbolic reference is HEAD."""
  49. __slots__ = ("repo", "path")
  50. _resolve_ref_on_create = False
  51. _points_to_commits_only = True
  52. _common_path_default = ""
  53. _remote_common_path_default = "refs/remotes"
  54. _id_attribute_ = "name"
  55. def __init__(self, repo: "Repo", path: PathLike, check_path: bool = False):
  56. self.repo = repo
  57. self.path = path
  58. def __str__(self) -> str:
  59. return str(self.path)
  60. def __repr__(self) -> str:
  61. return '<git.%s "%s">' % (self.__class__.__name__, self.path)
  62. def __eq__(self, other: object) -> bool:
  63. if hasattr(other, "path"):
  64. other = cast(SymbolicReference, other)
  65. return self.path == other.path
  66. return False
  67. def __ne__(self, other: object) -> bool:
  68. return not (self == other)
  69. def __hash__(self) -> int:
  70. return hash(self.path)
  71. @property
  72. def name(self) -> str:
  73. """
  74. :return:
  75. In case of symbolic references, the shortest assumable name
  76. is the path itself."""
  77. return str(self.path)
  78. @property
  79. def abspath(self) -> PathLike:
  80. return join_path_native(_git_dir(self.repo, self.path), self.path)
  81. @classmethod
  82. def _get_packed_refs_path(cls, repo: "Repo") -> str:
  83. return os.path.join(repo.common_dir, "packed-refs")
  84. @classmethod
  85. def _iter_packed_refs(cls, repo: "Repo") -> Iterator[Tuple[str, str]]:
  86. """Returns an iterator yielding pairs of sha1/path pairs (as strings) for the corresponding refs.
  87. :note: The packed refs file will be kept open as long as we iterate"""
  88. try:
  89. with open(cls._get_packed_refs_path(repo), "rt", encoding="UTF-8") as fp:
  90. for line in fp:
  91. line = line.strip()
  92. if not line:
  93. continue
  94. if line.startswith("#"):
  95. # "# pack-refs with: peeled fully-peeled sorted"
  96. # the git source code shows "peeled",
  97. # "fully-peeled" and "sorted" as the keywords
  98. # that can go on this line, as per comments in git file
  99. # refs/packed-backend.c
  100. # I looked at master on 2017-10-11,
  101. # commit 111ef79afe, after tag v2.15.0-rc1
  102. # from repo https://github.com/git/git.git
  103. if line.startswith("# pack-refs with:") and "peeled" not in line:
  104. raise TypeError("PackingType of packed-Refs not understood: %r" % line)
  105. # END abort if we do not understand the packing scheme
  106. continue
  107. # END parse comment
  108. # skip dereferenced tag object entries - previous line was actual
  109. # tag reference for it
  110. if line[0] == "^":
  111. continue
  112. yield cast(Tuple[str, str], tuple(line.split(" ", 1)))
  113. # END for each line
  114. except OSError:
  115. return None
  116. # END no packed-refs file handling
  117. # NOTE: Had try-finally block around here to close the fp,
  118. # but some python version wouldn't allow yields within that.
  119. # I believe files are closing themselves on destruction, so it is
  120. # alright.
  121. @classmethod
  122. def dereference_recursive(cls, repo: "Repo", ref_path: Union[PathLike, None]) -> str:
  123. """
  124. :return: hexsha stored in the reference at the given ref_path, recursively dereferencing all
  125. intermediate references as required
  126. :param repo: the repository containing the reference at ref_path"""
  127. while True:
  128. hexsha, ref_path = cls._get_ref_info(repo, ref_path)
  129. if hexsha is not None:
  130. return hexsha
  131. # END recursive dereferencing
  132. @classmethod
  133. def _get_ref_info_helper(
  134. cls, repo: "Repo", ref_path: Union[PathLike, None]
  135. ) -> Union[Tuple[str, None], Tuple[None, str]]:
  136. """Return: (str(sha), str(target_ref_path)) if available, the sha the file at
  137. rela_path points to, or None. target_ref_path is the reference we
  138. point to, or None"""
  139. tokens: Union[None, List[str], Tuple[str, str]] = None
  140. repodir = _git_dir(repo, ref_path)
  141. try:
  142. with open(os.path.join(repodir, str(ref_path)), "rt", encoding="UTF-8") as fp:
  143. value = fp.read().rstrip()
  144. # Don't only split on spaces, but on whitespace, which allows to parse lines like
  145. # 60b64ef992065e2600bfef6187a97f92398a9144 branch 'master' of git-server:/path/to/repo
  146. tokens = value.split()
  147. assert len(tokens) != 0
  148. except OSError:
  149. # Probably we are just packed, find our entry in the packed refs file
  150. # NOTE: We are not a symbolic ref if we are in a packed file, as these
  151. # are excluded explicitly
  152. for sha, path in cls._iter_packed_refs(repo):
  153. if path != ref_path:
  154. continue
  155. # sha will be used
  156. tokens = sha, path
  157. break
  158. # END for each packed ref
  159. # END handle packed refs
  160. if tokens is None:
  161. raise ValueError("Reference at %r does not exist" % ref_path)
  162. # is it a reference ?
  163. if tokens[0] == "ref:":
  164. return (None, tokens[1])
  165. # its a commit
  166. if repo.re_hexsha_only.match(tokens[0]):
  167. return (tokens[0], None)
  168. raise ValueError("Failed to parse reference information from %r" % ref_path)
  169. @classmethod
  170. def _get_ref_info(cls, repo: "Repo", ref_path: Union[PathLike, None]) -> Union[Tuple[str, None], Tuple[None, str]]:
  171. """Return: (str(sha), str(target_ref_path)) if available, the sha the file at
  172. rela_path points to, or None. target_ref_path is the reference we
  173. point to, or None"""
  174. return cls._get_ref_info_helper(repo, ref_path)
  175. def _get_object(self) -> Commit_ish:
  176. """
  177. :return:
  178. The object our ref currently refers to. Refs can be cached, they will
  179. always point to the actual object as it gets re-created on each query"""
  180. # have to be dynamic here as we may be a tag which can point to anything
  181. # Our path will be resolved to the hexsha which will be used accordingly
  182. return Object.new_from_sha(self.repo, hex_to_bin(self.dereference_recursive(self.repo, self.path)))
  183. def _get_commit(self) -> "Commit":
  184. """
  185. :return:
  186. Commit object we point to, works for detached and non-detached
  187. SymbolicReferences. The symbolic reference will be dereferenced recursively."""
  188. obj = self._get_object()
  189. if obj.type == "tag":
  190. obj = obj.object
  191. # END dereference tag
  192. if obj.type != Commit.type:
  193. raise TypeError("Symbolic Reference pointed to object %r, commit was required" % obj)
  194. # END handle type
  195. return obj
  196. def set_commit(
  197. self,
  198. commit: Union[Commit, "SymbolicReference", str],
  199. logmsg: Union[str, None] = None,
  200. ) -> "SymbolicReference":
  201. """As set_object, but restricts the type of object to be a Commit
  202. :raise ValueError: If commit is not a Commit object or doesn't point to
  203. a commit
  204. :return: self"""
  205. # check the type - assume the best if it is a base-string
  206. invalid_type = False
  207. if isinstance(commit, Object):
  208. invalid_type = commit.type != Commit.type
  209. elif isinstance(commit, SymbolicReference):
  210. invalid_type = commit.object.type != Commit.type
  211. else:
  212. try:
  213. invalid_type = self.repo.rev_parse(commit).type != Commit.type
  214. except (BadObject, BadName) as e:
  215. raise ValueError("Invalid object: %s" % commit) from e
  216. # END handle exception
  217. # END verify type
  218. if invalid_type:
  219. raise ValueError("Need commit, got %r" % commit)
  220. # END handle raise
  221. # we leave strings to the rev-parse method below
  222. self.set_object(commit, logmsg)
  223. return self
  224. def set_object(
  225. self,
  226. object: Union[Commit_ish, "SymbolicReference", str],
  227. logmsg: Union[str, None] = None,
  228. ) -> "SymbolicReference":
  229. """Set the object we point to, possibly dereference our symbolic reference first.
  230. If the reference does not exist, it will be created
  231. :param object: a refspec, a SymbolicReference or an Object instance. SymbolicReferences
  232. will be dereferenced beforehand to obtain the object they point to
  233. :param logmsg: If not None, the message will be used in the reflog entry to be
  234. written. Otherwise the reflog is not altered
  235. :note: plain SymbolicReferences may not actually point to objects by convention
  236. :return: self"""
  237. if isinstance(object, SymbolicReference):
  238. object = object.object # @ReservedAssignment
  239. # END resolve references
  240. is_detached = True
  241. try:
  242. is_detached = self.is_detached
  243. except ValueError:
  244. pass
  245. # END handle non-existing ones
  246. if is_detached:
  247. return self.set_reference(object, logmsg)
  248. # set the commit on our reference
  249. return self._get_reference().set_object(object, logmsg)
  250. commit = property(_get_commit, set_commit, doc="Query or set commits directly") # type: ignore
  251. object = property(_get_object, set_object, doc="Return the object our ref currently refers to") # type: ignore
  252. def _get_reference(self) -> "SymbolicReference":
  253. """:return: Reference Object we point to
  254. :raise TypeError: If this symbolic reference is detached, hence it doesn't point
  255. to a reference, but to a commit"""
  256. sha, target_ref_path = self._get_ref_info(self.repo, self.path)
  257. if target_ref_path is None:
  258. raise TypeError("%s is a detached symbolic reference as it points to %r" % (self, sha))
  259. return self.from_path(self.repo, target_ref_path)
  260. def set_reference(
  261. self,
  262. ref: Union[Commit_ish, "SymbolicReference", str],
  263. logmsg: Union[str, None] = None,
  264. ) -> "SymbolicReference":
  265. """Set ourselves to the given ref. It will stay a symbol if the ref is a Reference.
  266. Otherwise an Object, given as Object instance or refspec, is assumed and if valid,
  267. will be set which effectively detaches the reference if it was a purely
  268. symbolic one.
  269. :param ref: SymbolicReference instance, Object instance or refspec string
  270. Only if the ref is a SymbolicRef instance, we will point to it. Everything
  271. else is dereferenced to obtain the actual object.
  272. :param logmsg: If set to a string, the message will be used in the reflog.
  273. Otherwise, a reflog entry is not written for the changed reference.
  274. The previous commit of the entry will be the commit we point to now.
  275. See also: log_append()
  276. :return: self
  277. :note: This symbolic reference will not be dereferenced. For that, see
  278. ``set_object(...)``"""
  279. write_value = None
  280. obj = None
  281. if isinstance(ref, SymbolicReference):
  282. write_value = "ref: %s" % ref.path
  283. elif isinstance(ref, Object):
  284. obj = ref
  285. write_value = ref.hexsha
  286. elif isinstance(ref, str):
  287. try:
  288. obj = self.repo.rev_parse(ref + "^{}") # optionally deref tags
  289. write_value = obj.hexsha
  290. except (BadObject, BadName) as e:
  291. raise ValueError("Could not extract object from %s" % ref) from e
  292. # END end try string
  293. else:
  294. raise ValueError("Unrecognized Value: %r" % ref)
  295. # END try commit attribute
  296. # typecheck
  297. if obj is not None and self._points_to_commits_only and obj.type != Commit.type:
  298. raise TypeError("Require commit, got %r" % obj)
  299. # END verify type
  300. oldbinsha: bytes = b""
  301. if logmsg is not None:
  302. try:
  303. oldbinsha = self.commit.binsha
  304. except ValueError:
  305. oldbinsha = Commit.NULL_BIN_SHA
  306. # END handle non-existing
  307. # END retrieve old hexsha
  308. fpath = self.abspath
  309. assure_directory_exists(fpath, is_file=True)
  310. lfd = LockedFD(fpath)
  311. fd = lfd.open(write=True, stream=True)
  312. ok = True
  313. try:
  314. fd.write(write_value.encode("utf-8") + b"\n")
  315. lfd.commit()
  316. ok = True
  317. finally:
  318. if not ok:
  319. lfd.rollback()
  320. # Adjust the reflog
  321. if logmsg is not None:
  322. self.log_append(oldbinsha, logmsg)
  323. return self
  324. # aliased reference
  325. reference: Union["Head", "TagReference", "RemoteReference", "Reference"]
  326. reference = property(_get_reference, set_reference, doc="Returns the Reference we point to") # type: ignore
  327. ref = reference
  328. def is_valid(self) -> bool:
  329. """
  330. :return:
  331. True if the reference is valid, hence it can be read and points to
  332. a valid object or reference."""
  333. try:
  334. self.object
  335. except (OSError, ValueError):
  336. return False
  337. else:
  338. return True
  339. @property
  340. def is_detached(self) -> bool:
  341. """
  342. :return:
  343. True if we are a detached reference, hence we point to a specific commit
  344. instead to another reference"""
  345. try:
  346. self.ref
  347. return False
  348. except TypeError:
  349. return True
  350. def log(self) -> "RefLog":
  351. """
  352. :return: RefLog for this reference. Its last entry reflects the latest change
  353. applied to this reference
  354. .. note:: As the log is parsed every time, its recommended to cache it for use
  355. instead of calling this method repeatedly. It should be considered read-only."""
  356. return RefLog.from_file(RefLog.path(self))
  357. def log_append(
  358. self,
  359. oldbinsha: bytes,
  360. message: Union[str, None],
  361. newbinsha: Union[bytes, None] = None,
  362. ) -> "RefLogEntry":
  363. """Append a logentry to the logfile of this ref
  364. :param oldbinsha: binary sha this ref used to point to
  365. :param message: A message describing the change
  366. :param newbinsha: The sha the ref points to now. If None, our current commit sha
  367. will be used
  368. :return: added RefLogEntry instance"""
  369. # NOTE: we use the committer of the currently active commit - this should be
  370. # correct to allow overriding the committer on a per-commit level.
  371. # See https://github.com/gitpython-developers/GitPython/pull/146
  372. try:
  373. committer_or_reader: Union["Actor", "GitConfigParser"] = self.commit.committer
  374. except ValueError:
  375. committer_or_reader = self.repo.config_reader()
  376. # end handle newly cloned repositories
  377. if newbinsha is None:
  378. newbinsha = self.commit.binsha
  379. if message is None:
  380. message = ""
  381. return RefLog.append_entry(committer_or_reader, RefLog.path(self), oldbinsha, newbinsha, message)
  382. def log_entry(self, index: int) -> "RefLogEntry":
  383. """:return: RefLogEntry at the given index
  384. :param index: python list compatible positive or negative index
  385. .. note:: This method must read part of the reflog during execution, hence
  386. it should be used sparringly, or only if you need just one index.
  387. In that case, it will be faster than the ``log()`` method"""
  388. return RefLog.entry_at(RefLog.path(self), index)
  389. @classmethod
  390. def to_full_path(cls, path: Union[PathLike, "SymbolicReference"]) -> PathLike:
  391. """
  392. :return: string with a full repository-relative path which can be used to initialize
  393. a Reference instance, for instance by using ``Reference.from_path``"""
  394. if isinstance(path, SymbolicReference):
  395. path = path.path
  396. full_ref_path = path
  397. if not cls._common_path_default:
  398. return full_ref_path
  399. if not str(path).startswith(cls._common_path_default + "/"):
  400. full_ref_path = "%s/%s" % (cls._common_path_default, path)
  401. return full_ref_path
  402. @classmethod
  403. def delete(cls, repo: "Repo", path: PathLike) -> None:
  404. """Delete the reference at the given path
  405. :param repo:
  406. Repository to delete the reference from
  407. :param path:
  408. Short or full path pointing to the reference, i.e. refs/myreference
  409. or just "myreference", hence 'refs/' is implied.
  410. Alternatively the symbolic reference to be deleted"""
  411. full_ref_path = cls.to_full_path(path)
  412. abs_path = os.path.join(repo.common_dir, full_ref_path)
  413. if os.path.exists(abs_path):
  414. os.remove(abs_path)
  415. else:
  416. # check packed refs
  417. pack_file_path = cls._get_packed_refs_path(repo)
  418. try:
  419. with open(pack_file_path, "rb") as reader:
  420. new_lines = []
  421. made_change = False
  422. dropped_last_line = False
  423. for line_bytes in reader:
  424. line = line_bytes.decode(defenc)
  425. _, _, line_ref = line.partition(" ")
  426. line_ref = line_ref.strip()
  427. # keep line if it is a comment or if the ref to delete is not
  428. # in the line
  429. # If we deleted the last line and this one is a tag-reference object,
  430. # we drop it as well
  431. if (line.startswith("#") or full_ref_path != line_ref) and (
  432. not dropped_last_line or dropped_last_line and not line.startswith("^")
  433. ):
  434. new_lines.append(line)
  435. dropped_last_line = False
  436. continue
  437. # END skip comments and lines without our path
  438. # drop this line
  439. made_change = True
  440. dropped_last_line = True
  441. # write the new lines
  442. if made_change:
  443. # write-binary is required, otherwise windows will
  444. # open the file in text mode and change LF to CRLF !
  445. with open(pack_file_path, "wb") as fd:
  446. fd.writelines(line.encode(defenc) for line in new_lines)
  447. except OSError:
  448. pass # it didn't exist at all
  449. # delete the reflog
  450. reflog_path = RefLog.path(cls(repo, full_ref_path))
  451. if os.path.isfile(reflog_path):
  452. os.remove(reflog_path)
  453. # END remove reflog
  454. @classmethod
  455. def _create(
  456. cls: Type[T_References],
  457. repo: "Repo",
  458. path: PathLike,
  459. resolve: bool,
  460. reference: Union["SymbolicReference", str],
  461. force: bool,
  462. logmsg: Union[str, None] = None,
  463. ) -> T_References:
  464. """internal method used to create a new symbolic reference.
  465. If resolve is False, the reference will be taken as is, creating
  466. a proper symbolic reference. Otherwise it will be resolved to the
  467. corresponding object and a detached symbolic reference will be created
  468. instead"""
  469. git_dir = _git_dir(repo, path)
  470. full_ref_path = cls.to_full_path(path)
  471. abs_ref_path = os.path.join(git_dir, full_ref_path)
  472. # figure out target data
  473. target = reference
  474. if resolve:
  475. target = repo.rev_parse(str(reference))
  476. if not force and os.path.isfile(abs_ref_path):
  477. target_data = str(target)
  478. if isinstance(target, SymbolicReference):
  479. target_data = str(target.path)
  480. if not resolve:
  481. target_data = "ref: " + target_data
  482. with open(abs_ref_path, "rb") as fd:
  483. existing_data = fd.read().decode(defenc).strip()
  484. if existing_data != target_data:
  485. raise OSError(
  486. "Reference at %r does already exist, pointing to %r, requested was %r"
  487. % (full_ref_path, existing_data, target_data)
  488. )
  489. # END no force handling
  490. ref = cls(repo, full_ref_path)
  491. ref.set_reference(target, logmsg)
  492. return ref
  493. @classmethod
  494. def create(
  495. cls: Type[T_References],
  496. repo: "Repo",
  497. path: PathLike,
  498. reference: Union["SymbolicReference", str] = "HEAD",
  499. logmsg: Union[str, None] = None,
  500. force: bool = False,
  501. **kwargs: Any,
  502. ) -> T_References:
  503. """Create a new symbolic reference, hence a reference pointing , to another reference.
  504. :param repo:
  505. Repository to create the reference in
  506. :param path:
  507. full path at which the new symbolic reference is supposed to be
  508. created at, i.e. "NEW_HEAD" or "symrefs/my_new_symref"
  509. :param reference:
  510. The reference to which the new symbolic reference should point to.
  511. If it is a commit'ish, the symbolic ref will be detached.
  512. :param force:
  513. if True, force creation even if a symbolic reference with that name already exists.
  514. Raise OSError otherwise
  515. :param logmsg:
  516. If not None, the message to append to the reflog. Otherwise no reflog
  517. entry is written.
  518. :return: Newly created symbolic Reference
  519. :raise OSError:
  520. If a (Symbolic)Reference with the same name but different contents
  521. already exists.
  522. :note: This does not alter the current HEAD, index or Working Tree"""
  523. return cls._create(repo, path, cls._resolve_ref_on_create, reference, force, logmsg)
  524. def rename(self, new_path: PathLike, force: bool = False) -> "SymbolicReference":
  525. """Rename self to a new path
  526. :param new_path:
  527. Either a simple name or a full path, i.e. new_name or features/new_name.
  528. The prefix refs/ is implied for references and will be set as needed.
  529. In case this is a symbolic ref, there is no implied prefix
  530. :param force:
  531. If True, the rename will succeed even if a head with the target name
  532. already exists. It will be overwritten in that case
  533. :return: self
  534. :raise OSError: In case a file at path but a different contents already exists"""
  535. new_path = self.to_full_path(new_path)
  536. if self.path == new_path:
  537. return self
  538. new_abs_path = os.path.join(_git_dir(self.repo, new_path), new_path)
  539. cur_abs_path = os.path.join(_git_dir(self.repo, self.path), self.path)
  540. if os.path.isfile(new_abs_path):
  541. if not force:
  542. # if they point to the same file, its not an error
  543. with open(new_abs_path, "rb") as fd1:
  544. f1 = fd1.read().strip()
  545. with open(cur_abs_path, "rb") as fd2:
  546. f2 = fd2.read().strip()
  547. if f1 != f2:
  548. raise OSError("File at path %r already exists" % new_abs_path)
  549. # else: we could remove ourselves and use the otherone, but
  550. # but clarity we just continue as usual
  551. # END not force handling
  552. os.remove(new_abs_path)
  553. # END handle existing target file
  554. dname = os.path.dirname(new_abs_path)
  555. if not os.path.isdir(dname):
  556. os.makedirs(dname)
  557. # END create directory
  558. os.rename(cur_abs_path, new_abs_path)
  559. self.path = new_path
  560. return self
  561. @classmethod
  562. def _iter_items(
  563. cls: Type[T_References], repo: "Repo", common_path: Union[PathLike, None] = None
  564. ) -> Iterator[T_References]:
  565. if common_path is None:
  566. common_path = cls._common_path_default
  567. rela_paths = set()
  568. # walk loose refs
  569. # Currently we do not follow links
  570. for root, dirs, files in os.walk(join_path_native(repo.common_dir, common_path)):
  571. if "refs" not in root.split(os.sep): # skip non-refs subfolders
  572. refs_id = [d for d in dirs if d == "refs"]
  573. if refs_id:
  574. dirs[0:] = ["refs"]
  575. # END prune non-refs folders
  576. for f in files:
  577. if f == "packed-refs":
  578. continue
  579. abs_path = to_native_path_linux(join_path(root, f))
  580. rela_paths.add(abs_path.replace(to_native_path_linux(repo.common_dir) + "/", ""))
  581. # END for each file in root directory
  582. # END for each directory to walk
  583. # read packed refs
  584. for _sha, rela_path in cls._iter_packed_refs(repo):
  585. if rela_path.startswith(str(common_path)):
  586. rela_paths.add(rela_path)
  587. # END relative path matches common path
  588. # END packed refs reading
  589. # return paths in sorted order
  590. for path in sorted(rela_paths):
  591. try:
  592. yield cls.from_path(repo, path)
  593. except ValueError:
  594. continue
  595. # END for each sorted relative refpath
  596. @classmethod
  597. def iter_items(
  598. cls: Type[T_References],
  599. repo: "Repo",
  600. common_path: Union[PathLike, None] = None,
  601. *args: Any,
  602. **kwargs: Any,
  603. ) -> Iterator[T_References]:
  604. """Find all refs in the repository
  605. :param repo: is the Repo
  606. :param common_path:
  607. Optional keyword argument to the path which is to be shared by all
  608. returned Ref objects.
  609. Defaults to class specific portion if None assuring that only
  610. refs suitable for the actual class are returned.
  611. :return:
  612. git.SymbolicReference[], each of them is guaranteed to be a symbolic
  613. ref which is not detached and pointing to a valid ref
  614. List is lexicographically sorted
  615. The returned objects represent actual subclasses, such as Head or TagReference"""
  616. return (r for r in cls._iter_items(repo, common_path) if r.__class__ == SymbolicReference or not r.is_detached)
  617. @classmethod
  618. def from_path(cls: Type[T_References], repo: "Repo", path: PathLike) -> T_References:
  619. """
  620. :param path: full .git-directory-relative path name to the Reference to instantiate
  621. :note: use to_full_path() if you only have a partial path of a known Reference Type
  622. :return:
  623. Instance of type Reference, Head, or Tag
  624. depending on the given path"""
  625. if not path:
  626. raise ValueError("Cannot create Reference from %r" % path)
  627. # Names like HEAD are inserted after the refs module is imported - we have an import dependency
  628. # cycle and don't want to import these names in-function
  629. from . import HEAD, Head, RemoteReference, TagReference, Reference
  630. for ref_type in (
  631. HEAD,
  632. Head,
  633. RemoteReference,
  634. TagReference,
  635. Reference,
  636. SymbolicReference,
  637. ):
  638. try:
  639. instance: T_References
  640. instance = ref_type(repo, path)
  641. if instance.__class__ == SymbolicReference and instance.is_detached:
  642. raise ValueError("SymbolRef was detached, we drop it")
  643. else:
  644. return instance
  645. except ValueError:
  646. pass
  647. # END exception handling
  648. # END for each type to try
  649. raise ValueError("Could not find reference type suitable to handle path %r" % path)
  650. def is_remote(self) -> bool:
  651. """:return: True if this symbolic reference points to a remote branch"""
  652. return str(self.path).startswith(self._remote_common_path_default + "/")