11import os
2- from git .objects import Commit
2+ from git .objects import Object , Commit
33from git .util import (
44join_path ,
55join_path_native ,
@@ -30,6 +30,7 @@ class SymbolicReference(object):
3030A 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):
107108intermediate references as required
108109:param repo: the repository containing the reference at ref_path"""
109110while 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 )
112112if hexsha is not None :
113113return 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
118119rela_path points to, or None. target_ref_path is the reference we
119120point to, or None"""
120121tokens = None
121122try :
122- fp = open (self . abspath ,'r' )
123+ fp = open (join ( repo . git_dir , path ) ,'r' )
123124value = fp .read ().rstrip ()
124125fp .close ()
125126tokens = value .split (" " )
126127except (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
132133tokens = (sha ,path )
133134break
134135# END for each packed ref
135136# END handle packed refs
136137
137138if 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 ?
141142if tokens [0 ]== 'ref:' :
142143return (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 ]):
146147return (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+
150160def _get_commit (self ):
151161"""
152162:return:
153163Commit 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
164171def 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.
166183If 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+
170194is_detached = True
171195try :
172196is_detached = self .is_detached
@@ -175,56 +199,66 @@ def set_commit(self, commit, msg = None):
175199# END handle non-existing ones
176200
177201if 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
183207commit = 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
185210def _get_reference (self ):
186211""":return: Reference Object we point to
187212:raise TypeError: If this symbolic reference is detached, hence it doesn't point
188213to 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 )
190215if target_ref_path is None :
191216raise TypeError ("%s is a detached symbolic reference as it points to %r" % (self ,sha ))
192217return self .from_path (self .repo ,target_ref_path )
193218
194219def 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- Otherwisea commmit , given asCommit object or refspec, is assumed and if valid,
221+ Otherwisean Object , given asObject instance or refspec, is assumed and if valid,
197222will be set which effectively detaches the refererence if it was a purely
198223symbolic 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.
202229Otherwise, a reflog entry is not written for the changed reference.
203230The 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(...)``"""
206235write_value = None
236+ obj = None
207237if isinstance (ref ,SymbolicReference ):
208238write_value = "ref: %s" % ref .path
209- elif isinstance (ref ,Commit ):
239+ elif isinstance (ref ,Object ):
240+ obj = ref
210241write_value = ref .hexsha
211- else :
242+ elif isinstance ( ref , basestring ) :
212243try :
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+
224258oldbinsha = None
225259if msg is not None :
226260try :
227- oldhexsha = self .commit .binsha
261+ oldbinsha = self .commit .binsha
228262except ValueError :
229263oldbinsha = Commit .NULL_BIN_SHA
230264#END handle non-existing
@@ -247,14 +281,14 @@ def set_reference(self, ref, msg = None):
247281# aliased reference
248282reference = property (_get_reference ,set_reference ,doc = "Returns the Reference we point to" )
249283ref = reference
250-
284+
251285def is_valid (self ):
252286"""
253287:return:
254288True if the reference is valid, hence it can be read and points to
255289a valid object or reference."""
256290try :
257- self .commit
291+ self .object
258292except (OSError ,ValueError ):
259293return False
260294else :
@@ -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
289323will 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 ,
293327message )
294328