@@ -54,13 +54,17 @@ def name(self):
54
54
def _get_path (self ):
55
55
return join_path_native (self .repo .git_dir , self .path )
56
56
57
+ @classmethod
58
+ def _get_packed_refs_path (cls , repo ):
59
+ return os .path .join (repo .git_dir , 'packed-refs' )
60
+
57
61
@classmethod
58
62
def _iter_packed_refs (cls , repo ):
59
63
"""Returns an iterator yielding pairs of sha1/path pairs for the corresponding
60
64
refs.
61
65
NOTE: The packed refs file will be kept open as long as we iterate"""
62
66
try :
63
- fp = open (os . path . join (repo . git_dir , 'packed-refs' ), 'r' )
67
+ fp = open (cls . _get_packed_refs_path (repo ), 'r' )
64
68
for line in fp :
65
69
line = line .strip ()
66
70
if not line :
@@ -87,13 +91,10 @@ def _iter_packed_refs(cls, repo):
87
91
# I believe files are closing themselves on destruction, so it is
88
92
# alright.
89
93
90
- def _get_commit (self ):
91
- """
92
- Returns:
93
- Commit object we point to, works for detached and non-detached
94
- SymbolicReferences
95
- """
96
- # we partially reimplement it to prevent unnecessary file access
94
+ def _get_ref_info (self ):
95
+ """Return: (sha, target_ref_path) if available, the sha the file at
96
+ rela_path points to, or None. target_ref_path is the reference we
97
+ point to, or None"""
97
98
tokens = None
98
99
try :
99
100
fp = open (self ._get_path (), 'r' )
@@ -111,15 +112,30 @@ def _get_commit(self):
111
112
# END for each packed ref
112
113
# END handle packed refs
113
114
114
- # it is a detached reference
115
+ # is it a reference ?
116
+ if tokens [0 ] == 'ref:' :
117
+ return (None , tokens [1 ])
118
+
119
+ # its a commit
115
120
if self .repo .re_hexsha_only .match (tokens [0 ]):
116
- return Commit (self .repo , tokens [0 ])
121
+ return (tokens [0 ], None )
122
+
123
+ raise ValueError ("Failed to parse reference information from %r" % self .path )
124
+
125
+ def _get_commit (self ):
126
+ """
127
+ Returns:
128
+ Commit object we point to, works for detached and non-detached
129
+ SymbolicReferences
130
+ """
131
+ # we partially reimplement it to prevent unnecessary file access
132
+ sha , target_ref_path = self ._get_ref_info ()
117
133
118
- # must be a head ! Git does not allow symbol refs to other things than heads
119
- # Otherwise it would have detached it
120
- if tokens [ 0 ] != "ref:" :
121
- raise ValueError ( "Failed to parse symbolic refernce: wanted 'ref: <hexsha>', got %r" % value )
122
- return Reference .from_path (self .repo , tokens [ 1 ] ).commit
134
+ # it is a detached reference
135
+ if sha :
136
+ return Commit ( self . repo , sha )
137
+
138
+ return Reference .from_path (self .repo , target_ref_path ).commit
123
139
124
140
def _set_commit (self , commit ):
125
141
"""
@@ -138,14 +154,10 @@ def _get_reference(self):
138
154
Returns
139
155
Reference Object we point to
140
156
"""
141
- fp = open (self ._get_path (), 'r' )
142
- try :
143
- tokens = fp .readline ().rstrip ().split (' ' )
144
- if tokens [0 ] != 'ref:' :
145
- raise TypeError ("%s is a detached symbolic reference as it points to %r" % (self , tokens [0 ]))
146
- return Reference .from_path (self .repo , tokens [1 ])
147
- finally :
148
- fp .close ()
157
+ sha , target_ref_path = self ._get_ref_info ()
158
+ if target_ref_path is None :
159
+ raise TypeError ("%s is a detached symbolic reference as it points to %r" % (self , sha ))
160
+ return Reference .from_path (self .repo , target_ref_path )
149
161
150
162
def _set_reference (self , ref ):
151
163
"""
@@ -261,6 +273,40 @@ def delete(cls, repo, path):
261
273
abs_path = os .path .join (repo .git_dir , full_ref_path )
262
274
if os .path .exists (abs_path ):
263
275
os .remove (abs_path )
276
+ else :
277
+ # check packed refs
278
+ pack_file_path = cls ._get_packed_refs_path (repo )
279
+ try :
280
+ reader = open (pack_file_path )
281
+ except (OSError ,IOError ):
282
+ pass # it didnt exist at all
283
+ else :
284
+ new_lines = list ()
285
+ made_change = False
286
+ dropped_last_line = False
287
+ for line in reader :
288
+ # keep line if it is a comment or if the ref to delete is not
289
+ # in the line
290
+ # If we deleted the last line and this one is a tag-reference object,
291
+ # we drop it as well
292
+ if ( line .startswith ('#' ) or full_ref_path not in line ) and \
293
+ ( not dropped_last_line or dropped_last_line and not line .startswith ('^' ) ):
294
+ new_lines .append (line )
295
+ dropped_last_line = False
296
+ continue
297
+ # END skip comments and lines without our path
298
+
299
+ # drop this line
300
+ made_change = True
301
+ dropped_last_line = True
302
+ # END for each line in packed refs
303
+ reader .close ()
304
+
305
+ # write the new lines
306
+ if made_change :
307
+ open (pack_file_path , 'w' ).writelines (new_lines )
308
+ # END open exception handling
309
+ # END handle deletion
264
310
265
311
@classmethod
266
312
def _create (cls , repo , path , resolve , reference , force ):
@@ -367,6 +413,88 @@ def rename(self, new_path, force=False):
367
413
368
414
return self
369
415
416
+ @classmethod
417
+ def _iter_items (cls , repo , common_path = None ):
418
+ if common_path is None :
419
+ common_path = cls ._common_path_default
420
+
421
+ rela_paths = set ()
422
+
423
+ # walk loose refs
424
+ # Currently we do not follow links
425
+ for root , dirs , files in os .walk (join_path_native (repo .git_dir , common_path )):
426
+ if 'refs/' not in root : # skip non-refs subfolders
427
+ refs_id = [ i for i ,d in enumerate (dirs ) if d == 'refs' ]
428
+ if refs_id :
429
+ dirs [0 :] = ['refs' ]
430
+ # END prune non-refs folders
431
+
432
+ for f in files :
433
+ abs_path = to_native_path_linux (join_path (root , f ))
434
+ rela_paths .add (abs_path .replace (to_native_path_linux (repo .git_dir ) + '/' , "" ))
435
+ # END for each file in root directory
436
+ # END for each directory to walk
437
+
438
+ # read packed refs
439
+ for sha , rela_path in cls ._iter_packed_refs (repo ):
440
+ if rela_path .startswith (common_path ):
441
+ rela_paths .add (rela_path )
442
+ # END relative path matches common path
443
+ # END packed refs reading
444
+
445
+ # return paths in sorted order
446
+ for path in sorted (rela_paths ):
447
+ try :
448
+ yield cls .from_path (repo , path )
449
+ except ValueError :
450
+ continue
451
+ # END for each sorted relative refpath
452
+
453
+ @classmethod
454
+ def iter_items (cls , repo , common_path = None ):
455
+ """
456
+ Find all refs in the repository
457
+
458
+ ``repo``
459
+ is the Repo
460
+
461
+ ``common_path``
462
+ Optional keyword argument to the path which is to be shared by all
463
+ returned Ref objects.
464
+ Defaults to class specific portion if None assuring that only
465
+ refs suitable for the actual class are returned.
466
+
467
+ Returns
468
+ git.SymbolicReference[], each of them is guaranteed to be a symbolic
469
+ ref which is not detached.
470
+
471
+ List is lexigraphically sorted
472
+ The returned objects represent actual subclasses, such as Head or TagReference
473
+ """
474
+ return ( r for r in cls ._iter_items (repo , common_path ) if r .__class__ == SymbolicReference or not r .is_detached )
475
+
476
+ @classmethod
477
+ def from_path (cls , repo , path ):
478
+ """
479
+ Return
480
+ Instance of type Reference, Head, or Tag
481
+ depending on the given path
482
+ """
483
+ if not path :
484
+ raise ValueError ("Cannot create Reference from %r" % path )
485
+
486
+ for ref_type in (HEAD , Head , RemoteReference , TagReference , Reference , SymbolicReference ):
487
+ try :
488
+ instance = ref_type (repo , path )
489
+ if instance .__class__ == SymbolicReference and instance .is_detached :
490
+ raise ValueError ("SymbolRef was detached, we drop it" )
491
+ return instance
492
+ except ValueError :
493
+ pass
494
+ # END exception handling
495
+ # END for each type to try
496
+ raise ValueError ("Could not find reference type suitable to handle path %r" % path )
497
+
370
498
371
499
class Reference (SymbolicReference , LazyMixin , Iterable ):
372
500
"""
@@ -431,75 +559,6 @@ def name(self):
431
559
return self .path # could be refs/HEAD
432
560
return '/' .join (tokens [2 :])
433
561
434
- @classmethod
435
- def iter_items (cls , repo , common_path = None , ** kwargs ):
436
- """
437
- Find all refs in the repository
438
-
439
- ``repo``
440
- is the Repo
441
-
442
- ``common_path``
443
- Optional keyword argument to the path which is to be shared by all
444
- returned Ref objects.
445
- Defaults to class specific portion if None assuring that only
446
- refs suitable for the actual class are returned.
447
-
448
- Returns
449
- git.Reference[]
450
-
451
- List is lexigraphically sorted
452
- The returned objects represent actual subclasses, such as Head or TagReference
453
- """
454
- if common_path is None :
455
- common_path = cls ._common_path_default
456
-
457
- rela_paths = set ()
458
-
459
- # walk loose refs
460
- # Currently we do not follow links
461
- for root , dirs , files in os .walk (join_path_native (repo .git_dir , common_path )):
462
- for f in files :
463
- abs_path = to_native_path_linux (join_path (root , f ))
464
- rela_paths .add (abs_path .replace (to_native_path_linux (repo .git_dir ) + '/' , "" ))
465
- # END for each file in root directory
466
- # END for each directory to walk
467
-
468
- # read packed refs
469
- for sha , rela_path in cls ._iter_packed_refs (repo ):
470
- if rela_path .startswith (common_path ):
471
- rela_paths .add (rela_path )
472
- # END relative path matches common path
473
- # END packed refs reading
474
-
475
- # return paths in sorted order
476
- for path in sorted (rela_paths ):
477
- if path .endswith ('/HEAD' ):
478
- continue
479
- # END skip remote heads
480
- yield cls .from_path (repo , path )
481
- # END for each sorted relative refpath
482
-
483
-
484
- @classmethod
485
- def from_path (cls , repo , path ):
486
- """
487
- Return
488
- Instance of type Reference, Head, or Tag
489
- depending on the given path
490
- """
491
- if not path :
492
- raise ValueError ("Cannot create Reference from %r" % path )
493
-
494
- for ref_type in (Head , RemoteReference , TagReference , Reference ):
495
- try :
496
- return ref_type (repo , path )
497
- except ValueError :
498
- pass
499
- # END exception handling
500
- # END for each type to try
501
- raise ValueError ("Could not find reference type suitable to handle path %r" % path )
502
-
503
562
504
563
@classmethod
505
564
def create (cls , repo , path , commit = 'HEAD' , force = False ):
@@ -528,8 +587,15 @@ def create(cls, repo, path, commit='HEAD', force=False ):
528
587
This does not alter the current HEAD, index or Working Tree
529
588
"""
530
589
return cls ._create (repo , path , True , commit , force )
531
-
532
-
590
+
591
+ @classmethod
592
+ def iter_items (cls , repo , common_path = None ):
593
+ """
594
+ Equivalent to SymbolicReference.iter_items, but will return non-detached
595
+ references as well.
596
+ """
597
+ return cls ._iter_items (repo , common_path )
598
+
533
599
534
600
class HEAD (SymbolicReference ):
535
601
"""
@@ -850,12 +916,21 @@ def remote_head(self):
850
916
return '/' .join (tokens [3 :])
851
917
852
918
@classmethod
853
- def delete (cls , repo , * remotes , ** kwargs ):
919
+ def delete (cls , repo , * refs , ** kwargs ):
854
920
"""
855
921
Delete the given remote references.
856
922
857
923
Note
858
924
kwargs are given for compatability with the base class method as we
859
925
should not narrow the signature.
860
926
"""
861
- repo .git .branch ("-d" , "-r" , * remotes )
927
+ repo .git .branch ("-d" , "-r" , * refs )
928
+ # the official deletion method will ignore remote symbolic refs - these
929
+ # are generally ignored in the refs/ folder. We don't though
930
+ # and delete remainders manually
931
+ for ref in refs :
932
+ try :
933
+ os .remove (os .path .join (repo .git_dir , ref .path ))
934
+ except OSError :
935
+ pass
936
+ # END for each ref
0 commit comments