summaryrefslogtreecommitdiff
path: root/lib/git/refs.py
blob: 9754f65d33f4567ac2166b9751b7c3b9d8969a52 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# refs.py
# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
"""
Module containing all ref based objects
"""
from objects.base import Object
from objects.utils import get_object_type_by_name
from utils import LazyMixin, Iterable

class Ref(LazyMixin, Iterable):
	"""
	Represents a named reference to any object
	"""
	__slots__ = ("repo", "path", "object")
	
	def __init__(self, repo, path, object = None):
		"""
		Initialize this instance
		``repo``
			Our parent repository
		
		``path``
			Path relative to the .git/ directory pointing to the ref in question, i.e.
			refs/heads/master
			
		``object``
			Object instance, will be retrieved on demand if None
		"""
		self.repo = repo
		self.path = path
		if object is not None:
			self.object = object
		
	def _set_cache_(self, attr):
		if attr == "object":
			# have to be dynamic here as we may be a tag which can point to anything
			# it uses our path to stay dynamic
			typename, size = self.repo.git.get_object_header(self.path)
			# explicitly do not set the size as it may change if the our ref path points 
			# at some other place when the head changes for instance ... 
			self.object = get_object_type_by_name(typename)(self.repo, self.path)
		else:
			super(Ref, self)._set_cache_(attr)
		
	def __str__(self):
		return self.name
		
	def __repr__(self):
		return '<git.%s "%s">' % (self.__class__.__name__, self.path)
		
	def __eq__(self, other):
		return self.path == other.path and self.object == other.object
		
	def __ne__(self, other):
		return not ( self == other )
		
	def __hash__(self):
		return hash(self.path)
		
	@property
	def name(self):
		"""
		Returns
			(shortest) Name of this reference - it may contain path components
		"""
		# first two path tokens are can be removed as they are 
		# refs/heads or refs/tags or refs/remotes
		tokens = self.path.split('/')
		if len(tokens) < 3:
			return self.path	# could be refs/HEAD
		
		return '/'.join(tokens[2:])
		
	@classmethod
	def iter_items(cls, repo, common_path = "refs", **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

		``kwargs``
			Additional options given as keyword arguments, will be passed
			to git-for-each-ref

		Returns
			git.Ref[]
			
			List is sorted by committerdate
			The returned objects are compatible to the Ref base, but represent the 
			actual type, such as Head or Tag
		"""

		options = {'sort': "committerdate",
				   'format': "%(refname)%00%(objectname)%00%(objecttype)%00%(objectsize)"}
				   
		options.update(kwargs)

		output = repo.git.for_each_ref(common_path, **options)
		return cls._iter_from_stream(repo, iter(output.splitlines()))

	@classmethod
	def _iter_from_stream(cls, repo, stream):
		""" Parse out ref information into a list of Ref compatible objects
		Returns git.Ref[] list of Ref objects """
		heads = []

		for line in stream:
			heads.append(cls._from_string(repo, line))

		return heads

	@classmethod
	def _from_string(cls, repo, line):
		""" Create a new Ref instance from the given string.
		Format
			name: [a-zA-Z_/]+
			<null byte>
			id: [0-9A-Fa-f]{40}
		Returns git.Head """
		full_path, hexsha, type_name, object_size = line.split("\x00")
		
		# No, we keep the object dynamic by allowing it to be retrieved by
		# our path on demand - due to perstent commands it is fast
		return cls(repo, full_path)
		
		# obj = get_object_type_by_name(type_name)(repo, hexsha)
		# obj.size = object_size
		# return cls(repo, full_path, obj)
		

class Head(Ref):
	"""
	A Head is a named reference to a Commit. Every Head instance contains a name
	and a Commit object.

	Examples::

		>>> repo = Repo("/path/to/repo")
		>>> head = repo.heads[0]

		>>> head.name		
		'master'

		>>> head.commit		
		<git.Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">

		>>> head.commit.id
		'1c09f116cbc2cb4100fb6935bb162daa4723f455'
	"""

	@property
	def commit(self):
		"""
		Returns
			Commit object the head points to
		"""
		return self.object
		
	@classmethod
	def iter_items(cls, repo, common_path = "refs/heads", **kwargs):
		"""
		Returns
			Iterator yielding Head items
			
		For more documentation, please refer to git.base.Ref.list_items
		"""
		return super(Head,cls).iter_items(repo, common_path, **kwargs)

	def __repr__(self):
		return '<git.Head "%s">' % self.name
		
		

class TagRef(Ref):
	"""
	Class representing a lightweight tag reference which either points to a commit 
	or to a tag object. In the latter case additional information, like the signature
	or the tag-creator, is available.
	
	This tag object will always point to a commit object, but may carray additional
	information in a tag object::
	
	 tagref = TagRef.list_items(repo)[0]
	 print tagref.commit.message
	 if tagref.tag is not None:
		print tagref.tag.message
	"""
	
	__slots__ = tuple()
	
	@property
	def commit(self):
		"""
		Returns
			Commit object the tag ref points to
		"""
		if self.object.type == "commit":
			return self.object
		elif self.object.type == "tag":
			# it is a tag object which carries the commit as an object - we can point to anything
			return self.object.object
		else:
			raise ValueError( "Tag %s points to a Blob or Tree - have never seen that before" % self )  

	@property
	def tag(self):
		"""
		Returns
			Tag object this tag ref points to or None in case 
			we are a light weight tag
		"""
		if self.object.type == "tag":
			return self.object
		return None

	@classmethod
	def iter_items(cls, repo, common_path = "refs/tags", **kwargs):
		"""
		Returns
			Iterator yielding commit items
			
		For more documentation, please refer to git.base.Ref.list_items
		"""
		return super(TagRef,cls).iter_items(repo, common_path, **kwargs)
		
		
# provide an alias
Tag = TagRef