diff options
| author | Sebastian Thiel <byronimo@gmail.com> | 2009-12-04 11:55:01 +0100 | 
|---|---|---|
| committer | Sebastian Thiel <byronimo@gmail.com> | 2009-12-04 12:25:54 +0100 | 
| commit | ac4133f51817145e99b896c7063584d4dd18ad59 (patch) | |
| tree | 911786297fd0fb113ab6ed04c4c07b33998e3064 /lib/git/refs.py | |
| parent | 8e29a91324ca7086b948a9e3a4dbcbb2254a24a7 (diff) | |
| download | gitpython-ac4133f51817145e99b896c7063584d4dd18ad59.tar.gz | |
SymbolicRefence base is now fully aware of pack files in all operations.
Ref(anytype) Iteration was improved such that automatic filtering now also works for SymbolicReferences ( which only return symbolic refs)
Diffstat (limited to 'lib/git/refs.py')
| -rw-r--r-- | lib/git/refs.py | 267 | 
1 files changed, 171 insertions, 96 deletions
| diff --git a/lib/git/refs.py b/lib/git/refs.py index 96c30cd2..cd903174 100644 --- a/lib/git/refs.py +++ b/lib/git/refs.py @@ -55,12 +55,16 @@ class SymbolicReference(object):  		return join_path_native(self.repo.git_dir, self.path)  	@classmethod +	def _get_packed_refs_path(cls, repo): +		return os.path.join(repo.git_dir, 'packed-refs') +		 +	@classmethod  	def _iter_packed_refs(cls, repo):  		"""Returns an iterator yielding pairs of sha1/path pairs for the corresponding  		refs.  		NOTE: The packed refs file will be kept open as long as we iterate"""  		try: -			fp = open(os.path.join(repo.git_dir, 'packed-refs'), 'r') +			fp = open(cls._get_packed_refs_path(repo), 'r')  			for line in fp:  				line = line.strip()  				if not line: @@ -87,13 +91,10 @@ class SymbolicReference(object):  		# I believe files are closing themselves on destruction, so it is   		# alright. -	def _get_commit(self): -		""" -		Returns: -			Commit object we point to, works for detached and non-detached  -			SymbolicReferences -		""" -		# we partially reimplement it to prevent unnecessary file access +	def _get_ref_info(self): +		"""Return: (sha, target_ref_path) if available, the sha the file at  +		rela_path points to, or None. target_ref_path is the reference we  +		point to, or None"""  		tokens = None  		try:  			fp = open(self._get_path(), 'r') @@ -111,15 +112,30 @@ class SymbolicReference(object):  			# END for each packed ref  		# END handle packed refs -		# it is a detached reference +		# is it a reference ? +		if tokens[0] == 'ref:': +			return (None, tokens[1]) +			 +		# its a commit  		if self.repo.re_hexsha_only.match(tokens[0]): -			return Commit(self.repo, tokens[0]) +			return (tokens[0], None) +			 +		raise ValueError("Failed to parse reference information from %r" % self.path) +		 +	def _get_commit(self): +		""" +		Returns: +			Commit object we point to, works for detached and non-detached  +			SymbolicReferences +		""" +		# we partially reimplement it to prevent unnecessary file access +		sha, target_ref_path = self._get_ref_info() -		# must be a head ! Git does not allow symbol refs to other things than heads -		# Otherwise it would have detached it -		if tokens[0] != "ref:": -			raise ValueError("Failed to parse symbolic refernce: wanted 'ref: <hexsha>', got %r" % value) -		return Reference.from_path(self.repo, tokens[1]).commit +		# it is a detached reference +		if sha: +			return Commit(self.repo, sha) +		 +		return Reference.from_path(self.repo, target_ref_path).commit  	def _set_commit(self, commit):  		""" @@ -138,14 +154,10 @@ class SymbolicReference(object):  		Returns  			Reference Object we point to  		""" -		fp = open(self._get_path(), 'r') -		try: -			tokens = fp.readline().rstrip().split(' ') -			if tokens[0] != 'ref:': -				raise TypeError("%s is a detached symbolic reference as it points to %r" % (self, tokens[0])) -			return Reference.from_path(self.repo, tokens[1]) -		finally: -			fp.close() +		sha, target_ref_path = self._get_ref_info() +		if target_ref_path is None: +			raise TypeError("%s is a detached symbolic reference as it points to %r" % (self, sha)) +		return Reference.from_path(self.repo, target_ref_path)  	def _set_reference(self, ref):  		""" @@ -261,6 +273,40 @@ class SymbolicReference(object):  		abs_path = os.path.join(repo.git_dir, full_ref_path)  		if os.path.exists(abs_path):  			os.remove(abs_path) +		else: +			# check packed refs +			pack_file_path = cls._get_packed_refs_path(repo) +			try: +				reader = open(pack_file_path) +			except (OSError,IOError): +				pass # it didnt exist at all +			else: +				new_lines = list() +				made_change = False +				dropped_last_line = False +				for line in reader: +					# keep line if it is a comment or if the ref to delete is not  +					# in the line +					# If we deleted the last line and this one is a tag-reference object,  +					# we drop it as well +					if ( line.startswith('#') or full_ref_path not in line ) and \ +						( not dropped_last_line or dropped_last_line and not line.startswith('^') ): +						new_lines.append(line) +						dropped_last_line = False +						continue +					# END skip comments and lines without our path +					 +					# drop this line +					made_change = True +					dropped_last_line = True +				# END for each line in packed refs +				reader.close() +				 +				# write the new lines +				if made_change: +					open(pack_file_path, 'w').writelines(new_lines) +			# END open exception handling +		# END handle deletion  	@classmethod  	def _create(cls, repo, path, resolve, reference, force): @@ -367,6 +413,88 @@ class SymbolicReference(object):  		return self +	@classmethod +	def _iter_items(cls, repo, common_path = None): +		if common_path is None: +			common_path = cls._common_path_default +		 +		rela_paths = set() +		 +		# walk loose refs +		# Currently we do not follow links  +		for root, dirs, files in os.walk(join_path_native(repo.git_dir, common_path)): +			if 'refs/' not in root:	# skip non-refs subfolders +				refs_id = [ i for i,d in enumerate(dirs) if d == 'refs' ] +				if refs_id: +					dirs[0:] = ['refs'] +			# END prune non-refs folders +			 +			for f in files: +				abs_path = to_native_path_linux(join_path(root, f)) +				rela_paths.add(abs_path.replace(to_native_path_linux(repo.git_dir) + '/', "")) +			# END for each file in root directory +		# END for each directory to walk +		 +		# read packed refs +		for sha, rela_path in cls._iter_packed_refs(repo): +			if rela_path.startswith(common_path): +				rela_paths.add(rela_path) +			# END relative path matches common path +		# END packed refs reading +		 +		# return paths in sorted order +		for path in sorted(rela_paths): +			try: +				yield cls.from_path(repo, path) +			except ValueError: +				continue +		# END for each sorted relative refpath +		 +	@classmethod +	def iter_items(cls, repo, common_path = None): +		""" +		Find all refs in the repository + +		``repo`` +			is the Repo + +		``common_path`` +			Optional keyword argument to the path which is to be shared by all +			returned Ref objects. +			Defaults to class specific portion if None assuring that only  +			refs suitable for the actual class are returned. + +		Returns +			git.SymbolicReference[], each of them is guaranteed to be a symbolic +			ref which is not detached. +			 +			List is lexigraphically sorted +			The returned objects represent actual subclasses, such as Head or TagReference +		""" +		return ( r for r in cls._iter_items(repo, common_path) if r.__class__ == SymbolicReference or not r.is_detached ) +		 +	@classmethod +	def from_path(cls, repo, path): +		""" +		Return +			Instance of type Reference, Head, or Tag +			depending on the given path +		""" +		if not path: +			raise ValueError("Cannot create Reference from %r" % path) +		 +		for ref_type in (HEAD, Head, RemoteReference, TagReference, Reference, SymbolicReference): +			try: +				instance = ref_type(repo, path) +				if instance.__class__ == SymbolicReference and instance.is_detached: +					raise ValueError("SymbolRef was detached, we drop it") +				return instance +			except ValueError: +				pass +			# END exception handling +		# END for each type to try +		raise ValueError("Could not find reference type suitable to handle path %r" % path) +		  class Reference(SymbolicReference, LazyMixin, Iterable):  	""" @@ -431,75 +559,6 @@ class Reference(SymbolicReference, LazyMixin, Iterable):  			return self.path		   # could be refs/HEAD  		return '/'.join(tokens[2:]) -	@classmethod -	def iter_items(cls, repo, common_path = None, **kwargs): -		""" -		Find all refs in the repository - -		``repo`` -			is the Repo - -		``common_path`` -			Optional keyword argument to the path which is to be shared by all -			returned Ref objects. -			Defaults to class specific portion if None assuring that only  -			refs suitable for the actual class are returned. - -		Returns -			git.Reference[] -			 -			List is lexigraphically sorted -			The returned objects represent actual subclasses, such as Head or TagReference -		""" -		if common_path is None: -			common_path = cls._common_path_default -		 -		rela_paths = set() -		 -		# walk loose refs -		# Currently we do not follow links  -		for root, dirs, files in os.walk(join_path_native(repo.git_dir, common_path)): -			for f in files: -				abs_path = to_native_path_linux(join_path(root, f)) -				rela_paths.add(abs_path.replace(to_native_path_linux(repo.git_dir) + '/', "")) -			# END for each file in root directory -		# END for each directory to walk -		 -		# read packed refs -		for sha, rela_path in cls._iter_packed_refs(repo): -			if rela_path.startswith(common_path): -				rela_paths.add(rela_path) -			# END relative path matches common path -		# END packed refs reading -		 -		# return paths in sorted order -		for path in sorted(rela_paths): -			if path.endswith('/HEAD'): -				continue -			# END skip remote heads -			yield cls.from_path(repo, path) -		# END for each sorted relative refpath -		 -		 -	@classmethod -	def from_path(cls, repo, path): -		""" -		Return -			Instance of type Reference, Head, or Tag -			depending on the given path -		""" -		if not path: -			raise ValueError("Cannot create Reference from %r" % path) -		 -		for ref_type in (Head, RemoteReference, TagReference, Reference): -			try: -				return ref_type(repo, path) -			except ValueError: -				pass -			# END exception handling -		# END for each type to try -		raise ValueError("Could not find reference type suitable to handle path %r" % path) -		  	@classmethod  	def create(cls, repo, path, commit='HEAD', force=False ): @@ -528,8 +587,15 @@ class Reference(SymbolicReference, LazyMixin, Iterable):  			This does not alter the current HEAD, index or Working Tree  		"""  		return cls._create(repo, path, True, commit, force) -		 -		 +	 +	@classmethod	 +	def iter_items(cls, repo, common_path = None): +		""" +		Equivalent to SymbolicReference.iter_items, but will return non-detached +		references as well. +		""" +		return cls._iter_items(repo, common_path) +	  class HEAD(SymbolicReference):  	""" @@ -850,7 +916,7 @@ class RemoteReference(Head):  		return '/'.join(tokens[3:])  	@classmethod -	def delete(cls, repo, *remotes, **kwargs): +	def delete(cls, repo, *refs, **kwargs):  		"""  		Delete the given remote references. @@ -858,4 +924,13 @@ class RemoteReference(Head):  			kwargs are given for compatability with the base class method as we   			should not narrow the signature.  		""" -		repo.git.branch("-d", "-r", *remotes) +		repo.git.branch("-d", "-r", *refs) +		# the official deletion method will ignore remote symbolic refs - these  +		# are generally ignored in the refs/ folder. We don't though  +		# and delete remainders manually +		for ref in refs: +			try: +				os.remove(os.path.join(repo.git_dir, ref.path)) +			except OSError: +				pass +		# END for each ref | 
