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