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