summaryrefslogtreecommitdiff
path: root/gitdb/db/py/base.py
blob: c378b10e3541aa88467af78d0e5a5d879560d590 (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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# Copyright (C) 2010, 2011 Sebastian Thiel (byronimo@gmail.com) and contributors
#
# This module is part of GitDB and is released under
# the New BSD License: http://www.opensource.org/licenses/bsd-license.php
"""Contains basic implementations for the interface building blocks"""

from gitdb.db.interface import *

from gitdb.util import (
		pool,
		join,
		normpath,
		abspath,
		dirname,
		LazyMixin, 
		hex_to_bin,
		bin_to_hex,
		expandvars,
		expanduser,
		exists,
		is_git_dir
	)

from gitdb.config import GitConfigParser
from gitdb.exc import 	(
						BadObject, 
						AmbiguousObjectName,
						InvalidDBRoot
						)

from async import ChannelThreadTask

from itertools import chain
import sys
import os


__all__ = (	'PureObjectDBR', 'PureObjectDBW', 'PureRootPathDB', 'PureCompoundDB', 
			'PureConfigurationMixin', 'PureRepositoryPathsMixin')


class PureObjectDBR(ObjectDBR):
	
	#{ Query Interface 
		
	def has_object_async(self, reader):
		task = ChannelThreadTask(reader, str(self.has_object_async), lambda sha: (sha, self.has_object(sha)))
		return pool.add_task(task) 
		
	def info_async(self, reader):
		task = ChannelThreadTask(reader, str(self.info_async), self.info)
		return pool.add_task(task)
		
	def stream_async(self, reader):
		# base implementation just uses the stream method repeatedly
		task = ChannelThreadTask(reader, str(self.stream_async), self.stream)
		return pool.add_task(task)
	
	def partial_to_complete_sha_hex(self, partial_hexsha):
		len_partial_hexsha = len(partial_hexsha)
		if len_partial_hexsha % 2 != 0:
			partial_binsha = hex_to_bin(partial_hexsha + "0")
		else:
			partial_binsha = hex_to_bin(partial_hexsha)
		# END assure successful binary conversion
		return self.partial_to_complete_sha(partial_binsha, len(partial_hexsha))
	
	#} END query interface
	
	
class PureObjectDBW(ObjectDBW):
	
	def __init__(self, *args, **kwargs):
		super(PureObjectDBW, self).__init__(*args, **kwargs)
		self._ostream = None
	
	#{ Edit Interface
	def set_ostream(self, stream):
		cstream = self._ostream
		self._ostream = stream
		return cstream
		
	def ostream(self):
		return self._ostream
	
	def store_async(self, reader):
		task = ChannelThreadTask(reader, str(self.store_async), self.store) 
		return pool.add_task(task)
	
	#} END edit interface
	

class PureRootPathDB(RootPathDB):
	
	def __init__(self, root_path):
		super(PureRootPathDB, self).__init__(root_path)
		self._root_path = root_path
		
		
	#{ Interface 
	def root_path(self):
		return self._root_path
	
	def db_path(self, rela_path):
		return join(self._root_path, rela_path)
	#} END interface
		

def _databases_recursive(database, output):
	"""Fill output list with database from db, in order. Deals with Loose, Packed 
	and compound databases."""
	if isinstance(database, CompoundDB):
		compounds = list()
		dbs = database.databases()
		output.extend(db for db in dbs if not isinstance(db, CompoundDB))
		for cdb in (db for db in dbs if isinstance(db, CompoundDB)):
			_databases_recursive(cdb, output)
	else:
		output.append(database)
	# END handle database type
	

class PureCompoundDB(CompoundDB, PureObjectDBR, LazyMixin, CachingDB):
	def _set_cache_(self, attr):
		if attr == '_dbs':
			self._dbs = list()
		elif attr == '_db_cache':
			self._db_cache = dict()
		else:
			super(PureCompoundDB, self)._set_cache_(attr)
	
	def _db_query(self, sha):
		""":return: database containing the given 20 byte sha
		:raise BadObject:"""
		# most databases use binary representations, prevent converting 
		# it everytime a database is being queried
		try:
			return self._db_cache[sha]
		except KeyError:
			pass
		# END first level cache
		
		for db in self._dbs:
			if db.has_object(sha):
				self._db_cache[sha] = db
				return db
		# END for each database
		raise BadObject(sha)
	
	#{ PureObjectDBR interface 
	
	def has_object(self, sha):
		try:
			self._db_query(sha)
			return True
		except BadObject:
			return False
		# END handle exceptions
		
	def info(self, sha):
		return self._db_query(sha).info(sha)
		
	def stream(self, sha):
		return self._db_query(sha).stream(sha)

	def size(self):
		return reduce(lambda x,y: x+y, (db.size() for db in self._dbs), 0)
		
	def sha_iter(self):
		return chain(*(db.sha_iter() for db in self._dbs))
		
	#} END object DBR Interface
	
	#{ Interface
	
	def databases(self):
		return tuple(self._dbs)

	def update_cache(self, force=False):
		# something might have changed, clear everything
		self._db_cache.clear()
		stat = False
		for db in self._dbs:
			if isinstance(db, CachingDB):
				stat |= db.update_cache(force)
			# END if is caching db
		# END for each database to update
		return stat
		
	def partial_to_complete_sha_hex(self, partial_hexsha):
		databases = self.databases()
		
		len_partial_hexsha = len(partial_hexsha)
		if len_partial_hexsha % 2 != 0:
			partial_binsha = hex_to_bin(partial_hexsha + "0")
		else:
			partial_binsha = hex_to_bin(partial_hexsha)
		# END assure successful binary conversion 
		
		candidate = None
		for db in self._dbs:
			full_bin_sha = None
			try:
				if hasattr(db, 'partial_to_complete_sha_hex'):
					full_bin_sha = db.partial_to_complete_sha_hex(partial_hexsha)
				else:
					full_bin_sha = db.partial_to_complete_sha(partial_binsha, len_partial_hexsha)
				# END handle database type
			except BadObject:
				continue
			# END ignore bad objects
			if full_bin_sha:
				if candidate and candidate != full_bin_sha:
					raise AmbiguousObjectName(partial_hexsha)
				candidate = full_bin_sha
			# END handle candidate
		# END for each db
		if not candidate:
			raise BadObject(partial_binsha)
		return candidate
		
	def partial_to_complete_sha(self, partial_binsha, hex_len):
		"""Simple adaptor to feed into our implementation"""
		return self.partial_to_complete_sha_hex(bin_to_hex(partial_binsha)[:hex_len])
	#} END interface
	
		
class PureRepositoryPathsMixin(RepositoryPathsMixin):
	# slots has no effect here, its just to keep track of used attrs
	__slots__  = ("_git_path", '_bare')
	
	#{ Configuration 
	repo_dir = '.git'
	objs_dir = 'objects'
	#} END configuration
	
	#{ Subclass Interface
	def _initialize(self, path):
		epath = abspath(expandvars(expanduser(path or os.getcwd())))

		if not exists(epath):
			raise InvalidDBRoot(epath)
		#END check file 

		self._working_tree_dir = None
		self._git_path = None
		curpath = epath
		
		# walk up the path to find the .git dir
		while curpath:
			if is_git_dir(curpath):
				self._git_path = curpath
				self._working_tree_dir = os.path.dirname(curpath)
				break
			gitpath = join(curpath, self.repo_dir)
			if is_git_dir(gitpath):
				self._git_path = gitpath
				self._working_tree_dir = curpath
				break
			curpath, dummy = os.path.split(curpath)
			if not dummy:
				break
		# END while curpath
		
		if self._git_path is None:
			raise InvalidDBRoot(epath)
		# END path not found

		self._bare = self._git_path.endswith(self.repo_dir)
		if hasattr(self, 'config_reader'):
			try:
				self._bare = self.config_reader("repository").getboolean('core','bare') 
			except Exception:
				# lets not assume the option exists, although it should
				pass
		#END check bare flag

	
	#} end subclass interface
	
	#{ Interface
	
	def is_bare(self):
		return self._bare
		
	def git_path(self):
		return self._git_path
		
	def working_tree_path(self):
		if self.is_bare():
			raise AssertionError("Repository at %s is bare and does not have a working tree directory" % self.git_path())
		#END assertion
		return dirname(self.git_path())
		
	def objects_path(self):
		return join(self.git_path(), self.objs_dir)
		
	def working_dir(self):
		if self.is_bare():
			return self.git_path()
		else:
			return self.working_tree_dir()
		#END handle bare state
		
	#} END interface
		
		
class PureConfigurationMixin(ConfigurationMixin):
	
	#{ Configuration
	system_config_file_name = "gitconfig"
	repo_config_file_name = "config"
	#} END
	
	def __init__(self, *args, **kwargs):
		"""Verify prereqs"""
		assert hasattr(self, 'git_path')
	
	def _path_at_level(self, level ):
		# we do not support an absolute path of the gitconfig on windows , 
		# use the global config instead
		if sys.platform == "win32" and level == "system":
			level = "global"
		#END handle windows
			
		if level == "system":
			return "/etc/%s" % self.system_config_file_name
		elif level == "global":
			return normpath(expanduser("~/.%s" % self.system_config_file_name))
		elif level == "repository":
			return join(self.git_path(), self.repo_config_file_name)
		#END handle level
		
		raise ValueError("Invalid configuration level: %r" % level)
		
	#{ Interface
	
	def config_reader(self, config_level=None):
		files = None
		if config_level is None:
			files = [ self._path_at_level(f) for f in self.config_level ]
		else:
			files = [ self._path_at_level(config_level) ]
		#END handle level
		return GitConfigParser(files, read_only=True)
		
	def config_writer(self, config_level="repository"):
		return GitConfigParser(self._path_at_level(config_level), read_only=False)
	
	#} END interface