@@ -54,13 +54,17 @@ def name(self):
5454 def _get_path (self ):
5555 return join_path_native (self .repo .git_dir , self .path )
5656
57+ @classmethod
58+ def _get_packed_refs_path (cls , repo ):
59+ return os .path .join (repo .git_dir , 'packed-refs' )
60+
5761 @classmethod
5862 def _iter_packed_refs (cls , repo ):
5963 """Returns an iterator yielding pairs of sha1/path pairs for the corresponding
6064 refs.
6165 NOTE: The packed refs file will be kept open as long as we iterate"""
6266 try :
63- fp = open (os . path . join (repo . git_dir , 'packed-refs' ), 'r' )
67+ fp = open (cls . _get_packed_refs_path (repo ), 'r' )
6468 for line in fp :
6569 line = line .strip ()
6670 if not line :
@@ -87,13 +91,10 @@ def _iter_packed_refs(cls, repo):
8791 # I believe files are closing themselves on destruction, so it is
8892 # alright.
8993
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"""
9798 tokens = None
9899 try :
99100 fp = open (self ._get_path (), 'r' )
@@ -111,15 +112,30 @@ def _get_commit(self):
111112 # END for each packed ref
112113 # END handle packed refs
113114
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
115120 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 ()
117133
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
123139
124140 def _set_commit (self , commit ):
125141 """
@@ -138,14 +154,10 @@ def _get_reference(self):
138154 Returns
139155 Reference Object we point to
140156 """
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 )
149161
150162 def _set_reference (self , ref ):
151163 """
@@ -261,6 +273,40 @@ def delete(cls, repo, path):
261273 abs_path = os .path .join (repo .git_dir , full_ref_path )
262274 if os .path .exists (abs_path ):
263275 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
264310
265311 @classmethod
266312 def _create (cls , repo , path , resolve , reference , force ):
@@ -367,6 +413,88 @@ def rename(self, new_path, force=False):
367413
368414 return self
369415
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+
370498
371499class Reference (SymbolicReference , LazyMixin , Iterable ):
372500 """
@@ -431,75 +559,6 @@ def name(self):
431559 return self .path # could be refs/HEAD
432560 return '/' .join (tokens [2 :])
433561
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-
503562
504563 @classmethod
505564 def create (cls , repo , path , commit = 'HEAD' , force = False ):
@@ -528,8 +587,15 @@ def create(cls, repo, path, commit='HEAD', force=False ):
528587 This does not alter the current HEAD, index or Working Tree
529588 """
530589 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+
533599
534600class HEAD (SymbolicReference ):
535601 """
@@ -850,12 +916,21 @@ def remote_head(self):
850916 return '/' .join (tokens [3 :])
851917
852918 @classmethod
853- def delete (cls , repo , * remotes , ** kwargs ):
919+ def delete (cls , repo , * refs , ** kwargs ):
854920 """
855921 Delete the given remote references.
856922
857923 Note
858924 kwargs are given for compatability with the base class method as we
859925 should not narrow the signature.
860926 """
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