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