##// END OF EJS Templates
hg: improve 'Such branch %s does not exists' message
Mads Kiilerich -
r3554:189ce0d7 beta
parent child Browse files
Show More
@@ -1,542 +1,542 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 if path:
245 if path:
246 file_filter = match(self.path, '', [path])
246 file_filter = match(self.path, '', [path])
247 else:
247 else:
248 file_filter = None
248 file_filter = None
249
249
250 return ''.join(patch.diff(self._repo, rev1, rev2, match=file_filter,
250 return ''.join(patch.diff(self._repo, rev1, rev2, match=file_filter,
251 opts=diffopts(git=True,
251 opts=diffopts(git=True,
252 ignorews=ignore_whitespace,
252 ignorews=ignore_whitespace,
253 context=context)))
253 context=context)))
254
254
255 @classmethod
255 @classmethod
256 def _check_url(cls, url):
256 def _check_url(cls, url):
257 """
257 """
258 Function will check given url and try to verify if it's a valid
258 Function will check given url and try to verify if it's a valid
259 link. Sometimes it may happened that mercurial will issue basic
259 link. Sometimes it may happened that mercurial will issue basic
260 auth request that can cause whole API to hang when used from python
260 auth request that can cause whole API to hang when used from python
261 or other external calls.
261 or other external calls.
262
262
263 On failures it'll raise urllib2.HTTPError, return code 200 if url
263 On failures it'll raise urllib2.HTTPError, return code 200 if url
264 is valid or True if it's a local path
264 is valid or True if it's a local path
265 """
265 """
266
266
267 from mercurial.util import url as Url
267 from mercurial.util import url as Url
268
268
269 # those authnadlers are patched for python 2.6.5 bug an
269 # those authnadlers are patched for python 2.6.5 bug an
270 # infinit looping when given invalid resources
270 # infinit looping when given invalid resources
271 from mercurial.url import httpbasicauthhandler, httpdigestauthhandler
271 from mercurial.url import httpbasicauthhandler, httpdigestauthhandler
272
272
273 # check first if it's not an local url
273 # check first if it's not an local url
274 if os.path.isdir(url) or url.startswith('file:'):
274 if os.path.isdir(url) or url.startswith('file:'):
275 return True
275 return True
276
276
277 if('+' in url[:url.find('://')]):
277 if('+' in url[:url.find('://')]):
278 url = url[url.find('+') + 1:]
278 url = url[url.find('+') + 1:]
279
279
280 handlers = []
280 handlers = []
281 test_uri, authinfo = Url(url).authinfo()
281 test_uri, authinfo = Url(url).authinfo()
282
282
283 if authinfo:
283 if authinfo:
284 #create a password manager
284 #create a password manager
285 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
285 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
286 passmgr.add_password(*authinfo)
286 passmgr.add_password(*authinfo)
287
287
288 handlers.extend((httpbasicauthhandler(passmgr),
288 handlers.extend((httpbasicauthhandler(passmgr),
289 httpdigestauthhandler(passmgr)))
289 httpdigestauthhandler(passmgr)))
290
290
291 o = urllib2.build_opener(*handlers)
291 o = urllib2.build_opener(*handlers)
292 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
292 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
293 ('Accept', 'application/mercurial-0.1')]
293 ('Accept', 'application/mercurial-0.1')]
294
294
295 q = {"cmd": 'between'}
295 q = {"cmd": 'between'}
296 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
296 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
297 qs = '?%s' % urllib.urlencode(q)
297 qs = '?%s' % urllib.urlencode(q)
298 cu = "%s%s" % (test_uri, qs)
298 cu = "%s%s" % (test_uri, qs)
299 req = urllib2.Request(cu, None, {})
299 req = urllib2.Request(cu, None, {})
300
300
301 try:
301 try:
302 resp = o.open(req)
302 resp = o.open(req)
303 return resp.code == 200
303 return resp.code == 200
304 except Exception, e:
304 except Exception, e:
305 # means it cannot be cloned
305 # means it cannot be cloned
306 raise urllib2.URLError("[%s] %s" % (url, e))
306 raise urllib2.URLError("[%s] %s" % (url, e))
307
307
308 def _get_repo(self, create, src_url=None, update_after_clone=False):
308 def _get_repo(self, create, src_url=None, update_after_clone=False):
309 """
309 """
310 Function will check for mercurial repository in given path and return
310 Function will check for mercurial repository in given path and return
311 a localrepo object. If there is no repository in that path it will
311 a localrepo object. If there is no repository in that path it will
312 raise an exception unless ``create`` parameter is set to True - in
312 raise an exception unless ``create`` parameter is set to True - in
313 that case repository would be created and returned.
313 that case repository would be created and returned.
314 If ``src_url`` is given, would try to clone repository from the
314 If ``src_url`` is given, would try to clone repository from the
315 location at given clone_point. Additionally it'll make update to
315 location at given clone_point. Additionally it'll make update to
316 working copy accordingly to ``update_after_clone`` flag
316 working copy accordingly to ``update_after_clone`` flag
317 """
317 """
318
318
319 try:
319 try:
320 if src_url:
320 if src_url:
321 url = str(self._get_url(src_url))
321 url = str(self._get_url(src_url))
322 opts = {}
322 opts = {}
323 if not update_after_clone:
323 if not update_after_clone:
324 opts.update({'noupdate': True})
324 opts.update({'noupdate': True})
325 try:
325 try:
326 MercurialRepository._check_url(url)
326 MercurialRepository._check_url(url)
327 clone(self.baseui, url, self.path, **opts)
327 clone(self.baseui, url, self.path, **opts)
328 # except urllib2.URLError:
328 # except urllib2.URLError:
329 # raise Abort("Got HTTP 404 error")
329 # raise Abort("Got HTTP 404 error")
330 except Exception:
330 except Exception:
331 raise
331 raise
332
332
333 # Don't try to create if we've already cloned repo
333 # Don't try to create if we've already cloned repo
334 create = False
334 create = False
335 return localrepository(self.baseui, self.path, create=create)
335 return localrepository(self.baseui, self.path, create=create)
336 except (Abort, RepoError), err:
336 except (Abort, RepoError), err:
337 if create:
337 if create:
338 msg = "Cannot create repository at %s. Original error was %s"\
338 msg = "Cannot create repository at %s. Original error was %s"\
339 % (self.path, err)
339 % (self.path, err)
340 else:
340 else:
341 msg = "Not valid repository at %s. Original error was %s"\
341 msg = "Not valid repository at %s. Original error was %s"\
342 % (self.path, err)
342 % (self.path, err)
343 raise RepositoryError(msg)
343 raise RepositoryError(msg)
344
344
345 @LazyProperty
345 @LazyProperty
346 def in_memory_changeset(self):
346 def in_memory_changeset(self):
347 return MercurialInMemoryChangeset(self)
347 return MercurialInMemoryChangeset(self)
348
348
349 @LazyProperty
349 @LazyProperty
350 def description(self):
350 def description(self):
351 undefined_description = u'unknown'
351 undefined_description = u'unknown'
352 return safe_unicode(self._repo.ui.config('web', 'description',
352 return safe_unicode(self._repo.ui.config('web', 'description',
353 undefined_description, untrusted=True))
353 undefined_description, untrusted=True))
354
354
355 @LazyProperty
355 @LazyProperty
356 def contact(self):
356 def contact(self):
357 undefined_contact = u'Unknown'
357 undefined_contact = u'Unknown'
358 return safe_unicode(get_contact(self._repo.ui.config)
358 return safe_unicode(get_contact(self._repo.ui.config)
359 or undefined_contact)
359 or undefined_contact)
360
360
361 @LazyProperty
361 @LazyProperty
362 def last_change(self):
362 def last_change(self):
363 """
363 """
364 Returns last change made on this repository as datetime object
364 Returns last change made on this repository as datetime object
365 """
365 """
366 return date_fromtimestamp(self._get_mtime(), makedate()[1])
366 return date_fromtimestamp(self._get_mtime(), makedate()[1])
367
367
368 def _get_mtime(self):
368 def _get_mtime(self):
369 try:
369 try:
370 return time.mktime(self.get_changeset().date.timetuple())
370 return time.mktime(self.get_changeset().date.timetuple())
371 except RepositoryError:
371 except RepositoryError:
372 #fallback to filesystem
372 #fallback to filesystem
373 cl_path = os.path.join(self.path, '.hg', "00changelog.i")
373 cl_path = os.path.join(self.path, '.hg', "00changelog.i")
374 st_path = os.path.join(self.path, '.hg', "store")
374 st_path = os.path.join(self.path, '.hg', "store")
375 if os.path.exists(cl_path):
375 if os.path.exists(cl_path):
376 return os.stat(cl_path).st_mtime
376 return os.stat(cl_path).st_mtime
377 else:
377 else:
378 return os.stat(st_path).st_mtime
378 return os.stat(st_path).st_mtime
379
379
380 def _get_hidden(self):
380 def _get_hidden(self):
381 return self._repo.ui.configbool("web", "hidden", untrusted=True)
381 return self._repo.ui.configbool("web", "hidden", untrusted=True)
382
382
383 def _get_revision(self, revision):
383 def _get_revision(self, revision):
384 """
384 """
385 Get's an ID revision given as str. This will always return a fill
385 Get's an ID revision given as str. This will always return a fill
386 40 char revision number
386 40 char revision number
387
387
388 :param revision: str or int or None
388 :param revision: str or int or None
389 """
389 """
390
390
391 if self._empty:
391 if self._empty:
392 raise EmptyRepositoryError("There are no changesets yet")
392 raise EmptyRepositoryError("There are no changesets yet")
393
393
394 if revision in [-1, 'tip', None]:
394 if revision in [-1, 'tip', None]:
395 revision = 'tip'
395 revision = 'tip'
396
396
397 try:
397 try:
398 revision = hex(self._repo.lookup(revision))
398 revision = hex(self._repo.lookup(revision))
399 except (IndexError, ValueError, RepoLookupError, TypeError):
399 except (IndexError, ValueError, RepoLookupError, TypeError):
400 raise ChangesetDoesNotExistError("Revision %r does not "
400 raise ChangesetDoesNotExistError("Revision %r does not "
401 "exist for this repository %s" \
401 "exist for this repository %s" \
402 % (revision, self))
402 % (revision, self))
403 return revision
403 return revision
404
404
405 def _get_archives(self, archive_name='tip'):
405 def _get_archives(self, archive_name='tip'):
406 allowed = self.baseui.configlist("web", "allow_archive",
406 allowed = self.baseui.configlist("web", "allow_archive",
407 untrusted=True)
407 untrusted=True)
408 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
408 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
409 if i[0] in allowed or self._repo.ui.configbool("web",
409 if i[0] in allowed or self._repo.ui.configbool("web",
410 "allow" + i[0],
410 "allow" + i[0],
411 untrusted=True):
411 untrusted=True):
412 yield {"type": i[0], "extension": i[1], "node": archive_name}
412 yield {"type": i[0], "extension": i[1], "node": archive_name}
413
413
414 def _get_url(self, url):
414 def _get_url(self, url):
415 """
415 """
416 Returns normalized url. If schema is not given, would fall
416 Returns normalized url. If schema is not given, would fall
417 to filesystem
417 to filesystem
418 (``file:///``) schema.
418 (``file:///``) schema.
419 """
419 """
420 url = str(url)
420 url = str(url)
421 if url != 'default' and not '://' in url:
421 if url != 'default' and not '://' in url:
422 url = "file:" + urllib.pathname2url(url)
422 url = "file:" + urllib.pathname2url(url)
423 return url
423 return url
424
424
425 def get_hook_location(self):
425 def get_hook_location(self):
426 """
426 """
427 returns absolute path to location where hooks are stored
427 returns absolute path to location where hooks are stored
428 """
428 """
429 return os.path.join(self.path, '.hg', '.hgrc')
429 return os.path.join(self.path, '.hg', '.hgrc')
430
430
431 def get_changeset(self, revision=None):
431 def get_changeset(self, revision=None):
432 """
432 """
433 Returns ``MercurialChangeset`` object representing repository's
433 Returns ``MercurialChangeset`` object representing repository's
434 changeset at the given ``revision``.
434 changeset at the given ``revision``.
435 """
435 """
436 revision = self._get_revision(revision)
436 revision = self._get_revision(revision)
437 changeset = MercurialChangeset(repository=self, revision=revision)
437 changeset = MercurialChangeset(repository=self, revision=revision)
438 return changeset
438 return changeset
439
439
440 def get_changesets(self, start=None, end=None, start_date=None,
440 def get_changesets(self, start=None, end=None, start_date=None,
441 end_date=None, branch_name=None, reverse=False):
441 end_date=None, branch_name=None, reverse=False):
442 """
442 """
443 Returns iterator of ``MercurialChangeset`` objects from start to end
443 Returns iterator of ``MercurialChangeset`` objects from start to end
444 (both are inclusive)
444 (both are inclusive)
445
445
446 :param start: None, str, int or mercurial lookup format
446 :param start: None, str, int or mercurial lookup format
447 :param end: None, str, int or mercurial lookup format
447 :param end: None, str, int or mercurial lookup format
448 :param start_date:
448 :param start_date:
449 :param end_date:
449 :param end_date:
450 :param branch_name:
450 :param branch_name:
451 :param reversed: return changesets in reversed order
451 :param reversed: return changesets in reversed order
452 """
452 """
453
453
454 start_raw_id = self._get_revision(start)
454 start_raw_id = self._get_revision(start)
455 start_pos = self.revisions.index(start_raw_id) if start else None
455 start_pos = self.revisions.index(start_raw_id) if start else None
456 end_raw_id = self._get_revision(end)
456 end_raw_id = self._get_revision(end)
457 end_pos = self.revisions.index(end_raw_id) if end else None
457 end_pos = self.revisions.index(end_raw_id) if end else None
458
458
459 if None not in [start, end] and start_pos > end_pos:
459 if None not in [start, end] and start_pos > end_pos:
460 raise RepositoryError("start revision '%s' cannot be "
460 raise RepositoryError("Start revision '%s' cannot be "
461 "after end revision '%s'" % (start, end))
461 "after end revision '%s'" % (start, end))
462
462
463 if branch_name and branch_name not in self.branches.keys():
463 if branch_name and branch_name not in self.branches.keys():
464 raise BranchDoesNotExistError('Such branch %s does not exists for'
464 raise BranchDoesNotExistError('Branch %s not found in'
465 ' this repository' % branch_name)
465 ' this repository' % branch_name)
466 if end_pos is not None:
466 if end_pos is not None:
467 end_pos += 1
467 end_pos += 1
468
468
469 slice_ = reversed(self.revisions[start_pos:end_pos]) if reverse else \
469 slice_ = reversed(self.revisions[start_pos:end_pos]) if reverse else \
470 self.revisions[start_pos:end_pos]
470 self.revisions[start_pos:end_pos]
471
471
472 for id_ in slice_:
472 for id_ in slice_:
473 cs = self.get_changeset(id_)
473 cs = self.get_changeset(id_)
474 if branch_name and cs.branch != branch_name:
474 if branch_name and cs.branch != branch_name:
475 continue
475 continue
476 if start_date and cs.date < start_date:
476 if start_date and cs.date < start_date:
477 continue
477 continue
478 if end_date and cs.date > end_date:
478 if end_date and cs.date > end_date:
479 continue
479 continue
480
480
481 yield cs
481 yield cs
482
482
483 def pull(self, url):
483 def pull(self, url):
484 """
484 """
485 Tries to pull changes from external location.
485 Tries to pull changes from external location.
486 """
486 """
487 url = self._get_url(url)
487 url = self._get_url(url)
488 try:
488 try:
489 pull(self.baseui, self._repo, url)
489 pull(self.baseui, self._repo, url)
490 except Abort, err:
490 except Abort, err:
491 # Propagate error but with vcs's type
491 # Propagate error but with vcs's type
492 raise RepositoryError(str(err))
492 raise RepositoryError(str(err))
493
493
494 @LazyProperty
494 @LazyProperty
495 def workdir(self):
495 def workdir(self):
496 """
496 """
497 Returns ``Workdir`` instance for this repository.
497 Returns ``Workdir`` instance for this repository.
498 """
498 """
499 return MercurialWorkdir(self)
499 return MercurialWorkdir(self)
500
500
501 def get_config_value(self, section, name=None, config_file=None):
501 def get_config_value(self, section, name=None, config_file=None):
502 """
502 """
503 Returns configuration value for a given [``section``] and ``name``.
503 Returns configuration value for a given [``section``] and ``name``.
504
504
505 :param section: Section we want to retrieve value from
505 :param section: Section we want to retrieve value from
506 :param name: Name of configuration we want to retrieve
506 :param name: Name of configuration we want to retrieve
507 :param config_file: A path to file which should be used to retrieve
507 :param config_file: A path to file which should be used to retrieve
508 configuration from (might also be a list of file paths)
508 configuration from (might also be a list of file paths)
509 """
509 """
510 if config_file is None:
510 if config_file is None:
511 config_file = []
511 config_file = []
512 elif isinstance(config_file, basestring):
512 elif isinstance(config_file, basestring):
513 config_file = [config_file]
513 config_file = [config_file]
514
514
515 config = self._repo.ui
515 config = self._repo.ui
516 for path in config_file:
516 for path in config_file:
517 config.readconfig(path)
517 config.readconfig(path)
518 return config.config(section, name)
518 return config.config(section, name)
519
519
520 def get_user_name(self, config_file=None):
520 def get_user_name(self, config_file=None):
521 """
521 """
522 Returns user's name from global configuration file.
522 Returns user's name from global configuration file.
523
523
524 :param config_file: A path to file which should be used to retrieve
524 :param config_file: A path to file which should be used to retrieve
525 configuration from (might also be a list of file paths)
525 configuration from (might also be a list of file paths)
526 """
526 """
527 username = self.get_config_value('ui', 'username')
527 username = self.get_config_value('ui', 'username')
528 if username:
528 if username:
529 return author_name(username)
529 return author_name(username)
530 return None
530 return None
531
531
532 def get_user_email(self, config_file=None):
532 def get_user_email(self, config_file=None):
533 """
533 """
534 Returns user's email from global configuration file.
534 Returns user's email from global configuration file.
535
535
536 :param config_file: A path to file which should be used to retrieve
536 :param config_file: A path to file which should be used to retrieve
537 configuration from (might also be a list of file paths)
537 configuration from (might also be a list of file paths)
538 """
538 """
539 username = self.get_config_value('ui', 'username')
539 username = self.get_config_value('ui', 'username')
540 if username:
540 if username:
541 return author_email(username)
541 return author_email(username)
542 return None
542 return None
General Comments 0
You need to be logged in to leave comments. Login now