##// END OF EJS Templates
vcs: Fix internal server error when trying to get diff from Mercurial for paths that include globbing patterns (Issue #308):...
Branko Majic -
r7092:b4a56327 stable
parent child Browse files
Show More
@@ -1,627 +1,627 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 vcs.backends.hg.repository
3 vcs.backends.hg.repository
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Mercurial repository implementation.
6 Mercurial repository implementation.
7
7
8 :created_on: Apr 8, 2010
8 :created_on: Apr 8, 2010
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 """
10 """
11
11
12 import os
12 import os
13 import time
13 import time
14 import urllib
14 import urllib
15 import urllib2
15 import urllib2
16 import logging
16 import logging
17 import datetime
17 import datetime
18
18
19 from kallithea.lib.vcs.backends.base import BaseRepository, CollectionGenerator
19 from kallithea.lib.vcs.backends.base import BaseRepository, CollectionGenerator
20
20
21 from kallithea.lib.vcs.exceptions import (
21 from kallithea.lib.vcs.exceptions import (
22 BranchDoesNotExistError, ChangesetDoesNotExistError, EmptyRepositoryError,
22 BranchDoesNotExistError, ChangesetDoesNotExistError, EmptyRepositoryError,
23 RepositoryError, VCSError, TagAlreadyExistError, TagDoesNotExistError
23 RepositoryError, VCSError, TagAlreadyExistError, TagDoesNotExistError
24 )
24 )
25 from kallithea.lib.vcs.utils import (
25 from kallithea.lib.vcs.utils import (
26 author_email, author_name, date_fromtimestamp, makedate, safe_unicode, safe_str,
26 author_email, author_name, date_fromtimestamp, makedate, safe_unicode, safe_str,
27 )
27 )
28 from kallithea.lib.vcs.utils.lazy import LazyProperty
28 from kallithea.lib.vcs.utils.lazy import LazyProperty
29 from kallithea.lib.vcs.utils.ordered_dict import OrderedDict
29 from kallithea.lib.vcs.utils.ordered_dict import OrderedDict
30 from kallithea.lib.vcs.utils.paths import abspath
30 from kallithea.lib.vcs.utils.paths import abspath
31 from kallithea.lib.vcs.utils.hgcompat import (
31 from kallithea.lib.vcs.utils.hgcompat import (
32 ui, nullid, match, patch, diffopts, clone, get_contact,
32 ui, nullid, match, patch, diffopts, clone, get_contact,
33 localrepository, RepoLookupError, Abort, RepoError, hex, scmutil, hg_url,
33 localrepository, RepoLookupError, Abort, RepoError, hex, scmutil, hg_url,
34 httpbasicauthhandler, httpdigestauthhandler, peer, httppeer, sshpeer
34 httpbasicauthhandler, httpdigestauthhandler, peer, httppeer, sshpeer
35 )
35 )
36
36
37 from .changeset import MercurialChangeset
37 from .changeset import MercurialChangeset
38 from .inmemory import MercurialInMemoryChangeset
38 from .inmemory import MercurialInMemoryChangeset
39 from .workdir import MercurialWorkdir
39 from .workdir import MercurialWorkdir
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43
43
44 class MercurialRepository(BaseRepository):
44 class MercurialRepository(BaseRepository):
45 """
45 """
46 Mercurial repository backend
46 Mercurial repository backend
47 """
47 """
48 DEFAULT_BRANCH_NAME = 'default'
48 DEFAULT_BRANCH_NAME = 'default'
49 scm = 'hg'
49 scm = 'hg'
50
50
51 def __init__(self, repo_path, create=False, baseui=None, src_url=None,
51 def __init__(self, repo_path, create=False, baseui=None, src_url=None,
52 update_after_clone=False):
52 update_after_clone=False):
53 """
53 """
54 Raises RepositoryError if repository could not be find at the given
54 Raises RepositoryError if repository could not be find at the given
55 ``repo_path``.
55 ``repo_path``.
56
56
57 :param repo_path: local path of the repository
57 :param repo_path: local path of the repository
58 :param create=False: if set to True, would try to create repository if
58 :param create=False: if set to True, would try to create repository if
59 it does not exist rather than raising exception
59 it does not exist rather than raising exception
60 :param baseui=None: user data
60 :param baseui=None: user data
61 :param src_url=None: would try to clone repository from given location
61 :param src_url=None: would try to clone repository from given location
62 :param update_after_clone=False: sets update of working copy after
62 :param update_after_clone=False: sets update of working copy after
63 making a clone
63 making a clone
64 """
64 """
65
65
66 if not isinstance(repo_path, str):
66 if not isinstance(repo_path, str):
67 raise VCSError('Mercurial backend requires repository path to '
67 raise VCSError('Mercurial backend requires repository path to '
68 'be instance of <str> got %s instead' %
68 'be instance of <str> got %s instead' %
69 type(repo_path))
69 type(repo_path))
70
70
71 self.path = abspath(repo_path)
71 self.path = abspath(repo_path)
72 self.baseui = baseui or ui.ui()
72 self.baseui = baseui or ui.ui()
73 # We've set path and ui, now we can set _repo itself
73 # We've set path and ui, now we can set _repo itself
74 self._repo = self._get_repo(create, src_url, update_after_clone)
74 self._repo = self._get_repo(create, src_url, update_after_clone)
75
75
76 @property
76 @property
77 def _empty(self):
77 def _empty(self):
78 """
78 """
79 Checks if repository is empty ie. without any changesets
79 Checks if repository is empty ie. without any changesets
80 """
80 """
81 # TODO: Following raises errors when using InMemoryChangeset...
81 # TODO: Following raises errors when using InMemoryChangeset...
82 # return len(self._repo.changelog) == 0
82 # return len(self._repo.changelog) == 0
83 return len(self.revisions) == 0
83 return len(self.revisions) == 0
84
84
85 @LazyProperty
85 @LazyProperty
86 def revisions(self):
86 def revisions(self):
87 """
87 """
88 Returns list of revisions' ids, in ascending order. Being lazy
88 Returns list of revisions' ids, in ascending order. Being lazy
89 attribute allows external tools to inject shas from cache.
89 attribute allows external tools to inject shas from cache.
90 """
90 """
91 return self._get_all_revisions()
91 return self._get_all_revisions()
92
92
93 @LazyProperty
93 @LazyProperty
94 def name(self):
94 def name(self):
95 return os.path.basename(self.path)
95 return os.path.basename(self.path)
96
96
97 @LazyProperty
97 @LazyProperty
98 def branches(self):
98 def branches(self):
99 return self._get_branches()
99 return self._get_branches()
100
100
101 @LazyProperty
101 @LazyProperty
102 def closed_branches(self):
102 def closed_branches(self):
103 return self._get_branches(normal=False, closed=True)
103 return self._get_branches(normal=False, closed=True)
104
104
105 @LazyProperty
105 @LazyProperty
106 def allbranches(self):
106 def allbranches(self):
107 """
107 """
108 List all branches, including closed branches.
108 List all branches, including closed branches.
109 """
109 """
110 return self._get_branches(closed=True)
110 return self._get_branches(closed=True)
111
111
112 def _get_branches(self, normal=True, closed=False):
112 def _get_branches(self, normal=True, closed=False):
113 """
113 """
114 Gets branches for this repository
114 Gets branches for this repository
115 Returns only not closed branches by default
115 Returns only not closed branches by default
116
116
117 :param closed: return also closed branches for mercurial
117 :param closed: return also closed branches for mercurial
118 :param normal: return also normal branches
118 :param normal: return also normal branches
119 """
119 """
120
120
121 if self._empty:
121 if self._empty:
122 return {}
122 return {}
123
123
124 bt = OrderedDict()
124 bt = OrderedDict()
125 for bn, _heads, tip, isclosed in sorted(self._repo.branchmap().iterbranches()):
125 for bn, _heads, tip, isclosed in sorted(self._repo.branchmap().iterbranches()):
126 if isclosed:
126 if isclosed:
127 if closed:
127 if closed:
128 bt[safe_unicode(bn)] = hex(tip)
128 bt[safe_unicode(bn)] = hex(tip)
129 else:
129 else:
130 if normal:
130 if normal:
131 bt[safe_unicode(bn)] = hex(tip)
131 bt[safe_unicode(bn)] = hex(tip)
132
132
133 return bt
133 return bt
134
134
135 @LazyProperty
135 @LazyProperty
136 def tags(self):
136 def tags(self):
137 """
137 """
138 Gets tags for this repository
138 Gets tags for this repository
139 """
139 """
140 return self._get_tags()
140 return self._get_tags()
141
141
142 def _get_tags(self):
142 def _get_tags(self):
143 if self._empty:
143 if self._empty:
144 return {}
144 return {}
145
145
146 sortkey = lambda ctx: ctx[0] # sort by name
146 sortkey = lambda ctx: ctx[0] # sort by name
147 _tags = [(safe_unicode(n), hex(h),) for n, h in
147 _tags = [(safe_unicode(n), hex(h),) for n, h in
148 self._repo.tags().items()]
148 self._repo.tags().items()]
149
149
150 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
150 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
151
151
152 def tag(self, name, user, revision=None, message=None, date=None,
152 def tag(self, name, user, revision=None, message=None, date=None,
153 **kwargs):
153 **kwargs):
154 """
154 """
155 Creates and returns a tag for the given ``revision``.
155 Creates and returns a tag for the given ``revision``.
156
156
157 :param name: name for new tag
157 :param name: name for new tag
158 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
158 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
159 :param revision: changeset id for which new tag would be created
159 :param revision: changeset id for which new tag would be created
160 :param message: message of the tag's commit
160 :param message: message of the tag's commit
161 :param date: date of tag's commit
161 :param date: date of tag's commit
162
162
163 :raises TagAlreadyExistError: if tag with same name already exists
163 :raises TagAlreadyExistError: if tag with same name already exists
164 """
164 """
165 if name in self.tags:
165 if name in self.tags:
166 raise TagAlreadyExistError("Tag %s already exists" % name)
166 raise TagAlreadyExistError("Tag %s already exists" % name)
167 changeset = self.get_changeset(revision)
167 changeset = self.get_changeset(revision)
168 local = kwargs.setdefault('local', False)
168 local = kwargs.setdefault('local', False)
169
169
170 if message is None:
170 if message is None:
171 message = "Added tag %s for changeset %s" % (name,
171 message = "Added tag %s for changeset %s" % (name,
172 changeset.short_id)
172 changeset.short_id)
173
173
174 if date is None:
174 if date is None:
175 date = datetime.datetime.now().ctime()
175 date = datetime.datetime.now().ctime()
176
176
177 try:
177 try:
178 self._repo.tag(name, changeset._ctx.node(), message, local, user,
178 self._repo.tag(name, changeset._ctx.node(), message, local, user,
179 date)
179 date)
180 except Abort as e:
180 except Abort as e:
181 raise RepositoryError(e.message)
181 raise RepositoryError(e.message)
182
182
183 # Reinitialize tags
183 # Reinitialize tags
184 self.tags = self._get_tags()
184 self.tags = self._get_tags()
185 tag_id = self.tags[name]
185 tag_id = self.tags[name]
186
186
187 return self.get_changeset(revision=tag_id)
187 return self.get_changeset(revision=tag_id)
188
188
189 def remove_tag(self, name, user, message=None, date=None):
189 def remove_tag(self, name, user, message=None, date=None):
190 """
190 """
191 Removes tag with the given ``name``.
191 Removes tag with the given ``name``.
192
192
193 :param name: name of the tag to be removed
193 :param name: name of the tag to be removed
194 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
194 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
195 :param message: message of the tag's removal commit
195 :param message: message of the tag's removal commit
196 :param date: date of tag's removal commit
196 :param date: date of tag's removal commit
197
197
198 :raises TagDoesNotExistError: if tag with given name does not exists
198 :raises TagDoesNotExistError: if tag with given name does not exists
199 """
199 """
200 if name not in self.tags:
200 if name not in self.tags:
201 raise TagDoesNotExistError("Tag %s does not exist" % name)
201 raise TagDoesNotExistError("Tag %s does not exist" % name)
202 if message is None:
202 if message is None:
203 message = "Removed tag %s" % name
203 message = "Removed tag %s" % name
204 if date is None:
204 if date is None:
205 date = datetime.datetime.now().ctime()
205 date = datetime.datetime.now().ctime()
206 local = False
206 local = False
207
207
208 try:
208 try:
209 self._repo.tag(name, nullid, message, local, user, date)
209 self._repo.tag(name, nullid, message, local, user, date)
210 self.tags = self._get_tags()
210 self.tags = self._get_tags()
211 except Abort as e:
211 except Abort as e:
212 raise RepositoryError(e.message)
212 raise RepositoryError(e.message)
213
213
214 @LazyProperty
214 @LazyProperty
215 def bookmarks(self):
215 def bookmarks(self):
216 """
216 """
217 Gets bookmarks for this repository
217 Gets bookmarks for this repository
218 """
218 """
219 return self._get_bookmarks()
219 return self._get_bookmarks()
220
220
221 def _get_bookmarks(self):
221 def _get_bookmarks(self):
222 if self._empty:
222 if self._empty:
223 return {}
223 return {}
224
224
225 sortkey = lambda ctx: ctx[0] # sort by name
225 sortkey = lambda ctx: ctx[0] # sort by name
226 _bookmarks = [(safe_unicode(n), hex(h),) for n, h in
226 _bookmarks = [(safe_unicode(n), hex(h),) for n, h in
227 self._repo._bookmarks.items()]
227 self._repo._bookmarks.items()]
228 return OrderedDict(sorted(_bookmarks, key=sortkey, reverse=True))
228 return OrderedDict(sorted(_bookmarks, key=sortkey, reverse=True))
229
229
230 def _get_all_revisions(self):
230 def _get_all_revisions(self):
231
231
232 return [self._repo[x].hex() for x in self._repo.filtered('visible').changelog.revs()]
232 return [self._repo[x].hex() for x in self._repo.filtered('visible').changelog.revs()]
233
233
234 def get_diff(self, rev1, rev2, path='', ignore_whitespace=False,
234 def get_diff(self, rev1, rev2, path='', ignore_whitespace=False,
235 context=3):
235 context=3):
236 """
236 """
237 Returns (git like) *diff*, as plain text. Shows changes introduced by
237 Returns (git like) *diff*, as plain text. Shows changes introduced by
238 ``rev2`` since ``rev1``.
238 ``rev2`` since ``rev1``.
239
239
240 :param rev1: Entry point from which diff is shown. Can be
240 :param rev1: Entry point from which diff is shown. Can be
241 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
241 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
242 the changes since empty state of the repository until ``rev2``
242 the changes since empty state of the repository until ``rev2``
243 :param rev2: Until which revision changes should be shown.
243 :param rev2: Until which revision changes should be shown.
244 :param ignore_whitespace: If set to ``True``, would not show whitespace
244 :param ignore_whitespace: If set to ``True``, would not show whitespace
245 changes. Defaults to ``False``.
245 changes. Defaults to ``False``.
246 :param context: How many lines before/after changed lines should be
246 :param context: How many lines before/after changed lines should be
247 shown. Defaults to ``3``. If negative value is passed-in, it will be
247 shown. Defaults to ``3``. If negative value is passed-in, it will be
248 set to ``0`` instead.
248 set to ``0`` instead.
249 """
249 """
250
250
251 # Negative context values make no sense, and will result in
251 # Negative context values make no sense, and will result in
252 # errors. Ensure this does not happen.
252 # errors. Ensure this does not happen.
253 if context < 0:
253 if context < 0:
254 context = 0
254 context = 0
255
255
256 if hasattr(rev1, 'raw_id'):
256 if hasattr(rev1, 'raw_id'):
257 rev1 = getattr(rev1, 'raw_id')
257 rev1 = getattr(rev1, 'raw_id')
258
258
259 if hasattr(rev2, 'raw_id'):
259 if hasattr(rev2, 'raw_id'):
260 rev2 = getattr(rev2, 'raw_id')
260 rev2 = getattr(rev2, 'raw_id')
261
261
262 # Check if given revisions are present at repository (may raise
262 # Check if given revisions are present at repository (may raise
263 # ChangesetDoesNotExistError)
263 # ChangesetDoesNotExistError)
264 if rev1 != self.EMPTY_CHANGESET:
264 if rev1 != self.EMPTY_CHANGESET:
265 self.get_changeset(rev1)
265 self.get_changeset(rev1)
266 self.get_changeset(rev2)
266 self.get_changeset(rev2)
267 if path:
267 if path:
268 file_filter = match(self.path, '', [path])
268 file_filter = match(self.path, '', [path], exact=True)
269 else:
269 else:
270 file_filter = None
270 file_filter = None
271
271
272 return ''.join(patch.diff(self._repo, rev1, rev2, match=file_filter,
272 return ''.join(patch.diff(self._repo, rev1, rev2, match=file_filter,
273 opts=diffopts(git=True,
273 opts=diffopts(git=True,
274 showfunc=True,
274 showfunc=True,
275 ignorews=ignore_whitespace,
275 ignorews=ignore_whitespace,
276 context=context)))
276 context=context)))
277
277
278 @classmethod
278 @classmethod
279 def _check_url(cls, url, repoui=None):
279 def _check_url(cls, url, repoui=None):
280 """
280 """
281 Function will check given url and try to verify if it's a valid
281 Function will check given url and try to verify if it's a valid
282 link. Sometimes it may happened that mercurial will issue basic
282 link. Sometimes it may happened that mercurial will issue basic
283 auth request that can cause whole API to hang when used from python
283 auth request that can cause whole API to hang when used from python
284 or other external calls.
284 or other external calls.
285
285
286 On failures it'll raise urllib2.HTTPError, exception is also thrown
286 On failures it'll raise urllib2.HTTPError, exception is also thrown
287 when the return code is non 200
287 when the return code is non 200
288 """
288 """
289 # check first if it's not an local url
289 # check first if it's not an local url
290 if os.path.isdir(url) or url.startswith('file:'):
290 if os.path.isdir(url) or url.startswith('file:'):
291 return True
291 return True
292
292
293 if url.startswith('ssh:'):
293 if url.startswith('ssh:'):
294 # in case of invalid uri or authentication issues, sshpeer will
294 # in case of invalid uri or authentication issues, sshpeer will
295 # throw an exception.
295 # throw an exception.
296 sshpeer(repoui or ui.ui(), url).lookup('tip')
296 sshpeer(repoui or ui.ui(), url).lookup('tip')
297 return True
297 return True
298
298
299 url_prefix = None
299 url_prefix = None
300 if '+' in url[:url.find('://')]:
300 if '+' in url[:url.find('://')]:
301 url_prefix, url = url.split('+', 1)
301 url_prefix, url = url.split('+', 1)
302
302
303 handlers = []
303 handlers = []
304 url_obj = hg_url(url)
304 url_obj = hg_url(url)
305 test_uri, authinfo = url_obj.authinfo()
305 test_uri, authinfo = url_obj.authinfo()
306 url_obj.passwd = '*****'
306 url_obj.passwd = '*****'
307 cleaned_uri = str(url_obj)
307 cleaned_uri = str(url_obj)
308
308
309 if authinfo:
309 if authinfo:
310 #create a password manager
310 #create a password manager
311 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
311 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
312 passmgr.add_password(*authinfo)
312 passmgr.add_password(*authinfo)
313
313
314 handlers.extend((httpbasicauthhandler(passmgr),
314 handlers.extend((httpbasicauthhandler(passmgr),
315 httpdigestauthhandler(passmgr)))
315 httpdigestauthhandler(passmgr)))
316
316
317 o = urllib2.build_opener(*handlers)
317 o = urllib2.build_opener(*handlers)
318 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
318 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
319 ('Accept', 'application/mercurial-0.1')]
319 ('Accept', 'application/mercurial-0.1')]
320
320
321 q = {"cmd": 'between'}
321 q = {"cmd": 'between'}
322 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
322 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
323 qs = '?%s' % urllib.urlencode(q)
323 qs = '?%s' % urllib.urlencode(q)
324 cu = "%s%s" % (test_uri, qs)
324 cu = "%s%s" % (test_uri, qs)
325 req = urllib2.Request(cu, None, {})
325 req = urllib2.Request(cu, None, {})
326
326
327 try:
327 try:
328 resp = o.open(req)
328 resp = o.open(req)
329 if resp.code != 200:
329 if resp.code != 200:
330 raise Exception('Return Code is not 200')
330 raise Exception('Return Code is not 200')
331 except Exception as e:
331 except Exception as e:
332 # means it cannot be cloned
332 # means it cannot be cloned
333 raise urllib2.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
333 raise urllib2.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
334
334
335 if not url_prefix: # skip svn+http://... (and git+... too)
335 if not url_prefix: # skip svn+http://... (and git+... too)
336 # now check if it's a proper hg repo
336 # now check if it's a proper hg repo
337 try:
337 try:
338 httppeer(repoui or ui.ui(), url).lookup('tip')
338 httppeer(repoui or ui.ui(), url).lookup('tip')
339 except Exception as e:
339 except Exception as e:
340 raise urllib2.URLError(
340 raise urllib2.URLError(
341 "url [%s] does not look like an hg repo org_exc: %s"
341 "url [%s] does not look like an hg repo org_exc: %s"
342 % (cleaned_uri, e))
342 % (cleaned_uri, e))
343
343
344 return True
344 return True
345
345
346 def _get_repo(self, create, src_url=None, update_after_clone=False):
346 def _get_repo(self, create, src_url=None, update_after_clone=False):
347 """
347 """
348 Function will check for mercurial repository in given path and return
348 Function will check for mercurial repository in given path and return
349 a localrepo object. If there is no repository in that path it will
349 a localrepo object. If there is no repository in that path it will
350 raise an exception unless ``create`` parameter is set to True - in
350 raise an exception unless ``create`` parameter is set to True - in
351 that case repository would be created and returned.
351 that case repository would be created and returned.
352 If ``src_url`` is given, would try to clone repository from the
352 If ``src_url`` is given, would try to clone repository from the
353 location at given clone_point. Additionally it'll make update to
353 location at given clone_point. Additionally it'll make update to
354 working copy accordingly to ``update_after_clone`` flag
354 working copy accordingly to ``update_after_clone`` flag
355 """
355 """
356
356
357 try:
357 try:
358 if src_url:
358 if src_url:
359 url = str(self._get_url(src_url))
359 url = str(self._get_url(src_url))
360 opts = {}
360 opts = {}
361 if not update_after_clone:
361 if not update_after_clone:
362 opts.update({'noupdate': True})
362 opts.update({'noupdate': True})
363 MercurialRepository._check_url(url, self.baseui)
363 MercurialRepository._check_url(url, self.baseui)
364 clone(self.baseui, url, self.path, **opts)
364 clone(self.baseui, url, self.path, **opts)
365
365
366 # Don't try to create if we've already cloned repo
366 # Don't try to create if we've already cloned repo
367 create = False
367 create = False
368 return localrepository(self.baseui, self.path, create=create)
368 return localrepository(self.baseui, self.path, create=create)
369 except (Abort, RepoError) as err:
369 except (Abort, RepoError) as err:
370 if create:
370 if create:
371 msg = "Cannot create repository at %s. Original error was %s"\
371 msg = "Cannot create repository at %s. Original error was %s"\
372 % (self.path, err)
372 % (self.path, err)
373 else:
373 else:
374 msg = "Not valid repository at %s. Original error was %s"\
374 msg = "Not valid repository at %s. Original error was %s"\
375 % (self.path, err)
375 % (self.path, err)
376 raise RepositoryError(msg)
376 raise RepositoryError(msg)
377
377
378 @LazyProperty
378 @LazyProperty
379 def in_memory_changeset(self):
379 def in_memory_changeset(self):
380 return MercurialInMemoryChangeset(self)
380 return MercurialInMemoryChangeset(self)
381
381
382 @LazyProperty
382 @LazyProperty
383 def description(self):
383 def description(self):
384 undefined_description = u'unknown'
384 undefined_description = u'unknown'
385 _desc = self._repo.ui.config('web', 'description', None, untrusted=True)
385 _desc = self._repo.ui.config('web', 'description', None, untrusted=True)
386 return safe_unicode(_desc or undefined_description)
386 return safe_unicode(_desc or undefined_description)
387
387
388 @LazyProperty
388 @LazyProperty
389 def contact(self):
389 def contact(self):
390 undefined_contact = u'Unknown'
390 undefined_contact = u'Unknown'
391 return safe_unicode(get_contact(self._repo.ui.config)
391 return safe_unicode(get_contact(self._repo.ui.config)
392 or undefined_contact)
392 or undefined_contact)
393
393
394 @LazyProperty
394 @LazyProperty
395 def last_change(self):
395 def last_change(self):
396 """
396 """
397 Returns last change made on this repository as datetime object
397 Returns last change made on this repository as datetime object
398 """
398 """
399 return date_fromtimestamp(self._get_mtime(), makedate()[1])
399 return date_fromtimestamp(self._get_mtime(), makedate()[1])
400
400
401 def _get_mtime(self):
401 def _get_mtime(self):
402 try:
402 try:
403 return time.mktime(self.get_changeset().date.timetuple())
403 return time.mktime(self.get_changeset().date.timetuple())
404 except RepositoryError:
404 except RepositoryError:
405 #fallback to filesystem
405 #fallback to filesystem
406 cl_path = os.path.join(self.path, '.hg', "00changelog.i")
406 cl_path = os.path.join(self.path, '.hg', "00changelog.i")
407 st_path = os.path.join(self.path, '.hg', "store")
407 st_path = os.path.join(self.path, '.hg', "store")
408 if os.path.exists(cl_path):
408 if os.path.exists(cl_path):
409 return os.stat(cl_path).st_mtime
409 return os.stat(cl_path).st_mtime
410 else:
410 else:
411 return os.stat(st_path).st_mtime
411 return os.stat(st_path).st_mtime
412
412
413 def _get_revision(self, revision):
413 def _get_revision(self, revision):
414 """
414 """
415 Gets an ID revision given as str. This will always return a fill
415 Gets an ID revision given as str. This will always return a fill
416 40 char revision number
416 40 char revision number
417
417
418 :param revision: str or int or None
418 :param revision: str or int or None
419 """
419 """
420 if isinstance(revision, unicode):
420 if isinstance(revision, unicode):
421 revision = safe_str(revision)
421 revision = safe_str(revision)
422
422
423 if self._empty:
423 if self._empty:
424 raise EmptyRepositoryError("There are no changesets yet")
424 raise EmptyRepositoryError("There are no changesets yet")
425
425
426 if revision in [-1, 'tip', None]:
426 if revision in [-1, 'tip', None]:
427 revision = 'tip'
427 revision = 'tip'
428
428
429 try:
429 try:
430 revision = hex(self._repo.lookup(revision))
430 revision = hex(self._repo.lookup(revision))
431 except (LookupError, ):
431 except (LookupError, ):
432 msg = ("Ambiguous identifier `%s` for %s" % (revision, self))
432 msg = ("Ambiguous identifier `%s` for %s" % (revision, self))
433 raise ChangesetDoesNotExistError(msg)
433 raise ChangesetDoesNotExistError(msg)
434 except (IndexError, ValueError, RepoLookupError, TypeError):
434 except (IndexError, ValueError, RepoLookupError, TypeError):
435 msg = ("Revision %s does not exist for %s" % (revision, self))
435 msg = ("Revision %s does not exist for %s" % (revision, self))
436 raise ChangesetDoesNotExistError(msg)
436 raise ChangesetDoesNotExistError(msg)
437
437
438 return revision
438 return revision
439
439
440 def get_ref_revision(self, ref_type, ref_name):
440 def get_ref_revision(self, ref_type, ref_name):
441 """
441 """
442 Returns revision number for the given reference.
442 Returns revision number for the given reference.
443 """
443 """
444 ref_name = safe_str(ref_name)
444 ref_name = safe_str(ref_name)
445 if ref_type == 'rev' and not ref_name.strip('0'):
445 if ref_type == 'rev' and not ref_name.strip('0'):
446 return self.EMPTY_CHANGESET
446 return self.EMPTY_CHANGESET
447 # lookup up the exact node id
447 # lookup up the exact node id
448 _revset_predicates = {
448 _revset_predicates = {
449 'branch': 'branch',
449 'branch': 'branch',
450 'book': 'bookmark',
450 'book': 'bookmark',
451 'tag': 'tag',
451 'tag': 'tag',
452 'rev': 'id',
452 'rev': 'id',
453 }
453 }
454 # avoid expensive branch(x) iteration over whole repo
454 # avoid expensive branch(x) iteration over whole repo
455 rev_spec = "%%s & %s(%%s)" % _revset_predicates[ref_type]
455 rev_spec = "%%s & %s(%%s)" % _revset_predicates[ref_type]
456 try:
456 try:
457 revs = self._repo.revs(rev_spec, ref_name, ref_name)
457 revs = self._repo.revs(rev_spec, ref_name, ref_name)
458 except LookupError:
458 except LookupError:
459 msg = ("Ambiguous identifier %s:%s for %s" % (ref_type, ref_name, self.name))
459 msg = ("Ambiguous identifier %s:%s for %s" % (ref_type, ref_name, self.name))
460 raise ChangesetDoesNotExistError(msg)
460 raise ChangesetDoesNotExistError(msg)
461 except RepoLookupError:
461 except RepoLookupError:
462 msg = ("Revision %s:%s does not exist for %s" % (ref_type, ref_name, self.name))
462 msg = ("Revision %s:%s does not exist for %s" % (ref_type, ref_name, self.name))
463 raise ChangesetDoesNotExistError(msg)
463 raise ChangesetDoesNotExistError(msg)
464 if revs:
464 if revs:
465 try:
465 try:
466 revision = revs.last()
466 revision = revs.last()
467 except AttributeError:
467 except AttributeError:
468 # removed in hg 3.2
468 # removed in hg 3.2
469 revision = revs[-1]
469 revision = revs[-1]
470 else:
470 else:
471 # TODO: just report 'not found'?
471 # TODO: just report 'not found'?
472 revision = ref_name
472 revision = ref_name
473
473
474 return self._get_revision(revision)
474 return self._get_revision(revision)
475
475
476 def _get_archives(self, archive_name='tip'):
476 def _get_archives(self, archive_name='tip'):
477 allowed = self.baseui.configlist("web", "allow_archive",
477 allowed = self.baseui.configlist("web", "allow_archive",
478 untrusted=True)
478 untrusted=True)
479 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
479 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
480 if i[0] in allowed or self._repo.ui.configbool("web",
480 if i[0] in allowed or self._repo.ui.configbool("web",
481 "allow" + i[0],
481 "allow" + i[0],
482 untrusted=True):
482 untrusted=True):
483 yield {"type": i[0], "extension": i[1], "node": archive_name}
483 yield {"type": i[0], "extension": i[1], "node": archive_name}
484
484
485 def _get_url(self, url):
485 def _get_url(self, url):
486 """
486 """
487 Returns normalized url. If schema is not given, would fall
487 Returns normalized url. If schema is not given, would fall
488 to filesystem
488 to filesystem
489 (``file:///``) schema.
489 (``file:///``) schema.
490 """
490 """
491 url = str(url)
491 url = str(url)
492 if url != 'default' and not '://' in url:
492 if url != 'default' and not '://' in url:
493 url = "file:" + urllib.pathname2url(url)
493 url = "file:" + urllib.pathname2url(url)
494 return url
494 return url
495
495
496 def get_hook_location(self):
496 def get_hook_location(self):
497 """
497 """
498 returns absolute path to location where hooks are stored
498 returns absolute path to location where hooks are stored
499 """
499 """
500 return os.path.join(self.path, '.hg', '.hgrc')
500 return os.path.join(self.path, '.hg', '.hgrc')
501
501
502 def get_changeset(self, revision=None):
502 def get_changeset(self, revision=None):
503 """
503 """
504 Returns ``MercurialChangeset`` object representing repository's
504 Returns ``MercurialChangeset`` object representing repository's
505 changeset at the given ``revision``.
505 changeset at the given ``revision``.
506 """
506 """
507 revision = self._get_revision(revision)
507 revision = self._get_revision(revision)
508 changeset = MercurialChangeset(repository=self, revision=revision)
508 changeset = MercurialChangeset(repository=self, revision=revision)
509 return changeset
509 return changeset
510
510
511 def get_changesets(self, start=None, end=None, start_date=None,
511 def get_changesets(self, start=None, end=None, start_date=None,
512 end_date=None, branch_name=None, reverse=False):
512 end_date=None, branch_name=None, reverse=False):
513 """
513 """
514 Returns iterator of ``MercurialChangeset`` objects from start to end
514 Returns iterator of ``MercurialChangeset`` objects from start to end
515 (both are inclusive)
515 (both are inclusive)
516
516
517 :param start: None, str, int or mercurial lookup format
517 :param start: None, str, int or mercurial lookup format
518 :param end: None, str, int or mercurial lookup format
518 :param end: None, str, int or mercurial lookup format
519 :param start_date:
519 :param start_date:
520 :param end_date:
520 :param end_date:
521 :param branch_name:
521 :param branch_name:
522 :param reversed: return changesets in reversed order
522 :param reversed: return changesets in reversed order
523 """
523 """
524
524
525 start_raw_id = self._get_revision(start)
525 start_raw_id = self._get_revision(start)
526 start_pos = self.revisions.index(start_raw_id) if start else None
526 start_pos = self.revisions.index(start_raw_id) if start else None
527 end_raw_id = self._get_revision(end)
527 end_raw_id = self._get_revision(end)
528 end_pos = self.revisions.index(end_raw_id) if end else None
528 end_pos = self.revisions.index(end_raw_id) if end else None
529
529
530 if None not in [start, end] and start_pos > end_pos:
530 if None not in [start, end] and start_pos > end_pos:
531 raise RepositoryError("Start revision '%s' cannot be "
531 raise RepositoryError("Start revision '%s' cannot be "
532 "after end revision '%s'" % (start, end))
532 "after end revision '%s'" % (start, end))
533
533
534 if branch_name and branch_name not in self.allbranches.keys():
534 if branch_name and branch_name not in self.allbranches.keys():
535 msg = ("Branch %s not found in %s" % (branch_name, self))
535 msg = ("Branch %s not found in %s" % (branch_name, self))
536 raise BranchDoesNotExistError(msg)
536 raise BranchDoesNotExistError(msg)
537 if end_pos is not None:
537 if end_pos is not None:
538 end_pos += 1
538 end_pos += 1
539 #filter branches
539 #filter branches
540 filter_ = []
540 filter_ = []
541 if branch_name:
541 if branch_name:
542 filter_.append('branch("%s")' % (branch_name))
542 filter_.append('branch("%s")' % (branch_name))
543
543
544 if start_date and not end_date:
544 if start_date and not end_date:
545 filter_.append('date(">%s")' % start_date)
545 filter_.append('date(">%s")' % start_date)
546 if end_date and not start_date:
546 if end_date and not start_date:
547 filter_.append('date("<%s")' % end_date)
547 filter_.append('date("<%s")' % end_date)
548 if start_date and end_date:
548 if start_date and end_date:
549 filter_.append('date(">%s") and date("<%s")' % (start_date, end_date))
549 filter_.append('date(">%s") and date("<%s")' % (start_date, end_date))
550 if filter_:
550 if filter_:
551 revisions = scmutil.revrange(self._repo, filter_)
551 revisions = scmutil.revrange(self._repo, filter_)
552 else:
552 else:
553 revisions = self.revisions
553 revisions = self.revisions
554
554
555 # this is very much a hack to turn this into a list; a better solution
555 # this is very much a hack to turn this into a list; a better solution
556 # would be to get rid of this function entirely and use revsets
556 # would be to get rid of this function entirely and use revsets
557 revs = list(revisions)[start_pos:end_pos]
557 revs = list(revisions)[start_pos:end_pos]
558 if reverse:
558 if reverse:
559 revs = reversed(revs)
559 revs = reversed(revs)
560
560
561 return CollectionGenerator(self, revs)
561 return CollectionGenerator(self, revs)
562
562
563 def pull(self, url):
563 def pull(self, url):
564 """
564 """
565 Tries to pull changes from external location.
565 Tries to pull changes from external location.
566 """
566 """
567 url = self._get_url(url)
567 url = self._get_url(url)
568 other = peer(self._repo, {}, url)
568 other = peer(self._repo, {}, url)
569 try:
569 try:
570 # hg 3.2 moved push / pull to exchange module
570 # hg 3.2 moved push / pull to exchange module
571 from mercurial import exchange
571 from mercurial import exchange
572 exchange.pull(self._repo, other, heads=None, force=None)
572 exchange.pull(self._repo, other, heads=None, force=None)
573 except ImportError:
573 except ImportError:
574 self._repo.pull(other, heads=None, force=None)
574 self._repo.pull(other, heads=None, force=None)
575 except Abort as err:
575 except Abort as err:
576 # Propagate error but with vcs's type
576 # Propagate error but with vcs's type
577 raise RepositoryError(str(err))
577 raise RepositoryError(str(err))
578
578
579 @LazyProperty
579 @LazyProperty
580 def workdir(self):
580 def workdir(self):
581 """
581 """
582 Returns ``Workdir`` instance for this repository.
582 Returns ``Workdir`` instance for this repository.
583 """
583 """
584 return MercurialWorkdir(self)
584 return MercurialWorkdir(self)
585
585
586 def get_config_value(self, section, name=None, config_file=None):
586 def get_config_value(self, section, name=None, config_file=None):
587 """
587 """
588 Returns configuration value for a given [``section``] and ``name``.
588 Returns configuration value for a given [``section``] and ``name``.
589
589
590 :param section: Section we want to retrieve value from
590 :param section: Section we want to retrieve value from
591 :param name: Name of configuration we want to retrieve
591 :param name: Name of configuration we want to retrieve
592 :param config_file: A path to file which should be used to retrieve
592 :param config_file: A path to file which should be used to retrieve
593 configuration from (might also be a list of file paths)
593 configuration from (might also be a list of file paths)
594 """
594 """
595 if config_file is None:
595 if config_file is None:
596 config_file = []
596 config_file = []
597 elif isinstance(config_file, basestring):
597 elif isinstance(config_file, basestring):
598 config_file = [config_file]
598 config_file = [config_file]
599
599
600 config = self._repo.ui
600 config = self._repo.ui
601 for path in config_file:
601 for path in config_file:
602 config.readconfig(path)
602 config.readconfig(path)
603 return config.config(section, name)
603 return config.config(section, name)
604
604
605 def get_user_name(self, config_file=None):
605 def get_user_name(self, config_file=None):
606 """
606 """
607 Returns user's name from global configuration file.
607 Returns user's name from global configuration file.
608
608
609 :param config_file: A path to file which should be used to retrieve
609 :param config_file: A path to file which should be used to retrieve
610 configuration from (might also be a list of file paths)
610 configuration from (might also be a list of file paths)
611 """
611 """
612 username = self.get_config_value('ui', 'username')
612 username = self.get_config_value('ui', 'username')
613 if username:
613 if username:
614 return author_name(username)
614 return author_name(username)
615 return None
615 return None
616
616
617 def get_user_email(self, config_file=None):
617 def get_user_email(self, config_file=None):
618 """
618 """
619 Returns user's email from global configuration file.
619 Returns user's email from global configuration file.
620
620
621 :param config_file: A path to file which should be used to retrieve
621 :param config_file: A path to file which should be used to retrieve
622 configuration from (might also be a list of file paths)
622 configuration from (might also be a list of file paths)
623 """
623 """
624 username = self.get_config_value('ui', 'username')
624 username = self.get_config_value('ui', 'username')
625 if username:
625 if username:
626 return author_email(username)
626 return author_email(username)
627 return None
627 return None
@@ -1,228 +1,264 b''
1 import datetime
1 import datetime
2 from kallithea.tests.vcs.base import _BackendTestMixin
2 from kallithea.tests.vcs.base import _BackendTestMixin
3 from kallithea.tests.vcs.conf import SCM_TESTS
3 from kallithea.tests.vcs.conf import SCM_TESTS
4 from kallithea.tests.vcs.conf import TEST_USER_CONFIG_FILE
4 from kallithea.tests.vcs.conf import TEST_USER_CONFIG_FILE
5 from kallithea.lib.vcs.nodes import FileNode
5 from kallithea.lib.vcs.nodes import FileNode
6 from kallithea.lib.vcs.utils.compat import unittest
6 from kallithea.lib.vcs.utils.compat import unittest
7 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError
7 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError
8
8
9
9
10 class RepositoryBaseTest(_BackendTestMixin):
10 class RepositoryBaseTest(_BackendTestMixin):
11 recreate_repo_per_test = False
11 recreate_repo_per_test = False
12
12
13 @classmethod
13 @classmethod
14 def _get_commits(cls):
14 def _get_commits(cls):
15 return super(RepositoryBaseTest, cls)._get_commits()[:1]
15 return super(RepositoryBaseTest, cls)._get_commits()[:1]
16
16
17 def test_get_config_value(self):
17 def test_get_config_value(self):
18 self.assertEqual(self.repo.get_config_value('universal', 'foo',
18 self.assertEqual(self.repo.get_config_value('universal', 'foo',
19 TEST_USER_CONFIG_FILE), 'bar')
19 TEST_USER_CONFIG_FILE), 'bar')
20
20
21 def test_get_config_value_defaults_to_None(self):
21 def test_get_config_value_defaults_to_None(self):
22 self.assertEqual(self.repo.get_config_value('universal', 'nonexist',
22 self.assertEqual(self.repo.get_config_value('universal', 'nonexist',
23 TEST_USER_CONFIG_FILE), None)
23 TEST_USER_CONFIG_FILE), None)
24
24
25 def test_get_user_name(self):
25 def test_get_user_name(self):
26 self.assertEqual(self.repo.get_user_name(TEST_USER_CONFIG_FILE),
26 self.assertEqual(self.repo.get_user_name(TEST_USER_CONFIG_FILE),
27 'Foo Bar')
27 'Foo Bar')
28
28
29 def test_get_user_email(self):
29 def test_get_user_email(self):
30 self.assertEqual(self.repo.get_user_email(TEST_USER_CONFIG_FILE),
30 self.assertEqual(self.repo.get_user_email(TEST_USER_CONFIG_FILE),
31 'foo.bar@example.com')
31 'foo.bar@example.com')
32
32
33 def test_repo_equality(self):
33 def test_repo_equality(self):
34 self.assertTrue(self.repo == self.repo)
34 self.assertTrue(self.repo == self.repo)
35
35
36 def test_repo_equality_broken_object(self):
36 def test_repo_equality_broken_object(self):
37 import copy
37 import copy
38 _repo = copy.copy(self.repo)
38 _repo = copy.copy(self.repo)
39 delattr(_repo, 'path')
39 delattr(_repo, 'path')
40 self.assertTrue(self.repo != _repo)
40 self.assertTrue(self.repo != _repo)
41
41
42 def test_repo_equality_other_object(self):
42 def test_repo_equality_other_object(self):
43 class dummy(object):
43 class dummy(object):
44 path = self.repo.path
44 path = self.repo.path
45 self.assertTrue(self.repo != dummy())
45 self.assertTrue(self.repo != dummy())
46
46
47
47
48 class RepositoryGetDiffTest(_BackendTestMixin):
48 class RepositoryGetDiffTest(_BackendTestMixin):
49
49
50 @classmethod
50 @classmethod
51 def _get_commits(cls):
51 def _get_commits(cls):
52 commits = [
52 commits = [
53 {
53 {
54 'message': 'Initial commit',
54 'message': 'Initial commit',
55 'author': 'Joe Doe <joe.doe@example.com>',
55 'author': 'Joe Doe <joe.doe@example.com>',
56 'date': datetime.datetime(2010, 1, 1, 20),
56 'date': datetime.datetime(2010, 1, 1, 20),
57 'added': [
57 'added': [
58 FileNode('foobar', content='foobar'),
58 FileNode('foobar', content='foobar'),
59 FileNode('foobar2', content='foobar2'),
59 FileNode('foobar2', content='foobar2'),
60 ],
60 ],
61 },
61 },
62 {
62 {
63 'message': 'Changed foobar, added foobar3',
63 'message': 'Changed foobar, added foobar3',
64 'author': 'Jane Doe <jane.doe@example.com>',
64 'author': 'Jane Doe <jane.doe@example.com>',
65 'date': datetime.datetime(2010, 1, 1, 21),
65 'date': datetime.datetime(2010, 1, 1, 21),
66 'added': [
66 'added': [
67 FileNode('foobar3', content='foobar3'),
67 FileNode('foobar3', content='foobar3'),
68 ],
68 ],
69 'changed': [
69 'changed': [
70 FileNode('foobar', 'FOOBAR'),
70 FileNode('foobar', 'FOOBAR'),
71 ],
71 ],
72 },
72 },
73 {
73 {
74 'message': 'Removed foobar, changed foobar3',
74 'message': 'Removed foobar, changed foobar3',
75 'author': 'Jane Doe <jane.doe@example.com>',
75 'author': 'Jane Doe <jane.doe@example.com>',
76 'date': datetime.datetime(2010, 1, 1, 22),
76 'date': datetime.datetime(2010, 1, 1, 22),
77 'changed': [
77 'changed': [
78 FileNode('foobar3', content='FOOBAR\nFOOBAR\nFOOBAR\n'),
78 FileNode('foobar3', content='FOOBAR\nFOOBAR\nFOOBAR\n'),
79 ],
79 ],
80 'removed': [FileNode('foobar')],
80 'removed': [FileNode('foobar')],
81 },
81 },
82 {
83 'message': u'Commit that contains glob pattern in filename',
84 'author': 'Jane Doe <jane.doe@example.com>',
85 'date': datetime.datetime(2010, 1, 1, 22),
86 'added': [
87 FileNode('README{', content='Strangely-named README file'),
88 ],
89 },
82 ]
90 ]
83 return commits
91 return commits
84
92
85 def test_raise_for_wrong(self):
93 def test_raise_for_wrong(self):
86 with self.assertRaises(ChangesetDoesNotExistError):
94 with self.assertRaises(ChangesetDoesNotExistError):
87 self.repo.get_diff('a' * 40, 'b' * 40)
95 self.repo.get_diff('a' * 40, 'b' * 40)
88
96
97 def test_glob_patterns_in_filename_do_not_raise_exception(self):
98 revs = self.repo.revisions
99
100 diff = self.repo.get_diff(revs[2], revs[3], path='README{') # should not raise
101
89
102
90 class GitRepositoryGetDiffTest(RepositoryGetDiffTest, unittest.TestCase):
103 class GitRepositoryGetDiffTest(RepositoryGetDiffTest, unittest.TestCase):
91 backend_alias = 'git'
104 backend_alias = 'git'
92
105
93 def test_initial_commit_diff(self):
106 def test_initial_commit_diff(self):
94 initial_rev = self.repo.revisions[0]
107 initial_rev = self.repo.revisions[0]
95 self.assertEqual(self.repo.get_diff(self.repo.EMPTY_CHANGESET, initial_rev), '''diff --git a/foobar b/foobar
108 self.assertEqual(self.repo.get_diff(self.repo.EMPTY_CHANGESET, initial_rev), '''diff --git a/foobar b/foobar
96 new file mode 100644
109 new file mode 100644
97 index 0000000000000000000000000000000000000000..f6ea0495187600e7b2288c8ac19c5886383a4632
110 index 0000000000000000000000000000000000000000..f6ea0495187600e7b2288c8ac19c5886383a4632
98 --- /dev/null
111 --- /dev/null
99 +++ b/foobar
112 +++ b/foobar
100 @@ -0,0 +1 @@
113 @@ -0,0 +1 @@
101 +foobar
114 +foobar
102 \ No newline at end of file
115 \ No newline at end of file
103 diff --git a/foobar2 b/foobar2
116 diff --git a/foobar2 b/foobar2
104 new file mode 100644
117 new file mode 100644
105 index 0000000000000000000000000000000000000000..e8c9d6b98e3dce993a464935e1a53f50b56a3783
118 index 0000000000000000000000000000000000000000..e8c9d6b98e3dce993a464935e1a53f50b56a3783
106 --- /dev/null
119 --- /dev/null
107 +++ b/foobar2
120 +++ b/foobar2
108 @@ -0,0 +1 @@
121 @@ -0,0 +1 @@
109 +foobar2
122 +foobar2
110 \ No newline at end of file
123 \ No newline at end of file
111 ''')
124 ''')
112
125
113 def test_second_changeset_diff(self):
126 def test_second_changeset_diff(self):
114 revs = self.repo.revisions
127 revs = self.repo.revisions
115 self.assertEqual(self.repo.get_diff(revs[0], revs[1]), '''diff --git a/foobar b/foobar
128 self.assertEqual(self.repo.get_diff(revs[0], revs[1]), '''diff --git a/foobar b/foobar
116 index f6ea0495187600e7b2288c8ac19c5886383a4632..389865bb681b358c9b102d79abd8d5f941e96551 100644
129 index f6ea0495187600e7b2288c8ac19c5886383a4632..389865bb681b358c9b102d79abd8d5f941e96551 100644
117 --- a/foobar
130 --- a/foobar
118 +++ b/foobar
131 +++ b/foobar
119 @@ -1 +1 @@
132 @@ -1 +1 @@
120 -foobar
133 -foobar
121 \ No newline at end of file
134 \ No newline at end of file
122 +FOOBAR
135 +FOOBAR
123 \ No newline at end of file
136 \ No newline at end of file
124 diff --git a/foobar3 b/foobar3
137 diff --git a/foobar3 b/foobar3
125 new file mode 100644
138 new file mode 100644
126 index 0000000000000000000000000000000000000000..c11c37d41d33fb47741cff93fa5f9d798c1535b0
139 index 0000000000000000000000000000000000000000..c11c37d41d33fb47741cff93fa5f9d798c1535b0
127 --- /dev/null
140 --- /dev/null
128 +++ b/foobar3
141 +++ b/foobar3
129 @@ -0,0 +1 @@
142 @@ -0,0 +1 @@
130 +foobar3
143 +foobar3
131 \ No newline at end of file
144 \ No newline at end of file
132 ''')
145 ''')
133
146
134 def test_third_changeset_diff(self):
147 def test_third_changeset_diff(self):
135 revs = self.repo.revisions
148 revs = self.repo.revisions
136 self.assertEqual(self.repo.get_diff(revs[1], revs[2]), '''diff --git a/foobar b/foobar
149 self.assertEqual(self.repo.get_diff(revs[1], revs[2]), '''diff --git a/foobar b/foobar
137 deleted file mode 100644
150 deleted file mode 100644
138 index 389865bb681b358c9b102d79abd8d5f941e96551..0000000000000000000000000000000000000000
151 index 389865bb681b358c9b102d79abd8d5f941e96551..0000000000000000000000000000000000000000
139 --- a/foobar
152 --- a/foobar
140 +++ /dev/null
153 +++ /dev/null
141 @@ -1 +0,0 @@
154 @@ -1 +0,0 @@
142 -FOOBAR
155 -FOOBAR
143 \ No newline at end of file
156 \ No newline at end of file
144 diff --git a/foobar3 b/foobar3
157 diff --git a/foobar3 b/foobar3
145 index c11c37d41d33fb47741cff93fa5f9d798c1535b0..f9324477362684ff692aaf5b9a81e01b9e9a671c 100644
158 index c11c37d41d33fb47741cff93fa5f9d798c1535b0..f9324477362684ff692aaf5b9a81e01b9e9a671c 100644
146 --- a/foobar3
159 --- a/foobar3
147 +++ b/foobar3
160 +++ b/foobar3
148 @@ -1 +1,3 @@
161 @@ -1 +1,3 @@
149 -foobar3
162 -foobar3
150 \ No newline at end of file
163 \ No newline at end of file
151 +FOOBAR
164 +FOOBAR
152 +FOOBAR
165 +FOOBAR
153 +FOOBAR
166 +FOOBAR
154 ''')
167 ''')
155
168
169 def test_fourth_changeset_diff(self):
170 revs = self.repo.revisions
171 self.assertEqual(self.repo.get_diff(revs[2], revs[3]), '''diff --git a/README{ b/README{
172 new file mode 100644
173 index 0000000000000000000000000000000000000000..cdc0c1b5d234feedb37bbac19cd1b6442061102d
174 --- /dev/null
175 +++ b/README{
176 @@ -0,0 +1 @@
177 +Strangely-named README file
178 \ No newline at end of file
179 ''')
180
156
181
157 class HgRepositoryGetDiffTest(RepositoryGetDiffTest, unittest.TestCase):
182 class HgRepositoryGetDiffTest(RepositoryGetDiffTest, unittest.TestCase):
158 backend_alias = 'hg'
183 backend_alias = 'hg'
159
184
160 def test_initial_commit_diff(self):
185 def test_initial_commit_diff(self):
161 initial_rev = self.repo.revisions[0]
186 initial_rev = self.repo.revisions[0]
162 self.assertEqual(self.repo.get_diff(self.repo.EMPTY_CHANGESET, initial_rev), '''diff --git a/foobar b/foobar
187 self.assertEqual(self.repo.get_diff(self.repo.EMPTY_CHANGESET, initial_rev), '''diff --git a/foobar b/foobar
163 new file mode 100644
188 new file mode 100644
164 --- /dev/null
189 --- /dev/null
165 +++ b/foobar
190 +++ b/foobar
166 @@ -0,0 +1,1 @@
191 @@ -0,0 +1,1 @@
167 +foobar
192 +foobar
168 \ No newline at end of file
193 \ No newline at end of file
169 diff --git a/foobar2 b/foobar2
194 diff --git a/foobar2 b/foobar2
170 new file mode 100644
195 new file mode 100644
171 --- /dev/null
196 --- /dev/null
172 +++ b/foobar2
197 +++ b/foobar2
173 @@ -0,0 +1,1 @@
198 @@ -0,0 +1,1 @@
174 +foobar2
199 +foobar2
175 \ No newline at end of file
200 \ No newline at end of file
176 ''')
201 ''')
177
202
178 def test_second_changeset_diff(self):
203 def test_second_changeset_diff(self):
179 revs = self.repo.revisions
204 revs = self.repo.revisions
180 self.assertEqual(self.repo.get_diff(revs[0], revs[1]), '''diff --git a/foobar b/foobar
205 self.assertEqual(self.repo.get_diff(revs[0], revs[1]), '''diff --git a/foobar b/foobar
181 --- a/foobar
206 --- a/foobar
182 +++ b/foobar
207 +++ b/foobar
183 @@ -1,1 +1,1 @@
208 @@ -1,1 +1,1 @@
184 -foobar
209 -foobar
185 \ No newline at end of file
210 \ No newline at end of file
186 +FOOBAR
211 +FOOBAR
187 \ No newline at end of file
212 \ No newline at end of file
188 diff --git a/foobar3 b/foobar3
213 diff --git a/foobar3 b/foobar3
189 new file mode 100644
214 new file mode 100644
190 --- /dev/null
215 --- /dev/null
191 +++ b/foobar3
216 +++ b/foobar3
192 @@ -0,0 +1,1 @@
217 @@ -0,0 +1,1 @@
193 +foobar3
218 +foobar3
194 \ No newline at end of file
219 \ No newline at end of file
195 ''')
220 ''')
196
221
197 def test_third_changeset_diff(self):
222 def test_third_changeset_diff(self):
198 revs = self.repo.revisions
223 revs = self.repo.revisions
199 self.assertEqual(self.repo.get_diff(revs[1], revs[2]), '''diff --git a/foobar b/foobar
224 self.assertEqual(self.repo.get_diff(revs[1], revs[2]), '''diff --git a/foobar b/foobar
200 deleted file mode 100644
225 deleted file mode 100644
201 --- a/foobar
226 --- a/foobar
202 +++ /dev/null
227 +++ /dev/null
203 @@ -1,1 +0,0 @@
228 @@ -1,1 +0,0 @@
204 -FOOBAR
229 -FOOBAR
205 \ No newline at end of file
230 \ No newline at end of file
206 diff --git a/foobar3 b/foobar3
231 diff --git a/foobar3 b/foobar3
207 --- a/foobar3
232 --- a/foobar3
208 +++ b/foobar3
233 +++ b/foobar3
209 @@ -1,1 +1,3 @@
234 @@ -1,1 +1,3 @@
210 -foobar3
235 -foobar3
211 \ No newline at end of file
236 \ No newline at end of file
212 +FOOBAR
237 +FOOBAR
213 +FOOBAR
238 +FOOBAR
214 +FOOBAR
239 +FOOBAR
215 ''')
240 ''')
216
241
242 def test_fourth_changeset_diff(self):
243 revs = self.repo.revisions
244 self.assertEqual(self.repo.get_diff(revs[2], revs[3]), '''diff --git a/README{ b/README{
245 new file mode 100644
246 --- /dev/null
247 +++ b/README{
248 @@ -0,0 +1,1 @@
249 +Strangely-named README file
250 \ No newline at end of file
251 ''')
252
217
253
218 # For each backend create test case class
254 # For each backend create test case class
219 for alias in SCM_TESTS:
255 for alias in SCM_TESTS:
220 attrs = {
256 attrs = {
221 'backend_alias': alias,
257 'backend_alias': alias,
222 }
258 }
223 cls_name = alias.capitalize() + RepositoryBaseTest.__name__
259 cls_name = alias.capitalize() + RepositoryBaseTest.__name__
224 bases = (RepositoryBaseTest, unittest.TestCase)
260 bases = (RepositoryBaseTest, unittest.TestCase)
225 globals()[cls_name] = type(cls_name, bases, attrs)
261 globals()[cls_name] = type(cls_name, bases, attrs)
226
262
227 if __name__ == '__main__':
263 if __name__ == '__main__':
228 unittest.main()
264 unittest.main()
General Comments 0
You need to be logged in to leave comments. Login now