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