summaryrefslogtreecommitdiff
path: root/lib/git/base.py
blob: 22c7349156ebedda7accad09cb062a1d3b930dd6 (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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# base.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
import os

class LazyMixin(object):
	lazy_properties = []
	
	__slots__ = "__baked__"
	
	def __init__(self):
		self.__baked__ = False

	def __getattribute__(self, attr):
		val = object.__getattribute__(self, attr)
		if val is not None:
			return val
		else:
			self.__prebake__()
			return object.__getattribute__(self, attr)

	def __bake__(self):
		""" This method should be overridden in the derived class. """
		raise NotImplementedError(" '__bake__' method has not been implemented.")

	def __prebake__(self):
		if self.__baked__:
			return
		self.__bake__()
		self.__baked__ = True

	def __bake_it__(self):
		self.__baked__ = True
		
		
class Object(LazyMixin):
	"""
	Implements an Object which may be Blobs, Trees, Commits and Tags
	"""
	TYPES = ("blob", "tree", "commit", "tag")
	__slots__ = ("repo", "id", "size", "_data_cached" )
	type = None			# to be set by subclass
	
	def __init__(self, repo, id, size=None):
		"""
		Initialize an object by identifying it by its id. All keyword arguments
		will be set on demand if None.
		
		``repo``
			repository this object is located in
			
		``id``
			SHA1 or ref suitable for git-rev-parse
			
		``size``
			Size of the object's data in bytes
		"""
		super(Object,self).__init__()
		self.repo = repo
		self.id = id
		self.size = size
		self._data_cached = type(None)
		
	def __bake__(self):
		"""
		Retrieve object information
		"""
		self.size = int(self.repo.git.cat_file(self.id, s=True).rstrip())
		
	def __eq__(self, other):
		"""
		Returns
			True if the objects have the same SHA1
		"""
		return self.id == other.id
		
	def __ne__(self, other):
		"""
		Returns
			True if the objects do not have the same SHA1
		"""
		return self.id != other.id
		
	def __hash__(self):
		"""
		Returns
			Hash of our id allowing objects to be used in dicts and sets
		"""
		return hash(self.id)
		
	def __str__(self):
		"""
		Returns
			string of our SHA1 as understood by all git commands
		"""
		return self.id
		
	def __repr__(self):
		"""
		Returns
			string with pythonic representation of our object
		"""
		return '<git.%s "%s">' % (self.__class__.__name__, self.id)
	
	@property
	def data(self):
		"""
		The binary contents of this object.

		Returns
			str
			
		NOTE
			The data will be cached after the first access.
		"""
		self._data_cached = ( self._data_cached is not type(None) and self._data_cached ) or self.repo.git.cat_file(self.id, p=True, with_raw_output=True)
		return self._data_cached
	
	@classmethod
	def get_type_by_name(cls, object_type_name):
		"""
		Returns
			type suitable to handle the given object type name.
			Use the type to create new instances.
			
		``object_type_name``
			Member of TYPES
			
		Raises
			ValueError: In case object_type_name is unknown
		"""
		if object_type_name == "commit":
			import commit
			return commit.Commit
		elif object_type_name == "tag":
			import tag
			return tag.TagObject
		elif object_type_name == "blob":
			import blob
			return blob.Blob
		elif object_type_name == "tree":
			import tree
			return tree.Tree
		else:
			raise ValueError("Cannot handle unknown object type: %s" % object_type_name)
		
		
class IndexObject(Object):
	"""
	Base for all objects that can be part of the index file , namely Tree, Blob and
	SubModule objects
	"""
	__slots__ = ("path", "mode") 
	
	def __init__(self, repo, id, mode=None, path=None, size = None):
		"""
		Initialize a newly instanced IndexObject
		``repo``
			is the Repo we are located in

		``id`` : string
			is the git object id as hex sha

		``mode`` : int
			is the file mode as int, use the stat module to evaluate the infomration

		``path`` : str
			is the path to the file in the file system, relative to the git repository root, i.e.
			file.ext or folder/other.ext
			
		 ``size`` : int
		 	size of the object data in bytes
		"""
		super(IndexObject, self).__init__(repo, id, size)
		self.mode = mode
		self.path = path
		
	@property
	def basename(self):
	  """
	  Returns
		  The basename of the IndexObject's file path
	  """
	  return os.path.basename(self.path)
	  
		
class Ref(object):
	"""
	Represents a named reference to any object
	"""
	__slots__ = ("path", "object")
	
	def __init__(self, path, object = None):
		"""
		Initialize this instance
		
		``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.path = path
		self.object = object
		
	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
			Name of this reference
		"""
		return os.path.basename(self.path)
		
	@classmethod
	def find_all(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.list_from_string(repo, output)

	@classmethod
	def list_from_string(cls, repo, text):
		"""
		Parse out ref information into a list of Ref compatible objects

		``repo``
			is the Repo
		``text``
			is the text output from the git-for-each-ref command

		Returns
			git.Ref[]
			
			list of Ref objects
		"""
		heads = []

		for line in text.splitlines():
			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.

		``repo``
			is the Repo

		``line``
			is the formatted ref information

		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")
		obj = Object.get_type_by_name(type_name)(repo, hexsha, object_size)
		return cls(full_path, obj)