##// END OF EJS Templates
Use lower level API from mercurial when doing a pull
marcink -
r3877:5070c840 beta
parent child Browse files
Show More
@@ -1,571 +1,572 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 vcs.backends.hg.repository
3 vcs.backends.hg.repository
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Mercurial repository implementation.
6 Mercurial repository 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 time
13 import time
14 import urllib
14 import urllib
15 import urllib2
15 import urllib2
16 import logging
16 import logging
17 import datetime
17 import datetime
18
18
19
19
20 from rhodecode.lib.vcs.backends.base import BaseRepository, CollectionGenerator
20 from rhodecode.lib.vcs.backends.base import BaseRepository, CollectionGenerator
21 from rhodecode.lib.vcs.conf import settings
21 from rhodecode.lib.vcs.conf import settings
22
22
23 from rhodecode.lib.vcs.exceptions import (
23 from rhodecode.lib.vcs.exceptions import (
24 BranchDoesNotExistError, ChangesetDoesNotExistError, EmptyRepositoryError,
24 BranchDoesNotExistError, ChangesetDoesNotExistError, EmptyRepositoryError,
25 RepositoryError, VCSError, TagAlreadyExistError, TagDoesNotExistError
25 RepositoryError, VCSError, TagAlreadyExistError, TagDoesNotExistError
26 )
26 )
27 from rhodecode.lib.vcs.utils import (
27 from rhodecode.lib.vcs.utils import (
28 author_email, author_name, date_fromtimestamp, makedate, safe_unicode
28 author_email, author_name, date_fromtimestamp, makedate, safe_unicode
29 )
29 )
30 from rhodecode.lib.vcs.utils.lazy import LazyProperty
30 from rhodecode.lib.vcs.utils.lazy import LazyProperty
31 from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
31 from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
32 from rhodecode.lib.vcs.utils.paths import abspath
32 from rhodecode.lib.vcs.utils.paths import abspath
33 from rhodecode.lib.vcs.utils.hgcompat import (
33 from rhodecode.lib.vcs.utils.hgcompat import (
34 ui, nullid, match, patch, diffopts, clone, get_contact, pull,
34 ui, nullid, match, patch, diffopts, clone, get_contact, pull,
35 localrepository, RepoLookupError, Abort, RepoError, hex, scmutil, hg_url,
35 localrepository, RepoLookupError, Abort, RepoError, hex, scmutil, hg_url,
36 httpbasicauthhandler, httpdigestauthhandler
36 httpbasicauthhandler, httpdigestauthhandler, peer
37 )
37 )
38
38
39 from .changeset import MercurialChangeset
39 from .changeset import MercurialChangeset
40 from .inmemory import MercurialInMemoryChangeset
40 from .inmemory import MercurialInMemoryChangeset
41 from .workdir import MercurialWorkdir
41 from .workdir import MercurialWorkdir
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class MercurialRepository(BaseRepository):
46 class MercurialRepository(BaseRepository):
47 """
47 """
48 Mercurial repository backend
48 Mercurial repository backend
49 """
49 """
50 DEFAULT_BRANCH_NAME = 'default'
50 DEFAULT_BRANCH_NAME = 'default'
51 scm = 'hg'
51 scm = 'hg'
52
52
53 def __init__(self, repo_path, create=False, baseui=None, src_url=None,
53 def __init__(self, repo_path, create=False, baseui=None, src_url=None,
54 update_after_clone=False):
54 update_after_clone=False):
55 """
55 """
56 Raises RepositoryError if repository could not be find at the given
56 Raises RepositoryError if repository could not be find at the given
57 ``repo_path``.
57 ``repo_path``.
58
58
59 :param repo_path: local path of the repository
59 :param repo_path: local path of the repository
60 :param create=False: if set to True, would try to create repository if
60 :param create=False: if set to True, would try to create repository if
61 it does not exist rather than raising exception
61 it does not exist rather than raising exception
62 :param baseui=None: user data
62 :param baseui=None: user data
63 :param src_url=None: would try to clone repository from given location
63 :param src_url=None: would try to clone repository from given location
64 :param update_after_clone=False: sets update of working copy after
64 :param update_after_clone=False: sets update of working copy after
65 making a clone
65 making a clone
66 """
66 """
67
67
68 if not isinstance(repo_path, str):
68 if not isinstance(repo_path, str):
69 raise VCSError('Mercurial backend requires repository path to '
69 raise VCSError('Mercurial backend requires repository path to '
70 'be instance of <str> got %s instead' %
70 'be instance of <str> got %s instead' %
71 type(repo_path))
71 type(repo_path))
72
72
73 self.path = abspath(repo_path)
73 self.path = abspath(repo_path)
74 self.baseui = baseui or ui.ui()
74 self.baseui = baseui or ui.ui()
75 # We've set path and ui, now we can set _repo itself
75 # We've set path and ui, now we can set _repo itself
76 self._repo = self._get_repo(create, src_url, update_after_clone)
76 self._repo = self._get_repo(create, src_url, update_after_clone)
77
77
78 @property
78 @property
79 def _empty(self):
79 def _empty(self):
80 """
80 """
81 Checks if repository is empty ie. without any changesets
81 Checks if repository is empty ie. without any changesets
82 """
82 """
83 # TODO: Following raises errors when using InMemoryChangeset...
83 # TODO: Following raises errors when using InMemoryChangeset...
84 # return len(self._repo.changelog) == 0
84 # return len(self._repo.changelog) == 0
85 return len(self.revisions) == 0
85 return len(self.revisions) == 0
86
86
87 @LazyProperty
87 @LazyProperty
88 def revisions(self):
88 def revisions(self):
89 """
89 """
90 Returns list of revisions' ids, in ascending order. Being lazy
90 Returns list of revisions' ids, in ascending order. Being lazy
91 attribute allows external tools to inject shas from cache.
91 attribute allows external tools to inject shas from cache.
92 """
92 """
93 return self._get_all_revisions()
93 return self._get_all_revisions()
94
94
95 @LazyProperty
95 @LazyProperty
96 def name(self):
96 def name(self):
97 return os.path.basename(self.path)
97 return os.path.basename(self.path)
98
98
99 @LazyProperty
99 @LazyProperty
100 def branches(self):
100 def branches(self):
101 return self._get_branches()
101 return self._get_branches()
102
102
103 @LazyProperty
103 @LazyProperty
104 def allbranches(self):
104 def allbranches(self):
105 """
105 """
106 List all branches, including closed branches.
106 List all branches, including closed branches.
107 """
107 """
108 return self._get_branches(closed=True)
108 return self._get_branches(closed=True)
109
109
110 def _get_branches(self, closed=False):
110 def _get_branches(self, closed=False):
111 """
111 """
112 Get's branches for this repository
112 Get's branches for this repository
113 Returns only not closed branches by default
113 Returns only not closed branches by default
114
114
115 :param closed: return also closed branches for mercurial
115 :param closed: return also closed branches for mercurial
116 """
116 """
117
117
118 if self._empty:
118 if self._empty:
119 return {}
119 return {}
120
120
121 def _branchtags(localrepo):
121 def _branchtags(localrepo):
122 """
122 """
123 Patched version of mercurial branchtags to not return the closed
123 Patched version of mercurial branchtags to not return the closed
124 branches
124 branches
125
125
126 :param localrepo: locarepository instance
126 :param localrepo: locarepository instance
127 """
127 """
128
128
129 bt = {}
129 bt = {}
130 bt_closed = {}
130 bt_closed = {}
131 for bn, heads in localrepo.branchmap().iteritems():
131 for bn, heads in localrepo.branchmap().iteritems():
132 tip = heads[-1]
132 tip = heads[-1]
133 if 'close' in localrepo.changelog.read(tip)[5]:
133 if 'close' in localrepo.changelog.read(tip)[5]:
134 bt_closed[bn] = tip
134 bt_closed[bn] = tip
135 else:
135 else:
136 bt[bn] = tip
136 bt[bn] = tip
137
137
138 if closed:
138 if closed:
139 bt.update(bt_closed)
139 bt.update(bt_closed)
140 return bt
140 return bt
141
141
142 sortkey = lambda ctx: ctx[0] # sort by name
142 sortkey = lambda ctx: ctx[0] # sort by name
143 _branches = [(safe_unicode(n), hex(h),) for n, h in
143 _branches = [(safe_unicode(n), hex(h),) for n, h in
144 _branchtags(self._repo).items()]
144 _branchtags(self._repo).items()]
145
145
146 return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
146 return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
147
147
148 @LazyProperty
148 @LazyProperty
149 def tags(self):
149 def tags(self):
150 """
150 """
151 Get's tags for this repository
151 Get's tags for this repository
152 """
152 """
153 return self._get_tags()
153 return self._get_tags()
154
154
155 def _get_tags(self):
155 def _get_tags(self):
156 if self._empty:
156 if self._empty:
157 return {}
157 return {}
158
158
159 sortkey = lambda ctx: ctx[0] # sort by name
159 sortkey = lambda ctx: ctx[0] # sort by name
160 _tags = [(safe_unicode(n), hex(h),) for n, h in
160 _tags = [(safe_unicode(n), hex(h),) for n, h in
161 self._repo.tags().items()]
161 self._repo.tags().items()]
162
162
163 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
163 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
164
164
165 def tag(self, name, user, revision=None, message=None, date=None,
165 def tag(self, name, user, revision=None, message=None, date=None,
166 **kwargs):
166 **kwargs):
167 """
167 """
168 Creates and returns a tag for the given ``revision``.
168 Creates and returns a tag for the given ``revision``.
169
169
170 :param name: name for new tag
170 :param name: name for new tag
171 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
171 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
172 :param revision: changeset id for which new tag would be created
172 :param revision: changeset id for which new tag would be created
173 :param message: message of the tag's commit
173 :param message: message of the tag's commit
174 :param date: date of tag's commit
174 :param date: date of tag's commit
175
175
176 :raises TagAlreadyExistError: if tag with same name already exists
176 :raises TagAlreadyExistError: if tag with same name already exists
177 """
177 """
178 if name in self.tags:
178 if name in self.tags:
179 raise TagAlreadyExistError("Tag %s already exists" % name)
179 raise TagAlreadyExistError("Tag %s already exists" % name)
180 changeset = self.get_changeset(revision)
180 changeset = self.get_changeset(revision)
181 local = kwargs.setdefault('local', False)
181 local = kwargs.setdefault('local', False)
182
182
183 if message is None:
183 if message is None:
184 message = "Added tag %s for changeset %s" % (name,
184 message = "Added tag %s for changeset %s" % (name,
185 changeset.short_id)
185 changeset.short_id)
186
186
187 if date is None:
187 if date is None:
188 date = datetime.datetime.now().ctime()
188 date = datetime.datetime.now().ctime()
189
189
190 try:
190 try:
191 self._repo.tag(name, changeset._ctx.node(), message, local, user,
191 self._repo.tag(name, changeset._ctx.node(), message, local, user,
192 date)
192 date)
193 except Abort, e:
193 except Abort, e:
194 raise RepositoryError(e.message)
194 raise RepositoryError(e.message)
195
195
196 # Reinitialize tags
196 # Reinitialize tags
197 self.tags = self._get_tags()
197 self.tags = self._get_tags()
198 tag_id = self.tags[name]
198 tag_id = self.tags[name]
199
199
200 return self.get_changeset(revision=tag_id)
200 return self.get_changeset(revision=tag_id)
201
201
202 def remove_tag(self, name, user, message=None, date=None):
202 def remove_tag(self, name, user, message=None, date=None):
203 """
203 """
204 Removes tag with the given ``name``.
204 Removes tag with the given ``name``.
205
205
206 :param name: name of the tag to be removed
206 :param name: name of the tag to be removed
207 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
207 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
208 :param message: message of the tag's removal commit
208 :param message: message of the tag's removal commit
209 :param date: date of tag's removal commit
209 :param date: date of tag's removal commit
210
210
211 :raises TagDoesNotExistError: if tag with given name does not exists
211 :raises TagDoesNotExistError: if tag with given name does not exists
212 """
212 """
213 if name not in self.tags:
213 if name not in self.tags:
214 raise TagDoesNotExistError("Tag %s does not exist" % name)
214 raise TagDoesNotExistError("Tag %s does not exist" % name)
215 if message is None:
215 if message is None:
216 message = "Removed tag %s" % name
216 message = "Removed tag %s" % name
217 if date is None:
217 if date is None:
218 date = datetime.datetime.now().ctime()
218 date = datetime.datetime.now().ctime()
219 local = False
219 local = False
220
220
221 try:
221 try:
222 self._repo.tag(name, nullid, message, local, user, date)
222 self._repo.tag(name, nullid, message, local, user, date)
223 self.tags = self._get_tags()
223 self.tags = self._get_tags()
224 except Abort, e:
224 except Abort, e:
225 raise RepositoryError(e.message)
225 raise RepositoryError(e.message)
226
226
227 @LazyProperty
227 @LazyProperty
228 def bookmarks(self):
228 def bookmarks(self):
229 """
229 """
230 Get's bookmarks for this repository
230 Get's bookmarks for this repository
231 """
231 """
232 return self._get_bookmarks()
232 return self._get_bookmarks()
233
233
234 def _get_bookmarks(self):
234 def _get_bookmarks(self):
235 if self._empty:
235 if self._empty:
236 return {}
236 return {}
237
237
238 sortkey = lambda ctx: ctx[0] # sort by name
238 sortkey = lambda ctx: ctx[0] # sort by name
239 _bookmarks = [(safe_unicode(n), hex(h),) for n, h in
239 _bookmarks = [(safe_unicode(n), hex(h),) for n, h in
240 self._repo._bookmarks.items()]
240 self._repo._bookmarks.items()]
241 return OrderedDict(sorted(_bookmarks, key=sortkey, reverse=True))
241 return OrderedDict(sorted(_bookmarks, key=sortkey, reverse=True))
242
242
243 def _get_all_revisions(self):
243 def _get_all_revisions(self):
244
244
245 return map(lambda x: hex(x[7]), self._repo.changelog.index)[:-1]
245 return map(lambda x: hex(x[7]), self._repo.changelog.index)[:-1]
246
246
247 def get_diff(self, rev1, rev2, path='', ignore_whitespace=False,
247 def get_diff(self, rev1, rev2, path='', ignore_whitespace=False,
248 context=3):
248 context=3):
249 """
249 """
250 Returns (git like) *diff*, as plain text. Shows changes introduced by
250 Returns (git like) *diff*, as plain text. Shows changes introduced by
251 ``rev2`` since ``rev1``.
251 ``rev2`` since ``rev1``.
252
252
253 :param rev1: Entry point from which diff is shown. Can be
253 :param rev1: Entry point from which diff is shown. Can be
254 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
254 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
255 the changes since empty state of the repository until ``rev2``
255 the changes since empty state of the repository until ``rev2``
256 :param rev2: Until which revision changes should be shown.
256 :param rev2: Until which revision changes should be shown.
257 :param ignore_whitespace: If set to ``True``, would not show whitespace
257 :param ignore_whitespace: If set to ``True``, would not show whitespace
258 changes. Defaults to ``False``.
258 changes. Defaults to ``False``.
259 :param context: How many lines before/after changed lines should be
259 :param context: How many lines before/after changed lines should be
260 shown. Defaults to ``3``.
260 shown. Defaults to ``3``.
261 """
261 """
262 if hasattr(rev1, 'raw_id'):
262 if hasattr(rev1, 'raw_id'):
263 rev1 = getattr(rev1, 'raw_id')
263 rev1 = getattr(rev1, 'raw_id')
264
264
265 if hasattr(rev2, 'raw_id'):
265 if hasattr(rev2, 'raw_id'):
266 rev2 = getattr(rev2, 'raw_id')
266 rev2 = getattr(rev2, 'raw_id')
267
267
268 # Check if given revisions are present at repository (may raise
268 # Check if given revisions are present at repository (may raise
269 # ChangesetDoesNotExistError)
269 # ChangesetDoesNotExistError)
270 if rev1 != self.EMPTY_CHANGESET:
270 if rev1 != self.EMPTY_CHANGESET:
271 self.get_changeset(rev1)
271 self.get_changeset(rev1)
272 self.get_changeset(rev2)
272 self.get_changeset(rev2)
273 if path:
273 if path:
274 file_filter = match(self.path, '', [path])
274 file_filter = match(self.path, '', [path])
275 else:
275 else:
276 file_filter = None
276 file_filter = None
277
277
278 return ''.join(patch.diff(self._repo, rev1, rev2, match=file_filter,
278 return ''.join(patch.diff(self._repo, rev1, rev2, match=file_filter,
279 opts=diffopts(git=True,
279 opts=diffopts(git=True,
280 ignorews=ignore_whitespace,
280 ignorews=ignore_whitespace,
281 context=context)))
281 context=context)))
282
282
283 @classmethod
283 @classmethod
284 def _check_url(cls, url):
284 def _check_url(cls, url):
285 """
285 """
286 Function will check given url and try to verify if it's a valid
286 Function will check given url and try to verify if it's a valid
287 link. Sometimes it may happened that mercurial will issue basic
287 link. Sometimes it may happened that mercurial will issue basic
288 auth request that can cause whole API to hang when used from python
288 auth request that can cause whole API to hang when used from python
289 or other external calls.
289 or other external calls.
290
290
291 On failures it'll raise urllib2.HTTPError, return code 200 if url
291 On failures it'll raise urllib2.HTTPError, return code 200 if url
292 is valid or True if it's a local path
292 is valid or True if it's a local path
293 """
293 """
294
294
295 # check first if it's not an local url
295 # check first if it's not an local url
296 if os.path.isdir(url) or url.startswith('file:'):
296 if os.path.isdir(url) or url.startswith('file:'):
297 return True
297 return True
298
298
299 if('+' in url[:url.find('://')]):
299 if('+' in url[:url.find('://')]):
300 url = url[url.find('+') + 1:]
300 url = url[url.find('+') + 1:]
301
301
302 handlers = []
302 handlers = []
303 test_uri, authinfo = hg_url(url).authinfo()
303 test_uri, authinfo = hg_url(url).authinfo()
304
304
305 if authinfo:
305 if authinfo:
306 #create a password manager
306 #create a password manager
307 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
307 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
308 passmgr.add_password(*authinfo)
308 passmgr.add_password(*authinfo)
309
309
310 handlers.extend((httpbasicauthhandler(passmgr),
310 handlers.extend((httpbasicauthhandler(passmgr),
311 httpdigestauthhandler(passmgr)))
311 httpdigestauthhandler(passmgr)))
312
312
313 o = urllib2.build_opener(*handlers)
313 o = urllib2.build_opener(*handlers)
314 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
314 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
315 ('Accept', 'application/mercurial-0.1')]
315 ('Accept', 'application/mercurial-0.1')]
316
316
317 q = {"cmd": 'between'}
317 q = {"cmd": 'between'}
318 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
318 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
319 qs = '?%s' % urllib.urlencode(q)
319 qs = '?%s' % urllib.urlencode(q)
320 cu = "%s%s" % (test_uri, qs)
320 cu = "%s%s" % (test_uri, qs)
321 req = urllib2.Request(cu, None, {})
321 req = urllib2.Request(cu, None, {})
322
322
323 try:
323 try:
324 resp = o.open(req)
324 resp = o.open(req)
325 return resp.code == 200
325 return resp.code == 200
326 except Exception, e:
326 except Exception, e:
327 # means it cannot be cloned
327 # means it cannot be cloned
328 raise urllib2.URLError("[%s] %s" % (url, e))
328 raise urllib2.URLError("[%s] %s" % (url, e))
329
329
330 def _get_repo(self, create, src_url=None, update_after_clone=False):
330 def _get_repo(self, create, src_url=None, update_after_clone=False):
331 """
331 """
332 Function will check for mercurial repository in given path and return
332 Function will check for mercurial repository in given path and return
333 a localrepo object. If there is no repository in that path it will
333 a localrepo object. If there is no repository in that path it will
334 raise an exception unless ``create`` parameter is set to True - in
334 raise an exception unless ``create`` parameter is set to True - in
335 that case repository would be created and returned.
335 that case repository would be created and returned.
336 If ``src_url`` is given, would try to clone repository from the
336 If ``src_url`` is given, would try to clone repository from the
337 location at given clone_point. Additionally it'll make update to
337 location at given clone_point. Additionally it'll make update to
338 working copy accordingly to ``update_after_clone`` flag
338 working copy accordingly to ``update_after_clone`` flag
339 """
339 """
340
340
341 try:
341 try:
342 if src_url:
342 if src_url:
343 url = str(self._get_url(src_url))
343 url = str(self._get_url(src_url))
344 opts = {}
344 opts = {}
345 if not update_after_clone:
345 if not update_after_clone:
346 opts.update({'noupdate': True})
346 opts.update({'noupdate': True})
347 try:
347 try:
348 MercurialRepository._check_url(url)
348 MercurialRepository._check_url(url)
349 clone(self.baseui, url, self.path, **opts)
349 clone(self.baseui, url, self.path, **opts)
350 # except urllib2.URLError:
350 # except urllib2.URLError:
351 # raise Abort("Got HTTP 404 error")
351 # raise Abort("Got HTTP 404 error")
352 except Exception:
352 except Exception:
353 raise
353 raise
354
354
355 # Don't try to create if we've already cloned repo
355 # Don't try to create if we've already cloned repo
356 create = False
356 create = False
357 return localrepository(self.baseui, self.path, create=create)
357 return localrepository(self.baseui, self.path, create=create)
358 except (Abort, RepoError), err:
358 except (Abort, RepoError), err:
359 if create:
359 if create:
360 msg = "Cannot create repository at %s. Original error was %s"\
360 msg = "Cannot create repository at %s. Original error was %s"\
361 % (self.path, err)
361 % (self.path, err)
362 else:
362 else:
363 msg = "Not valid repository at %s. Original error was %s"\
363 msg = "Not valid repository at %s. Original error was %s"\
364 % (self.path, err)
364 % (self.path, err)
365 raise RepositoryError(msg)
365 raise RepositoryError(msg)
366
366
367 @LazyProperty
367 @LazyProperty
368 def in_memory_changeset(self):
368 def in_memory_changeset(self):
369 return MercurialInMemoryChangeset(self)
369 return MercurialInMemoryChangeset(self)
370
370
371 @LazyProperty
371 @LazyProperty
372 def description(self):
372 def description(self):
373 undefined_description = u'unknown'
373 undefined_description = u'unknown'
374 return safe_unicode(self._repo.ui.config('web', 'description',
374 return safe_unicode(self._repo.ui.config('web', 'description',
375 undefined_description, untrusted=True))
375 undefined_description, untrusted=True))
376
376
377 @LazyProperty
377 @LazyProperty
378 def contact(self):
378 def contact(self):
379 undefined_contact = u'Unknown'
379 undefined_contact = u'Unknown'
380 return safe_unicode(get_contact(self._repo.ui.config)
380 return safe_unicode(get_contact(self._repo.ui.config)
381 or undefined_contact)
381 or undefined_contact)
382
382
383 @LazyProperty
383 @LazyProperty
384 def last_change(self):
384 def last_change(self):
385 """
385 """
386 Returns last change made on this repository as datetime object
386 Returns last change made on this repository as datetime object
387 """
387 """
388 return date_fromtimestamp(self._get_mtime(), makedate()[1])
388 return date_fromtimestamp(self._get_mtime(), makedate()[1])
389
389
390 def _get_mtime(self):
390 def _get_mtime(self):
391 try:
391 try:
392 return time.mktime(self.get_changeset().date.timetuple())
392 return time.mktime(self.get_changeset().date.timetuple())
393 except RepositoryError:
393 except RepositoryError:
394 #fallback to filesystem
394 #fallback to filesystem
395 cl_path = os.path.join(self.path, '.hg', "00changelog.i")
395 cl_path = os.path.join(self.path, '.hg', "00changelog.i")
396 st_path = os.path.join(self.path, '.hg', "store")
396 st_path = os.path.join(self.path, '.hg', "store")
397 if os.path.exists(cl_path):
397 if os.path.exists(cl_path):
398 return os.stat(cl_path).st_mtime
398 return os.stat(cl_path).st_mtime
399 else:
399 else:
400 return os.stat(st_path).st_mtime
400 return os.stat(st_path).st_mtime
401
401
402 def _get_hidden(self):
402 def _get_hidden(self):
403 return self._repo.ui.configbool("web", "hidden", untrusted=True)
403 return self._repo.ui.configbool("web", "hidden", untrusted=True)
404
404
405 def _get_revision(self, revision):
405 def _get_revision(self, revision):
406 """
406 """
407 Get's an ID revision given as str. This will always return a fill
407 Get's an ID revision given as str. This will always return a fill
408 40 char revision number
408 40 char revision number
409
409
410 :param revision: str or int or None
410 :param revision: str or int or None
411 """
411 """
412
412
413 if self._empty:
413 if self._empty:
414 raise EmptyRepositoryError("There are no changesets yet")
414 raise EmptyRepositoryError("There are no changesets yet")
415
415
416 if revision in [-1, 'tip', None]:
416 if revision in [-1, 'tip', None]:
417 revision = 'tip'
417 revision = 'tip'
418
418
419 try:
419 try:
420 revision = hex(self._repo.lookup(revision))
420 revision = hex(self._repo.lookup(revision))
421 except (IndexError, ValueError, RepoLookupError, TypeError):
421 except (IndexError, ValueError, RepoLookupError, TypeError):
422 raise ChangesetDoesNotExistError("Revision %s does not "
422 raise ChangesetDoesNotExistError("Revision %s does not "
423 "exist for this repository"
423 "exist for this repository"
424 % (revision))
424 % (revision))
425 return revision
425 return revision
426
426
427 def _get_archives(self, archive_name='tip'):
427 def _get_archives(self, archive_name='tip'):
428 allowed = self.baseui.configlist("web", "allow_archive",
428 allowed = self.baseui.configlist("web", "allow_archive",
429 untrusted=True)
429 untrusted=True)
430 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
430 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
431 if i[0] in allowed or self._repo.ui.configbool("web",
431 if i[0] in allowed or self._repo.ui.configbool("web",
432 "allow" + i[0],
432 "allow" + i[0],
433 untrusted=True):
433 untrusted=True):
434 yield {"type": i[0], "extension": i[1], "node": archive_name}
434 yield {"type": i[0], "extension": i[1], "node": archive_name}
435
435
436 def _get_url(self, url):
436 def _get_url(self, url):
437 """
437 """
438 Returns normalized url. If schema is not given, would fall
438 Returns normalized url. If schema is not given, would fall
439 to filesystem
439 to filesystem
440 (``file:///``) schema.
440 (``file:///``) schema.
441 """
441 """
442 url = str(url)
442 url = str(url)
443 if url != 'default' and not '://' in url:
443 if url != 'default' and not '://' in url:
444 url = "file:" + urllib.pathname2url(url)
444 url = "file:" + urllib.pathname2url(url)
445 return url
445 return url
446
446
447 def get_hook_location(self):
447 def get_hook_location(self):
448 """
448 """
449 returns absolute path to location where hooks are stored
449 returns absolute path to location where hooks are stored
450 """
450 """
451 return os.path.join(self.path, '.hg', '.hgrc')
451 return os.path.join(self.path, '.hg', '.hgrc')
452
452
453 def get_changeset(self, revision=None):
453 def get_changeset(self, revision=None):
454 """
454 """
455 Returns ``MercurialChangeset`` object representing repository's
455 Returns ``MercurialChangeset`` object representing repository's
456 changeset at the given ``revision``.
456 changeset at the given ``revision``.
457 """
457 """
458 revision = self._get_revision(revision)
458 revision = self._get_revision(revision)
459 changeset = MercurialChangeset(repository=self, revision=revision)
459 changeset = MercurialChangeset(repository=self, revision=revision)
460 return changeset
460 return changeset
461
461
462 def get_changesets(self, start=None, end=None, start_date=None,
462 def get_changesets(self, start=None, end=None, start_date=None,
463 end_date=None, branch_name=None, reverse=False):
463 end_date=None, branch_name=None, reverse=False):
464 """
464 """
465 Returns iterator of ``MercurialChangeset`` objects from start to end
465 Returns iterator of ``MercurialChangeset`` objects from start to end
466 (both are inclusive)
466 (both are inclusive)
467
467
468 :param start: None, str, int or mercurial lookup format
468 :param start: None, str, int or mercurial lookup format
469 :param end: None, str, int or mercurial lookup format
469 :param end: None, str, int or mercurial lookup format
470 :param start_date:
470 :param start_date:
471 :param end_date:
471 :param end_date:
472 :param branch_name:
472 :param branch_name:
473 :param reversed: return changesets in reversed order
473 :param reversed: return changesets in reversed order
474 """
474 """
475
475
476 start_raw_id = self._get_revision(start)
476 start_raw_id = self._get_revision(start)
477 start_pos = self.revisions.index(start_raw_id) if start else None
477 start_pos = self.revisions.index(start_raw_id) if start else None
478 end_raw_id = self._get_revision(end)
478 end_raw_id = self._get_revision(end)
479 end_pos = self.revisions.index(end_raw_id) if end else None
479 end_pos = self.revisions.index(end_raw_id) if end else None
480
480
481 if None not in [start, end] and start_pos > end_pos:
481 if None not in [start, end] and start_pos > end_pos:
482 raise RepositoryError("Start revision '%s' cannot be "
482 raise RepositoryError("Start revision '%s' cannot be "
483 "after end revision '%s'" % (start, end))
483 "after end revision '%s'" % (start, end))
484
484
485 if branch_name and branch_name not in self.allbranches.keys():
485 if branch_name and branch_name not in self.allbranches.keys():
486 raise BranchDoesNotExistError('Branch %s not found in'
486 raise BranchDoesNotExistError('Branch %s not found in'
487 ' this repository' % branch_name)
487 ' this repository' % branch_name)
488 if end_pos is not None:
488 if end_pos is not None:
489 end_pos += 1
489 end_pos += 1
490 #filter branches
490 #filter branches
491 filter_ = []
491 filter_ = []
492 if branch_name:
492 if branch_name:
493 filter_.append('branch("%s")' % (branch_name))
493 filter_.append('branch("%s")' % (branch_name))
494
494
495 if start_date and not end_date:
495 if start_date and not end_date:
496 filter_.append('date(">%s")' % start_date)
496 filter_.append('date(">%s")' % start_date)
497 if end_date and not start_date:
497 if end_date and not start_date:
498 filter_.append('date("<%s")' % end_date)
498 filter_.append('date("<%s")' % end_date)
499 if start_date and end_date:
499 if start_date and end_date:
500 filter_.append('date(">%s") and date("<%s")' % (start_date, end_date))
500 filter_.append('date(">%s") and date("<%s")' % (start_date, end_date))
501 if filter_:
501 if filter_:
502 revisions = scmutil.revrange(self._repo, filter_)
502 revisions = scmutil.revrange(self._repo, filter_)
503 else:
503 else:
504 revisions = self.revisions
504 revisions = self.revisions
505
505
506 revs = revisions[start_pos:end_pos]
506 revs = revisions[start_pos:end_pos]
507 if reverse:
507 if reverse:
508 revs = reversed(revs)
508 revs = reversed(revs)
509
509
510 return CollectionGenerator(self, revs)
510 return CollectionGenerator(self, revs)
511
511
512 def pull(self, url):
512 def pull(self, url):
513 """
513 """
514 Tries to pull changes from external location.
514 Tries to pull changes from external location.
515 """
515 """
516 url = self._get_url(url)
516 url = self._get_url(url)
517 try:
517 try:
518 pull(self.baseui, self._repo, url)
518 other = peer(self._repo, {}, url)
519 self._repo.pull(other, heads=None, force=None)
519 except Abort, err:
520 except Abort, err:
520 # Propagate error but with vcs's type
521 # Propagate error but with vcs's type
521 raise RepositoryError(str(err))
522 raise RepositoryError(str(err))
522
523
523 @LazyProperty
524 @LazyProperty
524 def workdir(self):
525 def workdir(self):
525 """
526 """
526 Returns ``Workdir`` instance for this repository.
527 Returns ``Workdir`` instance for this repository.
527 """
528 """
528 return MercurialWorkdir(self)
529 return MercurialWorkdir(self)
529
530
530 def get_config_value(self, section, name=None, config_file=None):
531 def get_config_value(self, section, name=None, config_file=None):
531 """
532 """
532 Returns configuration value for a given [``section``] and ``name``.
533 Returns configuration value for a given [``section``] and ``name``.
533
534
534 :param section: Section we want to retrieve value from
535 :param section: Section we want to retrieve value from
535 :param name: Name of configuration we want to retrieve
536 :param name: Name of configuration we want to retrieve
536 :param config_file: A path to file which should be used to retrieve
537 :param config_file: A path to file which should be used to retrieve
537 configuration from (might also be a list of file paths)
538 configuration from (might also be a list of file paths)
538 """
539 """
539 if config_file is None:
540 if config_file is None:
540 config_file = []
541 config_file = []
541 elif isinstance(config_file, basestring):
542 elif isinstance(config_file, basestring):
542 config_file = [config_file]
543 config_file = [config_file]
543
544
544 config = self._repo.ui
545 config = self._repo.ui
545 for path in config_file:
546 for path in config_file:
546 config.readconfig(path)
547 config.readconfig(path)
547 return config.config(section, name)
548 return config.config(section, name)
548
549
549 def get_user_name(self, config_file=None):
550 def get_user_name(self, config_file=None):
550 """
551 """
551 Returns user's name from global configuration file.
552 Returns user's name from global configuration file.
552
553
553 :param config_file: A path to file which should be used to retrieve
554 :param config_file: A path to file which should be used to retrieve
554 configuration from (might also be a list of file paths)
555 configuration from (might also be a list of file paths)
555 """
556 """
556 username = self.get_config_value('ui', 'username')
557 username = self.get_config_value('ui', 'username')
557 if username:
558 if username:
558 return author_name(username)
559 return author_name(username)
559 return None
560 return None
560
561
561 def get_user_email(self, config_file=None):
562 def get_user_email(self, config_file=None):
562 """
563 """
563 Returns user's email from global configuration file.
564 Returns user's email from global configuration file.
564
565
565 :param config_file: A path to file which should be used to retrieve
566 :param config_file: A path to file which should be used to retrieve
566 configuration from (might also be a list of file paths)
567 configuration from (might also be a list of file paths)
567 """
568 """
568 username = self.get_config_value('ui', 'username')
569 username = self.get_config_value('ui', 'username')
569 if username:
570 if username:
570 return author_email(username)
571 return author_email(username)
571 return None
572 return None
@@ -1,24 +1,25 b''
1 """
1 """
2 Mercurial libs compatibility
2 Mercurial libs compatibility
3 """
3 """
4
4
5 from mercurial import archival, merge as hg_merge, patch, ui
5 from mercurial import archival, merge as hg_merge, patch, ui
6 from mercurial.commands import clone, nullid, pull
6 from mercurial.commands import clone, nullid, pull
7 from mercurial.context import memctx, memfilectx
7 from mercurial.context import memctx, memfilectx
8 from mercurial.error import RepoError, RepoLookupError, Abort
8 from mercurial.error import RepoError, RepoLookupError, Abort
9 from mercurial.hgweb.common import get_contact
9 from mercurial.hgweb.common import get_contact
10 from mercurial.localrepo import localrepository
10 from mercurial.localrepo import localrepository
11 from mercurial.match import match
11 from mercurial.match import match
12 from mercurial.mdiff import diffopts
12 from mercurial.mdiff import diffopts
13 from mercurial.node import hex
13 from mercurial.node import hex
14 from mercurial.encoding import tolocal
14 from mercurial.encoding import tolocal
15 from mercurial import discovery
15 from mercurial import discovery
16 from mercurial import localrepo
16 from mercurial import localrepo
17 from mercurial import scmutil
17 from mercurial import scmutil
18 from mercurial.discovery import findcommonoutgoing
18 from mercurial.discovery import findcommonoutgoing
19 from mercurial.hg import peer
19
20
20 from mercurial.util import url as hg_url
21 from mercurial.util import url as hg_url
21
22
22 # those authnadlers are patched for python 2.6.5 bug an
23 # those authnadlers are patched for python 2.6.5 bug an
23 # infinit looping when given invalid resources
24 # infinit looping when given invalid resources
24 from mercurial.url import httpbasicauthhandler, httpdigestauthhandler
25 from mercurial.url import httpbasicauthhandler, httpdigestauthhandler
General Comments 0
You need to be logged in to leave comments. Login now