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