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