11import os
2- from git .objects import Commit
2+ from git .objects import Object , Commit
33from git .util import (
44 join_path ,
55 join_path_native ,
@@ -30,6 +30,7 @@ class SymbolicReference(object):
3030 A typical example for a symbolic reference is HEAD."""
3131 __slots__ = ("repo" , "path" )
3232 _resolve_ref_on_create = False
33+ _points_to_commits_only = True
3334 _common_path_default = ""
3435 _id_attribute_ = "name"
3536
@@ -107,66 +108,89 @@ def dereference_recursive(cls, repo, ref_path):
107108 intermediate references as required
108109 :param repo: the repository containing the reference at ref_path"""
109110 while True :
110- ref = cls (repo , ref_path )
111- hexsha , ref_path = ref ._get_ref_info ()
111+ hexsha , ref_path = cls ._get_ref_info (repo , ref_path )
112112 if hexsha is not None :
113113 return hexsha
114114 # END recursive dereferencing
115115
116- def _get_ref_info (self ):
116+ @classmethod
117+ def _get_ref_info (cls , repo , path ):
117118 """Return: (sha, target_ref_path) if available, the sha the file at
118119 rela_path points to, or None. target_ref_path is the reference we
119120 point to, or None"""
120121 tokens = None
121122 try :
122- fp = open (self . abspath , 'r' )
123+ fp = open (join ( repo . git_dir , path ) , 'r' )
123124 value = fp .read ().rstrip ()
124125 fp .close ()
125126 tokens = value .split (" " )
126127 except (OSError ,IOError ):
127128 # Probably we are just packed, find our entry in the packed refs file
128129 # NOTE: We are not a symbolic ref if we are in a packed file, as these
129130 # are excluded explictly
130- for sha , path in self ._iter_packed_refs (self . repo ):
131- if path != self . path : continue
131+ for sha , path in cls ._iter_packed_refs (repo ):
132+ if path != path : continue
132133 tokens = (sha , path )
133134 break
134135 # END for each packed ref
135136 # END handle packed refs
136137
137138 if tokens is None :
138- raise ValueError ("Reference at %r does not exist" % self . path )
139+ raise ValueError ("Reference at %r does not exist" % path )
139140
140141 # is it a reference ?
141142 if tokens [0 ] == 'ref:' :
142143 return (None , tokens [1 ])
143144
144145 # its a commit
145- if self . repo .re_hexsha_only .match (tokens [0 ]):
146+ if repo .re_hexsha_only .match (tokens [0 ]):
146147 return (tokens [0 ], None )
147148
148- raise ValueError ("Failed to parse reference information from %r" % self .path )
149-
149+ raise ValueError ("Failed to parse reference information from %r" % path )
150+
151+ def _get_object (self ):
152+ """
153+ :return:
154+ The object our ref currently refers to. Refs can be cached, they will
155+ always point to the actual object as it gets re-created on each query"""
156+ # have to be dynamic here as we may be a tag which can point to anything
157+ # Our path will be resolved to the hexsha which will be used accordingly
158+ return Object .new_from_sha (self .repo , hex_to_bin (self .dereference_recursive (self .repo , self .path )))
159+
150160 def _get_commit (self ):
151161 """
152162 :return:
153163 Commit object we point to, works for detached and non-detached
154- SymbolicReferences"""
155- # we partially reimplement it to prevent unnecessary file access
156- hexsha , target_ref_path = self ._get_ref_info ()
157-
158- # it is a detached reference
159- if hexsha :
160- return Commit (self .repo , hex_to_bin (hexsha ))
161-
162- return self .from_path (self .repo , target_ref_path ).commit
164+ SymbolicReferences. The symbolic reference will be dereferenced recursively."""
165+ obj = self ._get_object ()
166+ if obj .type != Commit .type :
167+ raise TypeError ("Symbolic Reference pointed to object %r, commit was required" % obj )
168+ #END handle type
169+ return obj
163170
164171 def set_commit (self , commit , msg = None ):
165- """Set our commit, possibly dereference our symbolic reference first.
172+ """As set_object, but restricts the type of object to be a Commit
173+ :note: To save cycles, we do not yet check whether the given Object
174+ is actually referring to a commit - for now it may be any of our
175+ Object or Reference types, as well as a refspec"""
176+ # may have to check the type ... this is costly as we would have to use
177+ # revparse
178+ self .set_object (commit , msg )
179+
180+
181+ def set_object (self , object , msg = None ):
182+ """Set the object we point to, possibly dereference our symbolic reference first.
166183 If the reference does not exist, it will be created
167184
185+ :param object: a refspec, a SymbolicReference or an Object instance. SymbolicReferences
186+ will be dereferenced beforehand to obtain the object they point to
168187 :param msg: If not None, the message will be used in the reflog entry to be
169- written. Otherwise the reflog is not altered"""
188+ written. Otherwise the reflog is not altered
189+ :note: plain SymbolicReferences may not actually point to objects by convention"""
190+ if isinstance (object , SymbolicReference ):
191+ object = object .object
192+ #END resolve references
193+
170194 is_detached = True
171195 try :
172196 is_detached = self .is_detached
@@ -175,56 +199,66 @@ def set_commit(self, commit, msg = None):
175199 # END handle non-existing ones
176200
177201 if is_detached :
178- return self .set_reference (commit , msg )
202+ return self .set_reference (object , msg )
179203
180204 # set the commit on our reference
181- self ._get_reference ().set_commit ( commit , msg )
205+ self ._get_reference ().set_object ( object , msg )
182206
183207 commit = property (_get_commit , set_commit , doc = "Query or set commits directly" )
208+ object = property (_get_object , set_object , doc = "Return the object our ref currently refers to" )
184209
185210 def _get_reference (self ):
186211 """:return: Reference Object we point to
187212 :raise TypeError: If this symbolic reference is detached, hence it doesn't point
188213 to a reference, but to a commit"""
189- sha , target_ref_path = self ._get_ref_info ()
214+ sha , target_ref_path = self ._get_ref_info (self . repo , self . path )
190215 if target_ref_path is None :
191216 raise TypeError ("%s is a detached symbolic reference as it points to %r" % (self , sha ))
192217 return self .from_path (self .repo , target_ref_path )
193218
194219 def set_reference (self , ref , msg = None ):
195220 """Set ourselves to the given ref. It will stay a symbol if the ref is a Reference.
196- Otherwise a commmit , given as Commit object or refspec, is assumed and if valid,
221+ Otherwise an Object , given as Object instance or refspec, is assumed and if valid,
197222 will be set which effectively detaches the refererence if it was a purely
198223 symbolic one.
199224
200- :param ref: SymbolicReference instance, Commit instance or refspec string
225+ :param ref: SymbolicReference instance, Object instance or refspec string
226+ Only if the ref is a SymbolicRef instance, we will point to it. Everthiny
227+ else is dereferenced to obtain the actual object.
201228 :param msg: If set to a string, the message will be used in the reflog.
202229 Otherwise, a reflog entry is not written for the changed reference.
203230 The previous commit of the entry will be the commit we point to now.
204231
205- See also: log_append()"""
232+ See also: log_append()
233+ :note: This symbolic reference will not be dereferenced. For that, see
234+ ``set_object(...)``"""
206235 write_value = None
236+ obj = None
207237 if isinstance (ref , SymbolicReference ):
208238 write_value = "ref: %s" % ref .path
209- elif isinstance (ref , Commit ):
239+ elif isinstance (ref , Object ):
240+ obj = ref
210241 write_value = ref .hexsha
211- else :
242+ elif isinstance ( ref , basestring ) :
212243 try :
213- write_value = ref .commit .hexsha
214- except AttributeError :
215- try :
216- obj = self .repo .rev_parse (ref + "^{}" ) # optionally deref tags
217- if obj .type != "commit" :
218- raise TypeError ("Invalid object type behind sha: %s" % sha )
219- write_value = obj .hexsha
220- except Exception :
221- raise ValueError ("Could not extract object from %s" % ref )
222- # END end try string
244+ obj = self .repo .rev_parse (ref + "^{}" ) # optionally deref tags
245+ write_value = obj .hexsha
246+ except Exception :
247+ raise ValueError ("Could not extract object from %s" % ref )
248+ # END end try string
249+ else :
250+ raise ValueError ("Unrecognized Value: %r" % ref )
223251 # END try commit attribute
252+
253+ # typecheck
254+ if obj is not None and self ._points_to_commits_only and obj .type != Commit .type :
255+ raise TypeError ("Require commit, got %r" % obj )
256+ #END verify type
257+
224258 oldbinsha = None
225259 if msg is not None :
226260 try :
227- oldhexsha = self .commit .binsha
261+ oldbinsha = self .commit .binsha
228262 except ValueError :
229263 oldbinsha = Commit .NULL_BIN_SHA
230264 #END handle non-existing
@@ -247,14 +281,14 @@ def set_reference(self, ref, msg = None):
247281 # aliased reference
248282 reference = property (_get_reference , set_reference , doc = "Returns the Reference we point to" )
249283 ref = reference
250-
284+
251285 def is_valid (self ):
252286 """
253287 :return:
254288 True if the reference is valid, hence it can be read and points to
255289 a valid object or reference."""
256290 try :
257- self .commit
291+ self .object
258292 except (OSError , ValueError ):
259293 return False
260294 else :
@@ -288,7 +322,7 @@ def log_append(self, oldbinsha, message, newbinsha=None):
288322 :param newbinsha: The sha the ref points to now. If None, our current commit sha
289323 will be used
290324 :return: added RefLogEntry instance"""
291- return RefLog .append_entry (RefLog .path (self ), oldbinsha ,
325+ return RefLog .append_entry (self . repo . config_reader (), RefLog .path (self ), oldbinsha ,
292326 (newbinsha is None and self .commit .binsha ) or newbinsha ,
293327 message )
294328
0 commit comments