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