Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/git/refs/symbolic.py: 58%
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
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
1# This module is part of GitPython and is released under the
2# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
4__all__ = ["SymbolicReference"]
6import os
8from gitdb.exc import BadName, BadObject
10from git.compat import defenc
11from git.objects.base import Object
12from git.objects.commit import Commit
13from git.refs.log import RefLog
14from git.util import (
15 LockedFD,
16 assure_directory_exists,
17 hex_to_bin,
18 join_path,
19 join_path_native,
20 to_native_path_linux,
21)
23# typing ------------------------------------------------------------------
25from typing import (
26 Any,
27 Iterator,
28 List,
29 TYPE_CHECKING,
30 Tuple,
31 Type,
32 TypeVar,
33 Union,
34 cast,
35)
37from git.types import AnyGitObject, PathLike
39if TYPE_CHECKING:
40 from git.config import GitConfigParser
41 from git.objects.commit import Actor
42 from git.refs import Head, TagReference, RemoteReference, Reference
43 from git.refs.log import RefLogEntry
44 from git.repo import Repo
47T_References = TypeVar("T_References", bound="SymbolicReference")
49# ------------------------------------------------------------------------------
52def _git_dir(repo: "Repo", path: Union[PathLike, None]) -> PathLike:
53 """Find the git dir that is appropriate for the path."""
54 name = f"{path}"
55 if name in ["HEAD", "ORIG_HEAD", "FETCH_HEAD", "index", "logs"]:
56 return repo.git_dir
57 return repo.common_dir
60class SymbolicReference:
61 """Special case of a reference that is symbolic.
63 This does not point to a specific commit, but to another
64 :class:`~git.refs.head.Head`, which itself specifies a commit.
66 A typical example for a symbolic reference is :class:`~git.refs.head.HEAD`.
67 """
69 __slots__ = ("repo", "path")
71 _resolve_ref_on_create = False
72 _points_to_commits_only = True
73 _common_path_default = ""
74 _remote_common_path_default = "refs/remotes"
75 _id_attribute_ = "name"
77 def __init__(self, repo: "Repo", path: PathLike, check_path: bool = False) -> None:
78 self.repo = repo
79 self.path = path
81 def __str__(self) -> str:
82 return str(self.path)
84 def __repr__(self) -> str:
85 return '<git.%s "%s">' % (self.__class__.__name__, self.path)
87 def __eq__(self, other: object) -> bool:
88 if hasattr(other, "path"):
89 other = cast(SymbolicReference, other)
90 return self.path == other.path
91 return False
93 def __ne__(self, other: object) -> bool:
94 return not (self == other)
96 def __hash__(self) -> int:
97 return hash(self.path)
99 @property
100 def name(self) -> str:
101 """
102 :return:
103 In case of symbolic references, the shortest assumable name is the path
104 itself.
105 """
106 return str(self.path)
108 @property
109 def abspath(self) -> PathLike:
110 return join_path_native(_git_dir(self.repo, self.path), self.path)
112 @classmethod
113 def _get_packed_refs_path(cls, repo: "Repo") -> str:
114 return os.path.join(repo.common_dir, "packed-refs")
116 @classmethod
117 def _iter_packed_refs(cls, repo: "Repo") -> Iterator[Tuple[str, str]]:
118 """Return an iterator yielding pairs of sha1/path pairs (as strings) for the
119 corresponding refs.
121 :note:
122 The packed refs file will be kept open as long as we iterate.
123 """
124 try:
125 with open(cls._get_packed_refs_path(repo), "rt", encoding="UTF-8") as fp:
126 for line in fp:
127 line = line.strip()
128 if not line:
129 continue
130 if line.startswith("#"):
131 # "# pack-refs with: peeled fully-peeled sorted"
132 # the git source code shows "peeled",
133 # "fully-peeled" and "sorted" as the keywords
134 # that can go on this line, as per comments in git file
135 # refs/packed-backend.c
136 # I looked at master on 2017-10-11,
137 # commit 111ef79afe, after tag v2.15.0-rc1
138 # from repo https://github.com/git/git.git
139 if line.startswith("# pack-refs with:") and "peeled" not in line:
140 raise TypeError("PackingType of packed-Refs not understood: %r" % line)
141 # END abort if we do not understand the packing scheme
142 continue
143 # END parse comment
145 # Skip dereferenced tag object entries - previous line was actual
146 # tag reference for it.
147 if line[0] == "^":
148 continue
150 yield cast(Tuple[str, str], tuple(line.split(" ", 1)))
151 # END for each line
152 except OSError:
153 return None
154 # END no packed-refs file handling
156 @classmethod
157 def dereference_recursive(cls, repo: "Repo", ref_path: Union[PathLike, None]) -> str:
158 """
159 :return:
160 hexsha stored in the reference at the given `ref_path`, recursively
161 dereferencing all intermediate references as required
163 :param repo:
164 The repository containing the reference at `ref_path`.
165 """
167 while True:
168 hexsha, ref_path = cls._get_ref_info(repo, ref_path)
169 if hexsha is not None:
170 return hexsha
171 # END recursive dereferencing
173 @staticmethod
174 def _check_ref_name_valid(ref_path: PathLike) -> None:
175 """Check a ref name for validity.
177 This is based on the rules described in :manpage:`git-check-ref-format(1)`.
178 """
179 previous: Union[str, None] = None
180 one_before_previous: Union[str, None] = None
181 for c in str(ref_path):
182 if c in " ~^:?*[\\":
183 raise ValueError(
184 f"Invalid reference '{ref_path}': references cannot contain spaces, tildes (~), carets (^),"
185 f" colons (:), question marks (?), asterisks (*), open brackets ([) or backslashes (\\)"
186 )
187 elif c == ".":
188 if previous is None or previous == "/":
189 raise ValueError(
190 f"Invalid reference '{ref_path}': references cannot start with a period (.) or contain '/.'"
191 )
192 elif previous == ".":
193 raise ValueError(f"Invalid reference '{ref_path}': references cannot contain '..'")
194 elif c == "/":
195 if previous == "/":
196 raise ValueError(f"Invalid reference '{ref_path}': references cannot contain '//'")
197 elif previous is None:
198 raise ValueError(
199 f"Invalid reference '{ref_path}': references cannot start with forward slashes '/'"
200 )
201 elif c == "{" and previous == "@":
202 raise ValueError(f"Invalid reference '{ref_path}': references cannot contain '@{{'")
203 elif ord(c) < 32 or ord(c) == 127:
204 raise ValueError(f"Invalid reference '{ref_path}': references cannot contain ASCII control characters")
206 one_before_previous = previous
207 previous = c
209 if previous == ".":
210 raise ValueError(f"Invalid reference '{ref_path}': references cannot end with a period (.)")
211 elif previous == "/":
212 raise ValueError(f"Invalid reference '{ref_path}': references cannot end with a forward slash (/)")
213 elif previous == "@" and one_before_previous is None:
214 raise ValueError(f"Invalid reference '{ref_path}': references cannot be '@'")
215 elif any(component.endswith(".lock") for component in str(ref_path).split("/")):
216 raise ValueError(
217 f"Invalid reference '{ref_path}': references cannot have slash-separated components that end with"
218 " '.lock'"
219 )
221 @classmethod
222 def _get_ref_info_helper(
223 cls, repo: "Repo", ref_path: Union[PathLike, None]
224 ) -> Union[Tuple[str, None], Tuple[None, str]]:
225 """
226 :return:
227 *(str(sha), str(target_ref_path))*, where:
229 * *sha* is of the file at rela_path points to if available, or ``None``.
230 * *target_ref_path* is the reference we point to, or ``None``.
231 """
232 if ref_path:
233 cls._check_ref_name_valid(ref_path)
235 tokens: Union[None, List[str], Tuple[str, str]] = None
236 repodir = _git_dir(repo, ref_path)
237 try:
238 with open(os.path.join(repodir, str(ref_path)), "rt", encoding="UTF-8") as fp:
239 value = fp.read().rstrip()
240 # Don't only split on spaces, but on whitespace, which allows to parse lines like:
241 # 60b64ef992065e2600bfef6187a97f92398a9144 branch 'master' of git-server:/path/to/repo
242 tokens = value.split()
243 assert len(tokens) != 0
244 except OSError:
245 # Probably we are just packed. Find our entry in the packed refs file.
246 # NOTE: We are not a symbolic ref if we are in a packed file, as these
247 # are excluded explicitly.
248 for sha, path in cls._iter_packed_refs(repo):
249 if path != ref_path:
250 continue
251 # sha will be used.
252 tokens = sha, path
253 break
254 # END for each packed ref
255 # END handle packed refs
256 if tokens is None:
257 raise ValueError("Reference at %r does not exist" % ref_path)
259 # Is it a reference?
260 if tokens[0] == "ref:":
261 return (None, tokens[1])
263 # It's a commit.
264 if repo.re_hexsha_only.match(tokens[0]):
265 return (tokens[0], None)
267 raise ValueError("Failed to parse reference information from %r" % ref_path)
269 @classmethod
270 def _get_ref_info(cls, repo: "Repo", ref_path: Union[PathLike, None]) -> Union[Tuple[str, None], Tuple[None, str]]:
271 """
272 :return:
273 *(str(sha), str(target_ref_path))*, where:
275 * *sha* is of the file at rela_path points to if available, or ``None``.
276 * *target_ref_path* is the reference we point to, or ``None``.
277 """
278 return cls._get_ref_info_helper(repo, ref_path)
280 def _get_object(self) -> AnyGitObject:
281 """
282 :return:
283 The object our ref currently refers to. Refs can be cached, they will always
284 point to the actual object as it gets re-created on each query.
285 """
286 # We have to be dynamic here as we may be a tag which can point to anything.
287 # Our path will be resolved to the hexsha which will be used accordingly.
288 return Object.new_from_sha(self.repo, hex_to_bin(self.dereference_recursive(self.repo, self.path)))
290 def _get_commit(self) -> "Commit":
291 """
292 :return:
293 :class:`~git.objects.commit.Commit` object we point to. This works for
294 detached and non-detached :class:`SymbolicReference` instances. The symbolic
295 reference will be dereferenced recursively.
296 """
297 obj = self._get_object()
298 if obj.type == "tag":
299 obj = obj.object
300 # END dereference tag
302 if obj.type != Commit.type:
303 raise TypeError("Symbolic Reference pointed to object %r, commit was required" % obj)
304 # END handle type
305 return obj
307 def set_commit(
308 self,
309 commit: Union[Commit, "SymbolicReference", str],
310 logmsg: Union[str, None] = None,
311 ) -> "SymbolicReference":
312 """Like :meth:`set_object`, but restricts the type of object to be a
313 :class:`~git.objects.commit.Commit`.
315 :raise ValueError:
316 If `commit` is not a :class:`~git.objects.commit.Commit` object, nor does it
317 point to a commit.
319 :return:
320 self
321 """
322 # Check the type - assume the best if it is a base-string.
323 invalid_type = False
324 if isinstance(commit, Object):
325 invalid_type = commit.type != Commit.type
326 elif isinstance(commit, SymbolicReference):
327 invalid_type = commit.object.type != Commit.type
328 else:
329 try:
330 invalid_type = self.repo.rev_parse(commit).type != Commit.type
331 except (BadObject, BadName) as e:
332 raise ValueError("Invalid object: %s" % commit) from e
333 # END handle exception
334 # END verify type
336 if invalid_type:
337 raise ValueError("Need commit, got %r" % commit)
338 # END handle raise
340 # We leave strings to the rev-parse method below.
341 self.set_object(commit, logmsg)
343 return self
345 def set_object(
346 self,
347 object: Union[AnyGitObject, "SymbolicReference", str],
348 logmsg: Union[str, None] = None,
349 ) -> "SymbolicReference":
350 """Set the object we point to, possibly dereference our symbolic reference
351 first. If the reference does not exist, it will be created.
353 :param object:
354 A refspec, a :class:`SymbolicReference` or an
355 :class:`~git.objects.base.Object` instance.
357 * :class:`SymbolicReference` instances will be dereferenced beforehand to
358 obtain the git object they point to.
359 * :class:`~git.objects.base.Object` instances must represent git objects
360 (:class:`~git.types.AnyGitObject`).
362 :param logmsg:
363 If not ``None``, the message will be used in the reflog entry to be written.
364 Otherwise the reflog is not altered.
366 :note:
367 Plain :class:`SymbolicReference` instances may not actually point to objects
368 by convention.
370 :return:
371 self
372 """
373 if isinstance(object, SymbolicReference):
374 object = object.object # @ReservedAssignment
375 # END resolve references
377 is_detached = True
378 try:
379 is_detached = self.is_detached
380 except ValueError:
381 pass
382 # END handle non-existing ones
384 if is_detached:
385 return self.set_reference(object, logmsg)
387 # set the commit on our reference
388 return self._get_reference().set_object(object, logmsg)
390 commit = property(
391 _get_commit,
392 set_commit, # type: ignore[arg-type]
393 doc="Query or set commits directly",
394 )
396 object = property(
397 _get_object,
398 set_object, # type: ignore[arg-type]
399 doc="Return the object our ref currently refers to",
400 )
402 def _get_reference(self) -> "SymbolicReference":
403 """
404 :return:
405 :class:`~git.refs.reference.Reference` object we point to
407 :raise TypeError:
408 If this symbolic reference is detached, hence it doesn't point to a
409 reference, but to a commit.
410 """
411 sha, target_ref_path = self._get_ref_info(self.repo, self.path)
412 if target_ref_path is None:
413 raise TypeError("%s is a detached symbolic reference as it points to %r" % (self, sha))
414 return self.from_path(self.repo, target_ref_path)
416 def set_reference(
417 self,
418 ref: Union[AnyGitObject, "SymbolicReference", str],
419 logmsg: Union[str, None] = None,
420 ) -> "SymbolicReference":
421 """Set ourselves to the given `ref`.
423 It will stay a symbol if the `ref` is a :class:`~git.refs.reference.Reference`.
425 Otherwise a git object, specified as a :class:`~git.objects.base.Object`
426 instance or refspec, is assumed. If it is valid, this reference will be set to
427 it, which effectively detaches the reference if it was a purely symbolic one.
429 :param ref:
430 A :class:`SymbolicReference` instance, an :class:`~git.objects.base.Object`
431 instance (specifically an :class:`~git.types.AnyGitObject`), or a refspec
432 string. Only if the ref is a :class:`SymbolicReference` instance, we will
433 point to it. Everything else is dereferenced to obtain the actual object.
435 :param logmsg:
436 If set to a string, the message will be used in the reflog.
437 Otherwise, a reflog entry is not written for the changed reference.
438 The previous commit of the entry will be the commit we point to now.
440 See also: :meth:`log_append`
442 :return:
443 self
445 :note:
446 This symbolic reference will not be dereferenced. For that, see
447 :meth:`set_object`.
448 """
449 write_value = None
450 obj = None
451 if isinstance(ref, SymbolicReference):
452 write_value = "ref: %s" % ref.path
453 elif isinstance(ref, Object):
454 obj = ref
455 write_value = ref.hexsha
456 elif isinstance(ref, str):
457 try:
458 obj = self.repo.rev_parse(ref + "^{}") # Optionally dereference tags.
459 write_value = obj.hexsha
460 except (BadObject, BadName) as e:
461 raise ValueError("Could not extract object from %s" % ref) from e
462 # END end try string
463 else:
464 raise ValueError("Unrecognized Value: %r" % ref)
465 # END try commit attribute
467 # typecheck
468 if obj is not None and self._points_to_commits_only and obj.type != Commit.type:
469 raise TypeError("Require commit, got %r" % obj)
470 # END verify type
472 oldbinsha: bytes = b""
473 if logmsg is not None:
474 try:
475 oldbinsha = self.commit.binsha
476 except ValueError:
477 oldbinsha = Commit.NULL_BIN_SHA
478 # END handle non-existing
479 # END retrieve old hexsha
481 fpath = self.abspath
482 assure_directory_exists(fpath, is_file=True)
484 lfd = LockedFD(fpath)
485 fd = lfd.open(write=True, stream=True)
486 try:
487 fd.write(write_value.encode("utf-8") + b"\n")
488 lfd.commit()
489 except BaseException:
490 lfd.rollback()
491 raise
492 # Adjust the reflog
493 if logmsg is not None:
494 self.log_append(oldbinsha, logmsg)
496 return self
498 # Aliased reference
499 reference: Union["Head", "TagReference", "RemoteReference", "Reference"]
500 reference = property( # type: ignore[assignment]
501 _get_reference,
502 set_reference, # type: ignore[arg-type]
503 doc="Returns the Reference we point to",
504 )
505 ref = reference
507 def is_valid(self) -> bool:
508 """
509 :return:
510 ``True`` if the reference is valid, hence it can be read and points to a
511 valid object or reference.
512 """
513 try:
514 self.object # noqa: B018
515 except (OSError, ValueError):
516 return False
517 else:
518 return True
520 @property
521 def is_detached(self) -> bool:
522 """
523 :return:
524 ``True`` if we are a detached reference, hence we point to a specific commit
525 instead to another reference.
526 """
527 try:
528 self.ref # noqa: B018
529 return False
530 except TypeError:
531 return True
533 def log(self) -> "RefLog":
534 """
535 :return:
536 :class:`~git.refs.log.RefLog` for this reference.
537 Its last entry reflects the latest change applied to this reference.
539 :note:
540 As the log is parsed every time, its recommended to cache it for use instead
541 of calling this method repeatedly. It should be considered read-only.
542 """
543 return RefLog.from_file(RefLog.path(self))
545 def log_append(
546 self,
547 oldbinsha: bytes,
548 message: Union[str, None],
549 newbinsha: Union[bytes, None] = None,
550 ) -> "RefLogEntry":
551 """Append a logentry to the logfile of this ref.
553 :param oldbinsha:
554 Binary sha this ref used to point to.
556 :param message:
557 A message describing the change.
559 :param newbinsha:
560 The sha the ref points to now. If None, our current commit sha will be used.
562 :return:
563 The added :class:`~git.refs.log.RefLogEntry` instance.
564 """
565 # NOTE: We use the committer of the currently active commit - this should be
566 # correct to allow overriding the committer on a per-commit level.
567 # See https://github.com/gitpython-developers/GitPython/pull/146.
568 try:
569 committer_or_reader: Union["Actor", "GitConfigParser"] = self.commit.committer
570 except ValueError:
571 committer_or_reader = self.repo.config_reader()
572 # END handle newly cloned repositories
573 if newbinsha is None:
574 newbinsha = self.commit.binsha
576 if message is None:
577 message = ""
579 return RefLog.append_entry(committer_or_reader, RefLog.path(self), oldbinsha, newbinsha, message)
581 def log_entry(self, index: int) -> "RefLogEntry":
582 """
583 :return:
584 :class:`~git.refs.log.RefLogEntry` at the given index
586 :param index:
587 Python list compatible positive or negative index.
589 :note:
590 This method must read part of the reflog during execution, hence it should
591 be used sparingly, or only if you need just one index. In that case, it will
592 be faster than the :meth:`log` method.
593 """
594 return RefLog.entry_at(RefLog.path(self), index)
596 @classmethod
597 def to_full_path(cls, path: Union[PathLike, "SymbolicReference"]) -> PathLike:
598 """
599 :return:
600 String with a full repository-relative path which can be used to initialize
601 a :class:`~git.refs.reference.Reference` instance, for instance by using
602 :meth:`Reference.from_path <git.refs.reference.Reference.from_path>`.
603 """
604 if isinstance(path, SymbolicReference):
605 path = path.path
606 full_ref_path = path
607 if not cls._common_path_default:
608 return full_ref_path
609 if not str(path).startswith(cls._common_path_default + "/"):
610 full_ref_path = "%s/%s" % (cls._common_path_default, path)
611 return full_ref_path
613 @classmethod
614 def delete(cls, repo: "Repo", path: PathLike) -> None:
615 """Delete the reference at the given path.
617 :param repo:
618 Repository to delete the reference from.
620 :param path:
621 Short or full path pointing to the reference, e.g. ``refs/myreference`` or
622 just ``myreference``, hence ``refs/`` is implied.
623 Alternatively the symbolic reference to be deleted.
624 """
625 full_ref_path = cls.to_full_path(path)
626 abs_path = os.path.join(repo.common_dir, full_ref_path)
627 if os.path.exists(abs_path):
628 os.remove(abs_path)
629 else:
630 # Check packed refs.
631 pack_file_path = cls._get_packed_refs_path(repo)
632 try:
633 with open(pack_file_path, "rb") as reader:
634 new_lines = []
635 made_change = False
636 dropped_last_line = False
637 for line_bytes in reader:
638 line = line_bytes.decode(defenc)
639 _, _, line_ref = line.partition(" ")
640 line_ref = line_ref.strip()
641 # Keep line if it is a comment or if the ref to delete is not in
642 # the line.
643 # If we deleted the last line and this one is a tag-reference
644 # object, we drop it as well.
645 if (line.startswith("#") or full_ref_path != line_ref) and (
646 not dropped_last_line or dropped_last_line and not line.startswith("^")
647 ):
648 new_lines.append(line)
649 dropped_last_line = False
650 continue
651 # END skip comments and lines without our path
653 # Drop this line.
654 made_change = True
655 dropped_last_line = True
657 # Write the new lines.
658 if made_change:
659 # Binary writing is required, otherwise Windows will open the file
660 # in text mode and change LF to CRLF!
661 with open(pack_file_path, "wb") as fd:
662 fd.writelines(line.encode(defenc) for line in new_lines)
664 except OSError:
665 pass # It didn't exist at all.
667 # Delete the reflog.
668 reflog_path = RefLog.path(cls(repo, full_ref_path))
669 if os.path.isfile(reflog_path):
670 os.remove(reflog_path)
671 # END remove reflog
673 @classmethod
674 def _create(
675 cls: Type[T_References],
676 repo: "Repo",
677 path: PathLike,
678 resolve: bool,
679 reference: Union["SymbolicReference", str],
680 force: bool,
681 logmsg: Union[str, None] = None,
682 ) -> T_References:
683 """Internal method used to create a new symbolic reference.
685 If `resolve` is ``False``, the reference will be taken as is, creating a proper
686 symbolic reference. Otherwise it will be resolved to the corresponding object
687 and a detached symbolic reference will be created instead.
688 """
689 git_dir = _git_dir(repo, path)
690 full_ref_path = cls.to_full_path(path)
691 abs_ref_path = os.path.join(git_dir, full_ref_path)
693 # Figure out target data.
694 target = reference
695 if resolve:
696 target = repo.rev_parse(str(reference))
698 if not force and os.path.isfile(abs_ref_path):
699 target_data = str(target)
700 if isinstance(target, SymbolicReference):
701 target_data = str(target.path)
702 if not resolve:
703 target_data = "ref: " + target_data
704 with open(abs_ref_path, "rb") as fd:
705 existing_data = fd.read().decode(defenc).strip()
706 if existing_data != target_data:
707 raise OSError(
708 "Reference at %r does already exist, pointing to %r, requested was %r"
709 % (full_ref_path, existing_data, target_data)
710 )
711 # END no force handling
713 ref = cls(repo, full_ref_path)
714 ref.set_reference(target, logmsg)
715 return ref
717 @classmethod
718 def create(
719 cls: Type[T_References],
720 repo: "Repo",
721 path: PathLike,
722 reference: Union["SymbolicReference", str] = "HEAD",
723 logmsg: Union[str, None] = None,
724 force: bool = False,
725 **kwargs: Any,
726 ) -> T_References:
727 """Create a new symbolic reference: a reference pointing to another reference.
729 :param repo:
730 Repository to create the reference in.
732 :param path:
733 Full path at which the new symbolic reference is supposed to be created at,
734 e.g. ``NEW_HEAD`` or ``symrefs/my_new_symref``.
736 :param reference:
737 The reference which the new symbolic reference should point to.
738 If it is a commit-ish, the symbolic ref will be detached.
740 :param force:
741 If ``True``, force creation even if a symbolic reference with that name
742 already exists. Raise :exc:`OSError` otherwise.
744 :param logmsg:
745 If not ``None``, the message to append to the reflog.
746 If ``None``, no reflog entry is written.
748 :return:
749 Newly created symbolic reference
751 :raise OSError:
752 If a (Symbolic)Reference with the same name but different contents already
753 exists.
755 :note:
756 This does not alter the current HEAD, index or working tree.
757 """
758 return cls._create(repo, path, cls._resolve_ref_on_create, reference, force, logmsg)
760 def rename(self, new_path: PathLike, force: bool = False) -> "SymbolicReference":
761 """Rename self to a new path.
763 :param new_path:
764 Either a simple name or a full path, e.g. ``new_name`` or
765 ``features/new_name``.
766 The prefix ``refs/`` is implied for references and will be set as needed.
767 In case this is a symbolic ref, there is no implied prefix.
769 :param force:
770 If ``True``, the rename will succeed even if a head with the target name
771 already exists. It will be overwritten in that case.
773 :return:
774 self
776 :raise OSError:
777 If a file at path but with different contents already exists.
778 """
779 new_path = self.to_full_path(new_path)
780 if self.path == new_path:
781 return self
783 new_abs_path = os.path.join(_git_dir(self.repo, new_path), new_path)
784 cur_abs_path = os.path.join(_git_dir(self.repo, self.path), self.path)
785 if os.path.isfile(new_abs_path):
786 if not force:
787 # If they point to the same file, it's not an error.
788 with open(new_abs_path, "rb") as fd1:
789 f1 = fd1.read().strip()
790 with open(cur_abs_path, "rb") as fd2:
791 f2 = fd2.read().strip()
792 if f1 != f2:
793 raise OSError("File at path %r already exists" % new_abs_path)
794 # else: We could remove ourselves and use the other one, but...
795 # ...for clarity, we just continue as usual.
796 # END not force handling
797 os.remove(new_abs_path)
798 # END handle existing target file
800 dname = os.path.dirname(new_abs_path)
801 if not os.path.isdir(dname):
802 os.makedirs(dname)
803 # END create directory
805 os.rename(cur_abs_path, new_abs_path)
806 self.path = new_path
808 return self
810 @classmethod
811 def _iter_items(
812 cls: Type[T_References], repo: "Repo", common_path: Union[PathLike, None] = None
813 ) -> Iterator[T_References]:
814 if common_path is None:
815 common_path = cls._common_path_default
816 rela_paths = set()
818 # Walk loose refs.
819 # Currently we do not follow links.
820 for root, dirs, files in os.walk(join_path_native(repo.common_dir, common_path)):
821 if "refs" not in root.split(os.sep): # Skip non-refs subfolders.
822 refs_id = [d for d in dirs if d == "refs"]
823 if refs_id:
824 dirs[0:] = ["refs"]
825 # END prune non-refs folders
827 for f in files:
828 if f == "packed-refs":
829 continue
830 abs_path = to_native_path_linux(join_path(root, f))
831 rela_paths.add(abs_path.replace(to_native_path_linux(repo.common_dir) + "/", ""))
832 # END for each file in root directory
833 # END for each directory to walk
835 # Read packed refs.
836 for _sha, rela_path in cls._iter_packed_refs(repo):
837 if rela_path.startswith(str(common_path)):
838 rela_paths.add(rela_path)
839 # END relative path matches common path
840 # END packed refs reading
842 # Yield paths in sorted order.
843 for path in sorted(rela_paths):
844 try:
845 yield cls.from_path(repo, path)
846 except ValueError:
847 continue
848 # END for each sorted relative refpath
850 @classmethod
851 def iter_items(
852 cls: Type[T_References],
853 repo: "Repo",
854 common_path: Union[PathLike, None] = None,
855 *args: Any,
856 **kwargs: Any,
857 ) -> Iterator[T_References]:
858 """Find all refs in the repository.
860 :param repo:
861 The :class:`~git.repo.base.Repo`.
863 :param common_path:
864 Optional keyword argument to the path which is to be shared by all returned
865 Ref objects.
866 Defaults to class specific portion if ``None``, ensuring that only refs
867 suitable for the actual class are returned.
869 :return:
870 A list of :class:`SymbolicReference`, each guaranteed to be a symbolic ref
871 which is not detached and pointing to a valid ref.
873 The list is lexicographically sorted. The returned objects are instances of
874 concrete subclasses, such as :class:`~git.refs.head.Head` or
875 :class:`~git.refs.tag.TagReference`.
876 """
877 return (r for r in cls._iter_items(repo, common_path) if r.__class__ is SymbolicReference or not r.is_detached)
879 @classmethod
880 def from_path(cls: Type[T_References], repo: "Repo", path: PathLike) -> T_References:
881 """Make a symbolic reference from a path.
883 :param path:
884 Full ``.git``-directory-relative path name to the Reference to instantiate.
886 :note:
887 Use :meth:`to_full_path` if you only have a partial path of a known
888 Reference type.
890 :return:
891 Instance of type :class:`~git.refs.reference.Reference`,
892 :class:`~git.refs.head.Head`, or :class:`~git.refs.tag.Tag`, depending on
893 the given path.
894 """
895 if not path:
896 raise ValueError("Cannot create Reference from %r" % path)
898 # Names like HEAD are inserted after the refs module is imported - we have an
899 # import dependency cycle and don't want to import these names in-function.
900 from . import HEAD, Head, RemoteReference, TagReference, Reference
902 for ref_type in (
903 HEAD,
904 Head,
905 RemoteReference,
906 TagReference,
907 Reference,
908 SymbolicReference,
909 ):
910 try:
911 instance: T_References
912 instance = ref_type(repo, path)
913 if instance.__class__ is SymbolicReference and instance.is_detached:
914 raise ValueError("SymbolicRef was detached, we drop it")
915 else:
916 return instance
918 except ValueError:
919 pass
920 # END exception handling
921 # END for each type to try
922 raise ValueError("Could not find reference type suitable to handle path %r" % path)
924 def is_remote(self) -> bool:
925 """:return: True if this symbolic reference points to a remote branch"""
926 return str(self.path).startswith(self._remote_common_path_default + "/")