##// END OF EJS Templates
Don't cache dulwich Repos, in pararell multithreaded evniroment dulwich pack file openers...
marcink -
r3043:b61824e6 beta
parent child Browse files
Show More
@@ -1,659 +1,666 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 vcs.backends.git
4 4 ~~~~~~~~~~~~~~~~
5 5
6 6 Git backend implementation.
7 7
8 8 :created_on: Apr 8, 2010
9 9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 10 """
11 11
12 12 import os
13 13 import re
14 14 import time
15 15 import posixpath
16 16 import logging
17 17 import traceback
18 18 import urllib
19 19 import urllib2
20 20 from dulwich.repo import Repo, NotGitRepository
21 21 from dulwich.objects import Tag
22 22 from string import Template
23 23 from subprocess import Popen, PIPE
24 24 from rhodecode.lib.vcs.backends.base import BaseRepository
25 25 from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError
26 26 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
27 27 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
28 28 from rhodecode.lib.vcs.exceptions import RepositoryError
29 29 from rhodecode.lib.vcs.exceptions import TagAlreadyExistError
30 30 from rhodecode.lib.vcs.exceptions import TagDoesNotExistError
31 31 from rhodecode.lib.vcs.utils import safe_unicode, makedate, date_fromtimestamp
32 32 from rhodecode.lib.vcs.utils.lazy import LazyProperty
33 33 from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
34 34 from rhodecode.lib.vcs.utils.paths import abspath
35 35 from rhodecode.lib.vcs.utils.paths import get_user_home
36 36 from .workdir import GitWorkdir
37 37 from .changeset import GitChangeset
38 38 from .inmemory import GitInMemoryChangeset
39 39 from .config import ConfigFile
40 40 from rhodecode.lib import subprocessio
41 41
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class GitRepository(BaseRepository):
47 47 """
48 48 Git repository backend.
49 49 """
50 50 DEFAULT_BRANCH_NAME = 'master'
51 51 scm = 'git'
52 52
53 53 def __init__(self, repo_path, create=False, src_url=None,
54 54 update_after_clone=False, bare=False):
55 55
56 56 self.path = abspath(repo_path)
57 self._repo = self._get_repo(create, src_url, update_after_clone, bare)
57 repo = self._get_repo(create, src_url, update_after_clone, bare)
58 self.bare = repo.bare
59
60 self._config_files = [
61 bare and abspath(self.path, 'config') or abspath(self.path, '.git',
62 'config'),
63 abspath(get_user_home(), '.gitconfig'),
64 ]
65
66 @property
67 def _repo(self):
68 repo = Repo(self.path)
58 69 #temporary set that to now at later we will move it to constructor
59 70 baseui = None
60 71 if baseui is None:
61 72 from mercurial.ui import ui
62 73 baseui = ui()
63 74 # patch the instance of GitRepo with an "FAKE" ui object to add
64 75 # compatibility layer with Mercurial
65 setattr(self._repo, 'ui', baseui)
66
67 try:
68 self.head = self._repo.head()
69 except KeyError:
70 self.head = None
76 setattr(repo, 'ui', baseui)
77 return repo
71 78
72 self._config_files = [
73 bare and abspath(self.path, 'config') or abspath(self.path, '.git',
74 'config'),
75 abspath(get_user_home(), '.gitconfig'),
76 ]
77 self.bare = self._repo.bare
79 @property
80 def head(self):
81 try:
82 return self._repo.head()
83 except KeyError:
84 return None
78 85
79 86 @LazyProperty
80 87 def revisions(self):
81 88 """
82 89 Returns list of revisions' ids, in ascending order. Being lazy
83 90 attribute allows external tools to inject shas from cache.
84 91 """
85 92 return self._get_all_revisions()
86 93
87 94 def run_git_command(self, cmd):
88 95 """
89 96 Runs given ``cmd`` as git command and returns tuple
90 97 (returncode, stdout, stderr).
91 98
92 99 .. note::
93 100 This method exists only until log/blame functionality is implemented
94 101 at Dulwich (see https://bugs.launchpad.net/bugs/645142). Parsing
95 102 os command's output is road to hell...
96 103
97 104 :param cmd: git command to be executed
98 105 """
99 106
100 107 _copts = ['-c', 'core.quotepath=false', ]
101 108 _str_cmd = False
102 109 if isinstance(cmd, basestring):
103 110 cmd = [cmd]
104 111 _str_cmd = True
105 112
106 113 gitenv = os.environ
107 114 # need to clean fix GIT_DIR !
108 115 if 'GIT_DIR' in gitenv:
109 116 del gitenv['GIT_DIR']
110 117 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
111 118
112 119 cmd = ['git'] + _copts + cmd
113 120 if _str_cmd:
114 121 cmd = ' '.join(cmd)
115 122 try:
116 123 opts = dict(
117 124 env=gitenv,
118 125 shell=False,
119 126 )
120 127 if os.path.isdir(self.path):
121 128 opts['cwd'] = self.path
122 129 p = subprocessio.SubprocessIOChunker(cmd, **opts)
123 130 except (EnvironmentError, OSError), err:
124 131 log.error(traceback.format_exc())
125 132 raise RepositoryError("Couldn't run git command (%s).\n"
126 133 "Original error was:%s" % (cmd, err))
127 134
128 135 return ''.join(p.output), ''.join(p.error)
129 136
130 137 @classmethod
131 138 def _check_url(cls, url):
132 139 """
133 140 Functon will check given url and try to verify if it's a valid
134 141 link. Sometimes it may happened that mercurial will issue basic
135 142 auth request that can cause whole API to hang when used from python
136 143 or other external calls.
137 144
138 145 On failures it'll raise urllib2.HTTPError
139 146 """
140 147 from mercurial.util import url as Url
141 148
142 149 # those authnadlers are patched for python 2.6.5 bug an
143 150 # infinit looping when given invalid resources
144 151 from mercurial.url import httpbasicauthhandler, httpdigestauthhandler
145 152
146 153 # check first if it's not an local url
147 154 if os.path.isdir(url) or url.startswith('file:'):
148 155 return True
149 156
150 157 if('+' in url[:url.find('://')]):
151 158 url = url[url.find('+') + 1:]
152 159
153 160 handlers = []
154 161 test_uri, authinfo = Url(url).authinfo()
155 162 if not test_uri.endswith('info/refs'):
156 163 test_uri = test_uri.rstrip('/') + '/info/refs'
157 164 if authinfo:
158 165 #create a password manager
159 166 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
160 167 passmgr.add_password(*authinfo)
161 168
162 169 handlers.extend((httpbasicauthhandler(passmgr),
163 170 httpdigestauthhandler(passmgr)))
164 171
165 172 o = urllib2.build_opener(*handlers)
166 173 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
167 174
168 175 q = {"service": 'git-upload-pack'}
169 176 qs = '?%s' % urllib.urlencode(q)
170 177 cu = "%s%s" % (test_uri, qs)
171 178 req = urllib2.Request(cu, None, {})
172 179
173 180 try:
174 181 resp = o.open(req)
175 182 return resp.code == 200
176 183 except Exception, e:
177 184 # means it cannot be cloned
178 185 raise urllib2.URLError("[%s] %s" % (url, e))
179 186
180 187 def _get_repo(self, create, src_url=None, update_after_clone=False,
181 188 bare=False):
182 189 if create and os.path.exists(self.path):
183 190 raise RepositoryError("Location already exist")
184 191 if src_url and not create:
185 192 raise RepositoryError("Create should be set to True if src_url is "
186 193 "given (clone operation creates repository)")
187 194 try:
188 195 if create and src_url:
189 196 GitRepository._check_url(src_url)
190 197 self.clone(src_url, update_after_clone, bare)
191 198 return Repo(self.path)
192 199 elif create:
193 200 os.mkdir(self.path)
194 201 if bare:
195 202 return Repo.init_bare(self.path)
196 203 else:
197 204 return Repo.init(self.path)
198 205 else:
199 206 return Repo(self.path)
200 207 except (NotGitRepository, OSError), err:
201 208 raise RepositoryError(err)
202 209
203 210 def _get_all_revisions(self):
204 211 # we must check if this repo is not empty, since later command
205 212 # fails if it is. And it's cheaper to ask than throw the subprocess
206 213 # errors
207 214 try:
208 215 self._repo.head()
209 216 except KeyError:
210 217 return []
211 218 cmd = 'rev-list --all --reverse --date-order'
212 219 try:
213 220 so, se = self.run_git_command(cmd)
214 221 except RepositoryError:
215 222 # Can be raised for empty repositories
216 223 return []
217 224 return so.splitlines()
218 225
219 226 def _get_all_revisions2(self):
220 227 #alternate implementation using dulwich
221 228 includes = [x[1][0] for x in self._parsed_refs.iteritems()
222 229 if x[1][1] != 'T']
223 230 return [c.commit.id for c in self._repo.get_walker(include=includes)]
224 231
225 232 def _get_revision(self, revision):
226 233 """
227 234 For git backend we always return integer here. This way we ensure
228 235 that changset's revision attribute would become integer.
229 236 """
230 237 pattern = re.compile(r'^[[0-9a-fA-F]{12}|[0-9a-fA-F]{40}]$')
231 238 is_bstr = lambda o: isinstance(o, (str, unicode))
232 239 is_null = lambda o: len(o) == revision.count('0')
233 240
234 241 if len(self.revisions) == 0:
235 242 raise EmptyRepositoryError("There are no changesets yet")
236 243
237 244 if revision in (None, '', 'tip', 'HEAD', 'head', -1):
238 245 revision = self.revisions[-1]
239 246
240 247 if ((is_bstr(revision) and revision.isdigit() and len(revision) < 12)
241 248 or isinstance(revision, int) or is_null(revision)):
242 249 try:
243 250 revision = self.revisions[int(revision)]
244 251 except:
245 252 raise ChangesetDoesNotExistError("Revision %r does not exist "
246 253 "for this repository %s" % (revision, self))
247 254
248 255 elif is_bstr(revision):
249 256 # get by branch/tag name
250 257 _ref_revision = self._parsed_refs.get(revision)
251 258 _tags_shas = self.tags.values()
252 259 if _ref_revision: # and _ref_revision[1] in ['H', 'RH', 'T']:
253 260 return _ref_revision[0]
254 261
255 262 # maybe it's a tag ? we don't have them in self.revisions
256 263 elif revision in _tags_shas:
257 264 return _tags_shas[_tags_shas.index(revision)]
258 265
259 266 elif not pattern.match(revision) or revision not in self.revisions:
260 267 raise ChangesetDoesNotExistError("Revision %r does not exist "
261 268 "for this repository %s" % (revision, self))
262 269
263 270 # Ensure we return full id
264 271 if not pattern.match(str(revision)):
265 272 raise ChangesetDoesNotExistError("Given revision %r not recognized"
266 273 % revision)
267 274 return revision
268 275
269 276 def _get_archives(self, archive_name='tip'):
270 277
271 278 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
272 279 yield {"type": i[0], "extension": i[1], "node": archive_name}
273 280
274 281 def _get_url(self, url):
275 282 """
276 283 Returns normalized url. If schema is not given, would fall to
277 284 filesystem (``file:///``) schema.
278 285 """
279 286 url = str(url)
280 287 if url != 'default' and not '://' in url:
281 288 url = ':///'.join(('file', url))
282 289 return url
283 290
284 291 @LazyProperty
285 292 def name(self):
286 293 return os.path.basename(self.path)
287 294
288 295 @LazyProperty
289 296 def last_change(self):
290 297 """
291 298 Returns last change made on this repository as datetime object
292 299 """
293 300 return date_fromtimestamp(self._get_mtime(), makedate()[1])
294 301
295 302 def _get_mtime(self):
296 303 try:
297 304 return time.mktime(self.get_changeset().date.timetuple())
298 305 except RepositoryError:
299 306 idx_loc = '' if self.bare else '.git'
300 307 # fallback to filesystem
301 308 in_path = os.path.join(self.path, idx_loc, "index")
302 309 he_path = os.path.join(self.path, idx_loc, "HEAD")
303 310 if os.path.exists(in_path):
304 311 return os.stat(in_path).st_mtime
305 312 else:
306 313 return os.stat(he_path).st_mtime
307 314
308 315 @LazyProperty
309 316 def description(self):
310 317 idx_loc = '' if self.bare else '.git'
311 318 undefined_description = u'unknown'
312 319 description_path = os.path.join(self.path, idx_loc, 'description')
313 320 if os.path.isfile(description_path):
314 321 return safe_unicode(open(description_path).read())
315 322 else:
316 323 return undefined_description
317 324
318 325 @LazyProperty
319 326 def contact(self):
320 327 undefined_contact = u'Unknown'
321 328 return undefined_contact
322 329
323 330 @property
324 331 def branches(self):
325 332 if not self.revisions:
326 333 return {}
327 334 sortkey = lambda ctx: ctx[0]
328 335 _branches = [(x[0], x[1][0])
329 336 for x in self._parsed_refs.iteritems() if x[1][1] == 'H']
330 337 return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
331 338
332 339 @LazyProperty
333 340 def tags(self):
334 341 return self._get_tags()
335 342
336 343 def _get_tags(self):
337 344 if not self.revisions:
338 345 return {}
339 346
340 347 sortkey = lambda ctx: ctx[0]
341 348 _tags = [(x[0], x[1][0])
342 349 for x in self._parsed_refs.iteritems() if x[1][1] == 'T']
343 350 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
344 351
345 352 def tag(self, name, user, revision=None, message=None, date=None,
346 353 **kwargs):
347 354 """
348 355 Creates and returns a tag for the given ``revision``.
349 356
350 357 :param name: name for new tag
351 358 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
352 359 :param revision: changeset id for which new tag would be created
353 360 :param message: message of the tag's commit
354 361 :param date: date of tag's commit
355 362
356 363 :raises TagAlreadyExistError: if tag with same name already exists
357 364 """
358 365 if name in self.tags:
359 366 raise TagAlreadyExistError("Tag %s already exists" % name)
360 367 changeset = self.get_changeset(revision)
361 368 message = message or "Added tag %s for commit %s" % (name,
362 369 changeset.raw_id)
363 370 self._repo.refs["refs/tags/%s" % name] = changeset._commit.id
364 371
365 372 self._parsed_refs = self._get_parsed_refs()
366 373 self.tags = self._get_tags()
367 374 return changeset
368 375
369 376 def remove_tag(self, name, user, message=None, date=None):
370 377 """
371 378 Removes tag with the given ``name``.
372 379
373 380 :param name: name of the tag to be removed
374 381 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
375 382 :param message: message of the tag's removal commit
376 383 :param date: date of tag's removal commit
377 384
378 385 :raises TagDoesNotExistError: if tag with given name does not exists
379 386 """
380 387 if name not in self.tags:
381 388 raise TagDoesNotExistError("Tag %s does not exist" % name)
382 389 tagpath = posixpath.join(self._repo.refs.path, 'refs', 'tags', name)
383 390 try:
384 391 os.remove(tagpath)
385 392 self._parsed_refs = self._get_parsed_refs()
386 393 self.tags = self._get_tags()
387 394 except OSError, e:
388 395 raise RepositoryError(e.strerror)
389 396
390 397 @LazyProperty
391 398 def _parsed_refs(self):
392 399 return self._get_parsed_refs()
393 400
394 401 def _get_parsed_refs(self):
395 402 refs = self._repo.get_refs()
396 403 keys = [('refs/heads/', 'H'),
397 404 ('refs/remotes/origin/', 'RH'),
398 405 ('refs/tags/', 'T')]
399 406 _refs = {}
400 407 for ref, sha in refs.iteritems():
401 408 for k, type_ in keys:
402 409 if ref.startswith(k):
403 410 _key = ref[len(k):]
404 411 if type_ == 'T':
405 412 obj = self._repo.get_object(sha)
406 413 if isinstance(obj, Tag):
407 414 sha = self._repo.get_object(sha).object[1]
408 415 _refs[_key] = [sha, type_]
409 416 break
410 417 return _refs
411 418
412 419 def _heads(self, reverse=False):
413 420 refs = self._repo.get_refs()
414 421 heads = {}
415 422
416 423 for key, val in refs.items():
417 424 for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
418 425 if key.startswith(ref_key):
419 426 n = key[len(ref_key):]
420 427 if n not in ['HEAD']:
421 428 heads[n] = val
422 429
423 430 return heads if reverse else dict((y, x) for x, y in heads.iteritems())
424 431
425 432 def get_changeset(self, revision=None):
426 433 """
427 434 Returns ``GitChangeset`` object representing commit from git repository
428 435 at the given revision or head (most recent commit) if None given.
429 436 """
430 437 if isinstance(revision, GitChangeset):
431 438 return revision
432 439 revision = self._get_revision(revision)
433 440 changeset = GitChangeset(repository=self, revision=revision)
434 441 return changeset
435 442
436 443 def get_changesets(self, start=None, end=None, start_date=None,
437 444 end_date=None, branch_name=None, reverse=False):
438 445 """
439 446 Returns iterator of ``GitChangeset`` objects from start to end (both
440 447 are inclusive), in ascending date order (unless ``reverse`` is set).
441 448
442 449 :param start: changeset ID, as str; first returned changeset
443 450 :param end: changeset ID, as str; last returned changeset
444 451 :param start_date: if specified, changesets with commit date less than
445 452 ``start_date`` would be filtered out from returned set
446 453 :param end_date: if specified, changesets with commit date greater than
447 454 ``end_date`` would be filtered out from returned set
448 455 :param branch_name: if specified, changesets not reachable from given
449 456 branch would be filtered out from returned set
450 457 :param reverse: if ``True``, returned generator would be reversed
451 458 (meaning that returned changesets would have descending date order)
452 459
453 460 :raise BranchDoesNotExistError: If given ``branch_name`` does not
454 461 exist.
455 462 :raise ChangesetDoesNotExistError: If changeset for given ``start`` or
456 463 ``end`` could not be found.
457 464
458 465 """
459 466 if branch_name and branch_name not in self.branches:
460 467 raise BranchDoesNotExistError("Branch '%s' not found" \
461 468 % branch_name)
462 469 # %H at format means (full) commit hash, initial hashes are retrieved
463 470 # in ascending date order
464 471 cmd_template = 'log --date-order --reverse --pretty=format:"%H"'
465 472 cmd_params = {}
466 473 if start_date:
467 474 cmd_template += ' --since "$since"'
468 475 cmd_params['since'] = start_date.strftime('%m/%d/%y %H:%M:%S')
469 476 if end_date:
470 477 cmd_template += ' --until "$until"'
471 478 cmd_params['until'] = end_date.strftime('%m/%d/%y %H:%M:%S')
472 479 if branch_name:
473 480 cmd_template += ' $branch_name'
474 481 cmd_params['branch_name'] = branch_name
475 482 else:
476 483 cmd_template += ' --all'
477 484
478 485 cmd = Template(cmd_template).safe_substitute(**cmd_params)
479 486 revs = self.run_git_command(cmd)[0].splitlines()
480 487 start_pos = 0
481 488 end_pos = len(revs)
482 489 if start:
483 490 _start = self._get_revision(start)
484 491 try:
485 492 start_pos = revs.index(_start)
486 493 except ValueError:
487 494 pass
488 495
489 496 if end is not None:
490 497 _end = self._get_revision(end)
491 498 try:
492 499 end_pos = revs.index(_end)
493 500 except ValueError:
494 501 pass
495 502
496 503 if None not in [start, end] and start_pos > end_pos:
497 504 raise RepositoryError('start cannot be after end')
498 505
499 506 if end_pos is not None:
500 507 end_pos += 1
501 508
502 509 revs = revs[start_pos:end_pos]
503 510 if reverse:
504 511 revs = reversed(revs)
505 512 for rev in revs:
506 513 yield self.get_changeset(rev)
507 514
508 515 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
509 516 context=3):
510 517 """
511 518 Returns (git like) *diff*, as plain text. Shows changes introduced by
512 519 ``rev2`` since ``rev1``.
513 520
514 521 :param rev1: Entry point from which diff is shown. Can be
515 522 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
516 523 the changes since empty state of the repository until ``rev2``
517 524 :param rev2: Until which revision changes should be shown.
518 525 :param ignore_whitespace: If set to ``True``, would not show whitespace
519 526 changes. Defaults to ``False``.
520 527 :param context: How many lines before/after changed lines should be
521 528 shown. Defaults to ``3``.
522 529 """
523 530 flags = ['-U%s' % context, '--full-index', '--binary', '-p', '-M', '--abbrev=40']
524 531 if ignore_whitespace:
525 532 flags.append('-w')
526 533
527 534 if hasattr(rev1, 'raw_id'):
528 535 rev1 = getattr(rev1, 'raw_id')
529 536
530 537 if hasattr(rev2, 'raw_id'):
531 538 rev2 = getattr(rev2, 'raw_id')
532 539
533 540 if rev1 == self.EMPTY_CHANGESET:
534 541 rev2 = self.get_changeset(rev2).raw_id
535 542 cmd = ' '.join(['show'] + flags + [rev2])
536 543 else:
537 544 rev1 = self.get_changeset(rev1).raw_id
538 545 rev2 = self.get_changeset(rev2).raw_id
539 546 cmd = ' '.join(['diff'] + flags + [rev1, rev2])
540 547
541 548 if path:
542 549 cmd += ' -- "%s"' % path
543 550
544 551 stdout, stderr = self.run_git_command(cmd)
545 552 # If we used 'show' command, strip first few lines (until actual diff
546 553 # starts)
547 554 if rev1 == self.EMPTY_CHANGESET:
548 555 lines = stdout.splitlines()
549 556 x = 0
550 557 for line in lines:
551 558 if line.startswith('diff'):
552 559 break
553 560 x += 1
554 561 # Append new line just like 'diff' command do
555 562 stdout = '\n'.join(lines[x:]) + '\n'
556 563 return stdout
557 564
558 565 @LazyProperty
559 566 def in_memory_changeset(self):
560 567 """
561 568 Returns ``GitInMemoryChangeset`` object for this repository.
562 569 """
563 570 return GitInMemoryChangeset(self)
564 571
565 572 def clone(self, url, update_after_clone=True, bare=False):
566 573 """
567 574 Tries to clone changes from external location.
568 575
569 576 :param update_after_clone: If set to ``False``, git won't checkout
570 577 working directory
571 578 :param bare: If set to ``True``, repository would be cloned into
572 579 *bare* git repository (no working directory at all).
573 580 """
574 581 url = self._get_url(url)
575 582 cmd = ['clone']
576 583 if bare:
577 584 cmd.append('--bare')
578 585 elif not update_after_clone:
579 586 cmd.append('--no-checkout')
580 587 cmd += ['--', '"%s"' % url, '"%s"' % self.path]
581 588 cmd = ' '.join(cmd)
582 589 # If error occurs run_git_command raises RepositoryError already
583 590 self.run_git_command(cmd)
584 591
585 592 def pull(self, url):
586 593 """
587 594 Tries to pull changes from external location.
588 595 """
589 596 url = self._get_url(url)
590 597 cmd = ['pull']
591 598 cmd.append("--ff-only")
592 599 cmd.append(url)
593 600 cmd = ' '.join(cmd)
594 601 # If error occurs run_git_command raises RepositoryError already
595 602 self.run_git_command(cmd)
596 603
597 604 def fetch(self, url):
598 605 """
599 606 Tries to pull changes from external location.
600 607 """
601 608 url = self._get_url(url)
602 609 cmd = ['fetch']
603 610 cmd.append(url)
604 611 cmd = ' '.join(cmd)
605 612 # If error occurs run_git_command raises RepositoryError already
606 613 self.run_git_command(cmd)
607 614
608 615 @LazyProperty
609 616 def workdir(self):
610 617 """
611 618 Returns ``Workdir`` instance for this repository.
612 619 """
613 620 return GitWorkdir(self)
614 621
615 622 def get_config_value(self, section, name, config_file=None):
616 623 """
617 624 Returns configuration value for a given [``section``] and ``name``.
618 625
619 626 :param section: Section we want to retrieve value from
620 627 :param name: Name of configuration we want to retrieve
621 628 :param config_file: A path to file which should be used to retrieve
622 629 configuration from (might also be a list of file paths)
623 630 """
624 631 if config_file is None:
625 632 config_file = []
626 633 elif isinstance(config_file, basestring):
627 634 config_file = [config_file]
628 635
629 636 def gen_configs():
630 637 for path in config_file + self._config_files:
631 638 try:
632 639 yield ConfigFile.from_path(path)
633 640 except (IOError, OSError, ValueError):
634 641 continue
635 642
636 643 for config in gen_configs():
637 644 try:
638 645 return config.get(section, name)
639 646 except KeyError:
640 647 continue
641 648 return None
642 649
643 650 def get_user_name(self, config_file=None):
644 651 """
645 652 Returns user's name from global configuration file.
646 653
647 654 :param config_file: A path to file which should be used to retrieve
648 655 configuration from (might also be a list of file paths)
649 656 """
650 657 return self.get_config_value('user', 'name', config_file)
651 658
652 659 def get_user_email(self, config_file=None):
653 660 """
654 661 Returns user's email from global configuration file.
655 662
656 663 :param config_file: A path to file which should be used to retrieve
657 664 configuration from (might also be a list of file paths)
658 665 """
659 666 return self.get_config_value('user', 'email', config_file)
General Comments 0
You need to be logged in to leave comments. Login now