33from git .util import (
44 join_path ,
55 join_path_native ,
6- to_native_path_linux
6+ to_native_path_linux ,
7+ assure_directory_exists
78 )
89
910from gitdb .util import (
@@ -28,6 +29,7 @@ class SymbolicReference(object):
2829
2930 A typical example for a symbolic reference is HEAD."""
3031 __slots__ = ("repo" , "path" )
32+ _resolve_ref_on_create = False
3133 _common_path_default = ""
3234 _id_attribute_ = "name"
3335
@@ -58,7 +60,8 @@ def name(self):
5860 is the path itself."""
5961 return self .path
6062
61- def _abs_path (self ):
63+ @property
64+ def abspath (self ):
6265 return join_path_native (self .repo .git_dir , self .path )
6366
6467 @classmethod
@@ -116,7 +119,7 @@ def _get_ref_info(self):
116119 point to, or None"""
117120 tokens = None
118121 try :
119- fp = open (self ._abs_path () , 'r' )
122+ fp = open (self .abspath , 'r' )
120123 value = fp .read ().rstrip ()
121124 fp .close ()
122125 tokens = value .split (" " )
@@ -158,37 +161,48 @@ def _get_commit(self):
158161
159162 return self .from_path (self .repo , target_ref_path ).commit
160163
161- def _set_commit (self , commit ):
164+ def set_commit (self , commit , msg = None ):
162165 """Set our commit, possibly dereference our symbolic reference first.
163- If the reference does not exist, it will be created"""
166+ If the reference does not exist, it will be created
167+
168+ :param msg: If not None, the message will be used in the reflog entry to be
169+ written. Otherwise the reflog is not altered"""
164170 is_detached = True
165171 try :
166172 is_detached = self .is_detached
167173 except ValueError :
168174 pass
169175 # END handle non-existing ones
176+
170177 if is_detached :
171- return self ._set_reference (commit )
178+ return self .set_reference (commit , msg )
172179
173180 # set the commit on our reference
174- self ._get_reference ().commit = commit
181+ self ._get_reference ().set_commit ( commit , msg )
175182
176- commit = property (_get_commit , _set_commit , doc = "Query or set commits directly" )
183+ commit = property (_get_commit , set_commit , doc = "Query or set commits directly" )
177184
178185 def _get_reference (self ):
179- """:return: Reference Object we point to"""
186+ """:return: Reference Object we point to
187+ :raise TypeError: If this symbolic reference is detached, hence it doesn't point
188+ to a reference, but to a commit"""
180189 sha , target_ref_path = self ._get_ref_info ()
181190 if target_ref_path is None :
182191 raise TypeError ("%s is a detached symbolic reference as it points to %r" % (self , sha ))
183192 return self .from_path (self .repo , target_ref_path )
184193
185- def _set_reference (self , ref , msg = None ):
194+ def set_reference (self , ref , msg = None ):
186195 """Set ourselves to the given ref. It will stay a symbol if the ref is a Reference.
187- Otherwise we try to get a commit from it using our interface.
196+ Otherwise a commmit, given as Commit object or refspec, is assumed and if valid,
197+ will be set which effectively detaches the refererence if it was a purely
198+ symbolic one.
188199
189- Strings are allowed but will be checked to be sure we have a commit
200+ :param ref: SymbolicReference instance, Commit instance or refspec string
190201 :param msg: If set to a string, the message will be used in the reflog.
191- Otherwise, a reflog entry is not written for the changed reference"""
202+ Otherwise, a reflog entry is not written for the changed reference.
203+ The previous commit of the entry will be the commit we point to now.
204+
205+ See also: log_append()"""
192206 write_value = None
193207 if isinstance (ref , SymbolicReference ):
194208 write_value = "ref: %s" % ref .path
@@ -207,33 +221,31 @@ def _set_reference(self, ref, msg = None):
207221 raise ValueError ("Could not extract object from %s" % ref )
208222 # END end try string
209223 # END try commit attribute
224+ oldbinsha = None
225+ if msg is not None :
226+ try :
227+ oldhexsha = self .commit .binsha
228+ except ValueError :
229+ oldbinsha = Commit .NULL_BIN_SHA
230+ #END handle non-existing
231+ #END retrieve old hexsha
232+
233+ fpath = self .abspath
234+ assure_directory_exists (fpath , is_file = True )
235+
236+ lfd = LockedFD (fpath )
237+ fd = lfd .open (write = True , stream = True )
238+ fd .write (write_value )
239+ lfd .commit ()
240+
241+ # Adjust the reflog
242+ if msg is not None :
243+ self .log_append (oldbinsha , msg )
244+ #END handle reflog
210245
211- # if we are writing a ref, use symbolic ref to get the reflog and more
212- # checking
213- # Otherwise we detach it and have to do it manually. Besides, this works
214- # recursively automaitcally, but should be replaced with a python implementation
215- # soon
216- if write_value .startswith ('ref:' ):
217- self .repo .git .symbolic_ref (self .path , write_value [5 :])
218- return
219- # END non-detached handling
220-
221- path = self ._abs_path ()
222- directory = dirname (path )
223- if not isdir (directory ):
224- os .makedirs (directory )
225-
226- # TODO: Write using LockedFD
227- fp = open (path , "wb" )
228- try :
229- fp .write (write_value )
230- finally :
231- fp .close ()
232- # END writing
233-
234246
235247 # aliased reference
236- reference = property (_get_reference , _set_reference , doc = "Returns the Reference we point to" )
248+ reference = property (_get_reference , set_reference , doc = "Returns the Reference we point to" )
237249 ref = reference
238250
239251 def is_valid (self ):
@@ -255,7 +267,7 @@ def is_detached(self):
255267 True if we are a detached reference, hence we point to a specific commit
256268 instead to another reference"""
257269 try :
258- self .reference
270+ self .ref
259271 return False
260272 except TypeError :
261273 return True
@@ -343,11 +355,18 @@ def delete(cls, repo, path):
343355 open (pack_file_path , 'w' ).writelines (new_lines )
344356 # END open exception handling
345357 # END handle deletion
358+
359+ # delete the reflog
360+ reflog_path = RefLog .path (cls (repo , full_ref_path ))
361+ if os .path .isfile (reflog_path ):
362+ os .remove (reflog_path )
363+ #END remove reflog
364+
346365
347366 @classmethod
348- def _create (cls , repo , path , resolve , reference , force ):
367+ def _create (cls , repo , path , resolve , reference , force , msg = None ):
349368 """internal method used to create a new symbolic reference.
350- If resolve is False,, the reference will be taken as is, creating
369+ If resolve is False, the reference will be taken as is, creating
351370 a proper symbolic reference. Otherwise it will be resolved to the
352371 corresponding object and a detached symbolic reference will be created
353372 instead"""
@@ -365,16 +384,17 @@ def _create(cls, repo, path, resolve, reference, force):
365384 target_data = target .path
366385 if not resolve :
367386 target_data = "ref: " + target_data
368- if open (abs_ref_path , 'rb' ).read ().strip () != target_data :
369- raise OSError ("Reference at %s does already exist" % full_ref_path )
387+ existing_data = open (abs_ref_path , 'rb' ).read ().strip ()
388+ if existing_data != target_data :
389+ raise OSError ("Reference at %r does already exist, pointing to %r, requested was %r" % (full_ref_path , existing_data , target_data ))
370390 # END no force handling
371391
372392 ref = cls (repo , full_ref_path )
373- ref .reference = target
393+ ref .set_reference ( target , msg )
374394 return ref
375395
376396 @classmethod
377- def create (cls , repo , path , reference = 'HEAD' , force = False ):
397+ def create (cls , repo , path , reference = 'HEAD' , force = False , msg = None ):
378398 """Create a new symbolic reference, hence a reference pointing to another reference.
379399
380400 :param repo:
@@ -391,14 +411,18 @@ def create(cls, repo, path, reference='HEAD', force=False ):
391411 if True, force creation even if a symbolic reference with that name already exists.
392412 Raise OSError otherwise
393413
414+ :param msg:
415+ If not None, the message to append to the reflog. Otherwise no reflog
416+ entry is written.
417+
394418 :return: Newly created symbolic Reference
395419
396420 :raise OSError:
397421 If a (Symbolic)Reference with the same name but different contents
398422 already exists.
399423
400424 :note: This does not alter the current HEAD, index or Working Tree"""
401- return cls ._create (repo , path , False , reference , force )
425+ return cls ._create (repo , path , cls . _resolve_ref_on_create , reference , force , msg )
402426
403427 def rename (self , new_path , force = False ):
404428 """Rename self to a new path
0 commit comments