Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/git/refs/reference.py: 73%

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

55 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__ = ["Reference"] 

5 

6from git.util import IterableObj, LazyMixin 

7 

8from .symbolic import SymbolicReference, T_References 

9 

10# typing ------------------------------------------------------------------ 

11 

12from typing import Any, Callable, Iterator, TYPE_CHECKING, Type, Union 

13 

14from git.types import AnyGitObject, PathLike, _T 

15 

16if TYPE_CHECKING: 

17 from git.repo import Repo 

18 

19# ------------------------------------------------------------------------------ 

20 

21# { Utilities 

22 

23 

24def require_remote_ref_path(func: Callable[..., _T]) -> Callable[..., _T]: 

25 """A decorator raising :exc:`ValueError` if we are not a valid remote, based on the 

26 path.""" 

27 

28 def wrapper(self: T_References, *args: Any) -> _T: 

29 if not self.is_remote(): 

30 raise ValueError("ref path does not point to a remote reference: %s" % self.path) 

31 return func(self, *args) 

32 

33 # END wrapper 

34 wrapper.__name__ = func.__name__ 

35 return wrapper 

36 

37 

38# } END utilities 

39 

40 

41class Reference(SymbolicReference, LazyMixin, IterableObj): 

42 """A named reference to any object. 

43 

44 Subclasses may apply restrictions though, e.g., a :class:`~git.refs.head.Head` can 

45 only point to commits. 

46 """ 

47 

48 __slots__ = () 

49 

50 _points_to_commits_only = False 

51 _resolve_ref_on_create = True 

52 _common_path_default = "refs" 

53 

54 def __init__(self, repo: "Repo", path: PathLike, check_path: bool = True) -> None: 

55 """Initialize this instance. 

56 

57 :param repo: 

58 Our parent repository. 

59 

60 :param path: 

61 Path relative to the ``.git/`` directory pointing to the ref in question, 

62 e.g. ``refs/heads/master``. 

63 

64 :param check_path: 

65 If ``False``, you can provide any path. 

66 Otherwise the path must start with the default path prefix of this type. 

67 """ 

68 if check_path and not str(path).startswith(self._common_path_default + "/"): 

69 raise ValueError(f"Cannot instantiate {self.__class__.__name__!r} from path {path}") 

70 self.path: str # SymbolicReference converts to string at the moment. 

71 super().__init__(repo, path) 

72 

73 def __str__(self) -> str: 

74 return self.name 

75 

76 # { Interface 

77 

78 # @ReservedAssignment 

79 def set_object( 

80 self, 

81 object: Union[AnyGitObject, "SymbolicReference", str], 

82 logmsg: Union[str, None] = None, 

83 ) -> "Reference": 

84 """Special version which checks if the head-log needs an update as well. 

85 

86 :return: 

87 self 

88 """ 

89 oldbinsha = None 

90 if logmsg is not None: 

91 head = self.repo.head 

92 if not head.is_detached and head.ref == self: 

93 oldbinsha = self.commit.binsha 

94 # END handle commit retrieval 

95 # END handle message is set 

96 

97 super().set_object(object, logmsg) 

98 

99 if oldbinsha is not None: 

100 # From refs/files-backend.c in git-source: 

101 # /* 

102 # * Special hack: If a branch is updated directly and HEAD 

103 # * points to it (may happen on the remote side of a push 

104 # * for example) then logically the HEAD reflog should be 

105 # * updated too. 

106 # * A generic solution implies reverse symref information, 

107 # * but finding all symrefs pointing to the given branch 

108 # * would be rather costly for this rare event (the direct 

109 # * update of a branch) to be worth it. So let's cheat and 

110 # * check with HEAD only which should cover 99% of all usage 

111 # * scenarios (even 100% of the default ones). 

112 # */ 

113 self.repo.head.log_append(oldbinsha, logmsg) 

114 # END check if the head 

115 

116 return self 

117 

118 # NOTE: No need to overwrite properties, as the will only work without a the log. 

119 

120 @property 

121 def name(self) -> str: 

122 """ 

123 :return: 

124 (shortest) Name of this reference - it may contain path components 

125 """ 

126 # The first two path tokens can be removed as they are 

127 # refs/heads or refs/tags or refs/remotes. 

128 tokens = self.path.split("/") 

129 if len(tokens) < 3: 

130 return self.path # could be refs/HEAD 

131 return "/".join(tokens[2:]) 

132 

133 @classmethod 

134 def iter_items( 

135 cls: Type[T_References], 

136 repo: "Repo", 

137 common_path: Union[PathLike, None] = None, 

138 *args: Any, 

139 **kwargs: Any, 

140 ) -> Iterator[T_References]: 

141 """Equivalent to 

142 :meth:`SymbolicReference.iter_items <git.refs.symbolic.SymbolicReference.iter_items>`, 

143 but will return non-detached references as well.""" 

144 return cls._iter_items(repo, common_path) 

145 

146 # } END interface 

147 

148 # { Remote Interface 

149 

150 @property 

151 @require_remote_ref_path 

152 def remote_name(self) -> str: 

153 """ 

154 :return: 

155 Name of the remote we are a reference of, such as ``origin`` for a reference 

156 named ``origin/master``. 

157 """ 

158 tokens = self.path.split("/") 

159 # /refs/remotes/<remote name>/<branch_name> 

160 return tokens[2] 

161 

162 @property 

163 @require_remote_ref_path 

164 def remote_head(self) -> str: 

165 """ 

166 :return: 

167 Name of the remote head itself, e.g. ``master``. 

168 

169 :note: 

170 The returned name is usually not qualified enough to uniquely identify a 

171 branch. 

172 """ 

173 tokens = self.path.split("/") 

174 return "/".join(tokens[3:]) 

175 

176 # } END remote interface