66"""
77Module containing all ref based objects
88"""
9- from objects .base import Object
9+ import os
10+ from objects import Object , Commit
1011from objects .utils import get_object_type_by_name
1112from utils import LazyMixin , Iterable
1213
@@ -31,6 +32,9 @@ def __init__(self, repo, path, object = None):
3132 ``object``
3233 Object instance, will be retrieved on demand if None
3334 """
35+ if not path .startswith (self ._common_path_default ):
36+ raise ValueError ("Cannot instantiate %s Reference from path %s" % ( self .__class__ .__name__ , path ))
37+
3438 self .repo = repo
3539 self .path = path
3640 if object is not None :
@@ -75,6 +79,17 @@ def object(self):
7579 # have to be dynamic here as we may be a tag which can point to anything
7680 # Our path will be resolved to the hexsha which will be used accordingly
7781 return Object .new (self .repo , self .path )
82+
83+ @property
84+ def commit (self ):
85+ """
86+ Returns
87+ Commit object the head points to
88+ """
89+ commit = self .object
90+ if commit .type != "commit" :
91+ raise TypeError ("Object of reference %s did not point to a commit" % self )
92+ return commit
7893
7994 @classmethod
8095 def iter_items (cls , repo , common_path = None , ** kwargs ):
@@ -112,6 +127,29 @@ def iter_items(cls, repo, common_path = None, **kwargs):
112127
113128 output = repo .git .for_each_ref (common_path , ** options )
114129 return cls ._iter_from_stream (repo , iter (output .splitlines ()))
130+
131+ @classmethod
132+ def from_path (cls , repo , path ):
133+ """
134+ Return
135+ Instance of type Reference, Head, Tag, SymbolicReference or HEAD
136+ depending on the given path
137+ """
138+ if path == 'HEAD' :
139+ return HEAD (repo , path )
140+
141+ if '/' not in path :
142+ return SymbolicReference (repo , path )
143+
144+ for ref_type in (Head , RemoteReference , TagReference , Reference ):
145+ try :
146+ return ref_type (repo , path )
147+ except ValueError :
148+ pass
149+ # END exception handling
150+ # END for each type to try
151+ raise ValueError ("Could not find reference type suitable to handle path %r" % path )
152+
115153
116154 @classmethod
117155 def _iter_from_stream (cls , repo , stream ):
@@ -145,47 +183,150 @@ def _from_string(cls, repo, line):
145183 # return cls(repo, full_path, obj)
146184
147185
148- class Head ( Reference ):
186+ class SymbolicReference ( object ):
149187 """
150- A Head is a named reference to a Commit. Every Head instance contains a name
151- and a Commit object.
152-
153- Examples::
154-
155- >>> repo = Repo("/path/to/repo")
156- >>> head = repo.heads[0]
157-
158- >>> head.name
159- 'master'
160-
161- >>> head.commit
162- <git.Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">
163-
164- >>> head.commit.id
165- '1c09f116cbc2cb4100fb6935bb162daa4723f455'
188+ Represents a special case of a reference such that this reference is symbolic.
189+ It does not point to a specific commit, but to another Head, which itself
190+ specifies a commit.
191+
192+ A typical example for a symbolic reference is HEAD.
166193 """
167- _common_path_default = "refs/heads"
194+ __slots__ = ( "repo" , "name" )
168195
196+ def __init__ (self , repo , name ):
197+ if '/' in name :
198+ raise ValueError ("SymbolicReferences are not located within a directory, got %s" % name )
199+ self .repo = repo
200+ self .name = name
201+
202+ def __str__ (self ):
203+ return self .name
204+
205+ def __repr__ (self ):
206+ return '<git.%s "%s">' % (self .__class__ .__name__ , self .name )
207+
208+ def __eq__ (self , other ):
209+ return self .name == other .name
210+
211+ def __ne__ (self , other ):
212+ return not ( self == other )
213+
214+ def __hash__ (self ):
215+ return hash (self .name )
216+
217+ def _get_path (self ):
218+ return os .path .join (self .repo .path , self .name )
219+
169220 @property
170221 def commit (self ):
222+ """
223+ Returns:
224+ Commit object we point to, works for detached and non-detached
225+ SymbolicReferences
226+ """
227+ # we partially reimplement it to prevent unnecessary file access
228+ fp = open (self ._get_path (), 'r' )
229+ value = fp .read ().rstrip ()
230+ fp .close ()
231+ tokens = value .split (" " )
232+
233+ # it is a detached reference
234+ if len (tokens ) == 1 and len (tokens [0 ]) == 40 :
235+ return Commit (self .repo , tokens [0 ])
236+
237+ # must be a head ! Git does not allow symbol refs to other things than heads
238+ # Otherwise it would have detached it
239+ return Head (self .repo , tokens [1 ]).commit
240+
241+ def _get_reference (self ):
171242 """
172243 Returns
173- Commit object the head points to
244+ Reference Object we point to
174245 """
175- return self .object
246+ fp = open (self ._get_path (), 'r' )
247+ try :
248+ tokens = fp .readline ().rstrip ().split (' ' )
249+ if tokens [0 ] != 'ref:' :
250+ raise TypeError ("%s is a detached symbolic reference as it points to %r" % tokens [0 ])
251+ return Reference .from_path (self .repo , tokens [1 ])
252+ finally :
253+ fp .close ()
176254
177- @classmethod
178- def reset (cls , repo , commit = 'HEAD' , index = True , working_tree = False ,
255+ def _set_reference (self , ref ):
256+ """
257+ Set ourselves to the given ref. It will stay a symbol if the ref is a Head.
258+ Otherwise we try to get a commit from it using our interface.
259+
260+ Strings are allowed but will be checked to be sure we have a commit
261+ """
262+ write_value = None
263+ if isinstance (ref , Head ):
264+ write_value = "ref: %s" % ref .path
265+ elif isinstance (ref , Commit ):
266+ write_value = ref .id
267+ else :
268+ try :
269+ write_value = ref .commit .id
270+ except AttributeError :
271+ sha = str (ref )
272+ try :
273+ obj = Object .new (self .repo , sha )
274+ if obj .type != "commit" :
275+ raise TypeError ("Invalid object type behind sha: %s" % sha )
276+ write_value = obj .id
277+ except Exception :
278+ raise ValueError ("Could not extract object from %s" % ref )
279+ # END end try string
280+ # END try commit attribute
281+ fp = open (self ._get_path (), "w" )
282+ try :
283+ fp .write (write_value )
284+ finally :
285+ fp .close ()
286+ # END writing
287+
288+ reference = property (_get_reference , _set_reference , doc = "Returns the Reference we point to" )
289+
290+ # alias
291+ ref = reference
292+
293+ @property
294+ def is_detached (self ):
295+ """
296+ Returns
297+ True if we are a detached reference, hence we point to a specific commit
298+ instead to another reference
299+ """
300+ try :
301+ self .reference
302+ return False
303+ except TypeError :
304+ return True
305+
306+
307+ class HEAD (SymbolicReference ):
308+ """
309+ Special case of a Symbolic Reference as it represents the repository's
310+ HEAD reference.
311+ """
312+ _HEAD_NAME = 'HEAD'
313+ __slots__ = tuple ()
314+
315+ def __init__ (self , repo , name = _HEAD_NAME ):
316+ if name != self ._HEAD_NAME :
317+ raise ValueError ("HEAD instance must point to %r, got %r" % (self ._HEAD_NAME , name ))
318+ super (HEAD , self ).__init__ (repo , name )
319+
320+
321+ def reset (self , commit = 'HEAD' , index = True , working_tree = False ,
179322 paths = None , ** kwargs ):
180323 """
181- Reset the current head to the given commit optionally synchronizing
324+ Reset our HEAD to the given commit optionally synchronizing
182325 the index and working tree.
183326
184- ``repo``
185- Repository containing commit
186-
187327 ``commit``
188- Commit object, Reference Object or string identifying a revision
328+ Commit object, Reference Object or string identifying a revision we
329+ should reset HEAD to.
189330
190331 ``index``
191332 If True, the index will be set to match the given commit. Otherwise
@@ -204,7 +345,7 @@ def reset(cls, repo, commit='HEAD', index=True, working_tree = False,
204345 Additional arguments passed to git-reset.
205346
206347 Returns
207- Head pointing to the specified commit
348+ self
208349 """
209350 mode = "--soft"
210351 if index :
@@ -216,12 +357,34 @@ def reset(cls, repo, commit='HEAD', index=True, working_tree = False,
216357 raise ValueError ( "Cannot reset the working tree if the index is not reset as well" )
217358 # END working tree handling
218359
219- repo .git .reset (mode , commit , paths , ** kwargs )
360+ self . repo .git .reset (mode , commit , paths , ** kwargs )
220361
221- # we always point to the active branch as it is the one changing
222- return repo .active_branch
362+ return self
363+
364+
365+ class Head (Reference ):
366+ """
367+ A Head is a named reference to a Commit. Every Head instance contains a name
368+ and a Commit object.
369+
370+ Examples::
371+
372+ >>> repo = Repo("/path/to/repo")
373+ >>> head = repo.heads[0]
374+
375+ >>> head.name
376+ 'master'
377+
378+ >>> head.commit
379+ <git.Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">
380+
381+ >>> head.commit.id
382+ '1c09f116cbc2cb4100fb6935bb162daa4723f455'
383+ """
384+ _common_path_default = "refs/heads"
385+
223386
224- class TagReference (Head ):
387+ class TagReference (Reference ):
225388 """
226389 Class representing a lightweight tag reference which either points to a commit
227390 or to a tag object. In the latter case additional information, like the signature
@@ -230,7 +393,7 @@ class TagReference(Head):
230393 This tag object will always point to a commit object, but may carray additional
231394 information in a tag object::
232395
233- tagref = TagRef .list_items(repo)[0]
396+ tagref = TagReference .list_items(repo)[0]
234397 print tagref.commit.message
235398 if tagref.tag is not None:
236399 print tagref.tag.message
0 commit comments