3
3
from git .util import (
4
4
join_path ,
5
5
join_path_native ,
6
- to_native_path_linux
6
+ to_native_path_linux ,
7
+ assure_directory_exists
7
8
)
8
9
9
10
from gitdb .util import (
@@ -28,6 +29,7 @@ class SymbolicReference(object):
28
29
29
30
A typical example for a symbolic reference is HEAD."""
30
31
__slots__ = ("repo" , "path" )
32
+ _resolve_ref_on_create = False
31
33
_common_path_default = ""
32
34
_id_attribute_ = "name"
33
35
@@ -58,7 +60,8 @@ def name(self):
58
60
is the path itself."""
59
61
return self .path
60
62
61
- def _abs_path (self ):
63
+ @property
64
+ def abspath (self ):
62
65
return join_path_native (self .repo .git_dir , self .path )
63
66
64
67
@classmethod
@@ -116,7 +119,7 @@ def _get_ref_info(self):
116
119
point to, or None"""
117
120
tokens = None
118
121
try :
119
- fp = open (self ._abs_path () , 'r' )
122
+ fp = open (self .abspath , 'r' )
120
123
value = fp .read ().rstrip ()
121
124
fp .close ()
122
125
tokens = value .split (" " )
@@ -158,37 +161,48 @@ def _get_commit(self):
158
161
159
162
return self .from_path (self .repo , target_ref_path ).commit
160
163
161
- def _set_commit (self , commit ):
164
+ def set_commit (self , commit , msg = None ):
162
165
"""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"""
164
170
is_detached = True
165
171
try :
166
172
is_detached = self .is_detached
167
173
except ValueError :
168
174
pass
169
175
# END handle non-existing ones
176
+
170
177
if is_detached :
171
- return self ._set_reference (commit )
178
+ return self .set_reference (commit , msg )
172
179
173
180
# set the commit on our reference
174
- self ._get_reference ().commit = commit
181
+ self ._get_reference ().set_commit ( commit , msg )
175
182
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" )
177
184
178
185
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"""
180
189
sha , target_ref_path = self ._get_ref_info ()
181
190
if target_ref_path is None :
182
191
raise TypeError ("%s is a detached symbolic reference as it points to %r" % (self , sha ))
183
192
return self .from_path (self .repo , target_ref_path )
184
193
185
- def _set_reference (self , ref , msg = None ):
194
+ def set_reference (self , ref , msg = None ):
186
195
"""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.
188
199
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
190
201
: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()"""
192
206
write_value = None
193
207
if isinstance (ref , SymbolicReference ):
194
208
write_value = "ref: %s" % ref .path
@@ -207,33 +221,31 @@ def _set_reference(self, ref, msg = None):
207
221
raise ValueError ("Could not extract object from %s" % ref )
208
222
# END end try string
209
223
# 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
210
245
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
-
234
246
235
247
# 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" )
237
249
ref = reference
238
250
239
251
def is_valid (self ):
@@ -255,7 +267,7 @@ def is_detached(self):
255
267
True if we are a detached reference, hence we point to a specific commit
256
268
instead to another reference"""
257
269
try :
258
- self .reference
270
+ self .ref
259
271
return False
260
272
except TypeError :
261
273
return True
@@ -343,11 +355,18 @@ def delete(cls, repo, path):
343
355
open (pack_file_path , 'w' ).writelines (new_lines )
344
356
# END open exception handling
345
357
# 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
+
346
365
347
366
@classmethod
348
- def _create (cls , repo , path , resolve , reference , force ):
367
+ def _create (cls , repo , path , resolve , reference , force , msg = None ):
349
368
"""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
351
370
a proper symbolic reference. Otherwise it will be resolved to the
352
371
corresponding object and a detached symbolic reference will be created
353
372
instead"""
@@ -365,16 +384,17 @@ def _create(cls, repo, path, resolve, reference, force):
365
384
target_data = target .path
366
385
if not resolve :
367
386
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 ))
370
390
# END no force handling
371
391
372
392
ref = cls (repo , full_ref_path )
373
- ref .reference = target
393
+ ref .set_reference ( target , msg )
374
394
return ref
375
395
376
396
@classmethod
377
- def create (cls , repo , path , reference = 'HEAD' , force = False ):
397
+ def create (cls , repo , path , reference = 'HEAD' , force = False , msg = None ):
378
398
"""Create a new symbolic reference, hence a reference pointing to another reference.
379
399
380
400
:param repo:
@@ -391,14 +411,18 @@ def create(cls, repo, path, reference='HEAD', force=False ):
391
411
if True, force creation even if a symbolic reference with that name already exists.
392
412
Raise OSError otherwise
393
413
414
+ :param msg:
415
+ If not None, the message to append to the reflog. Otherwise no reflog
416
+ entry is written.
417
+
394
418
:return: Newly created symbolic Reference
395
419
396
420
:raise OSError:
397
421
If a (Symbolic)Reference with the same name but different contents
398
422
already exists.
399
423
400
424
: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 )
402
426
403
427
def rename (self , new_path , force = False ):
404
428
"""Rename self to a new path
0 commit comments