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