1
# Copyright (C) 2004, 2005, 2006, 2007 Canonical Ltd
1
# Copyright (C) 2004, 2005 by Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27
from bzrlib.tests import TestCase, TestCaseWithTransport
28
from bzrlib.revisionspec import (
35
def spec_in_history(spec, branch):
36
"""A simple helper to change a revision spec into a branch search"""
37
return RevisionSpec.from_string(spec).in_history(branch)
40
# Basic class, which just creates a really basic set of revisions
41
class TestRevisionSpec(TestCaseWithTransport):
44
super(TestRevisionSpec, self).setUp()
45
# this sets up a revision graph:
50
self.tree = self.make_branch_and_tree('tree')
51
self.build_tree(['tree/a'])
52
self.tree.lock_write()
53
self.addCleanup(self.tree.unlock)
55
self.tree.commit('a', rev_id='r1')
57
self.tree2 = self.tree.bzrdir.sprout('tree2').open_workingtree()
58
self.tree2.commit('alt', rev_id='alt_r2')
60
self.tree.merge_from_branch(self.tree2.branch)
61
self.tree.commit('second', rev_id='r2')
63
def get_in_history(self, revision_spec):
64
return spec_in_history(revision_spec, self.tree.branch)
66
def assertInHistoryIs(self, exp_revno, exp_revision_id, revision_spec):
67
rev_info = self.get_in_history(revision_spec)
68
self.assertEqual(exp_revno, rev_info.revno,
69
'Revision spec: %r returned wrong revno: %r != %r'
70
% (revision_spec, exp_revno, rev_info.revno))
71
self.assertEqual(exp_revision_id, rev_info.rev_id,
72
'Revision spec: %r returned wrong revision id:'
74
% (revision_spec, exp_revision_id, rev_info.rev_id))
76
def assertInvalid(self, revision_spec, extra=''):
78
self.get_in_history(revision_spec)
79
except errors.InvalidRevisionSpec, e:
80
self.assertEqual(revision_spec, e.spec)
81
self.assertEqual(extra, e.extra)
83
self.fail('Expected InvalidRevisionSpec to be raised for %s'
86
def assertAsRevisionId(self, revision_id, revision_spec):
87
"""Calling as_revision_id() should return the specified id."""
88
spec = RevisionSpec.from_string(revision_spec)
89
self.assertEqual(revision_id,
90
spec.as_revision_id(self.tree.branch))
93
class TestOddRevisionSpec(TestRevisionSpec):
94
"""Test things that aren't normally thought of as revision specs"""
97
self.assertInHistoryIs(None, None, None)
99
def test_object(self):
100
self.assertRaises(TypeError, RevisionSpec.from_string, object())
102
def test_unregistered_spec(self):
103
self.assertRaises(errors.NoSuchRevisionSpec,
104
RevisionSpec.from_string, 'foo')
105
self.assertRaises(errors.NoSuchRevisionSpec,
106
RevisionSpec.from_string, '123a')
110
class TestRevnoFromString(TestCase):
112
def test_from_string_dotted_decimal(self):
113
self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '-1.1')
114
self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '.1')
115
self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '1..1')
116
self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '1.2..1')
117
self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '1.')
118
self.assertIsInstance(RevisionSpec.from_string('1.1'), RevisionSpec_revno)
119
self.assertIsInstance(RevisionSpec.from_string('1.1.3'), RevisionSpec_revno)
122
class TestRevisionSpec_revno(TestRevisionSpec):
124
def test_positive_int(self):
125
self.assertInHistoryIs(0, 'null:', '0')
126
self.assertInHistoryIs(1, 'r1', '1')
127
self.assertInHistoryIs(2, 'r2', '2')
128
self.assertInvalid('3')
130
def test_dotted_decimal(self):
131
self.assertInHistoryIs(None, 'alt_r2', '1.1.1')
133
def test_negative_int(self):
134
self.assertInHistoryIs(2, 'r2', '-1')
135
self.assertInHistoryIs(1, 'r1', '-2')
137
self.assertInHistoryIs(1, 'r1', '-3')
138
self.assertInHistoryIs(1, 'r1', '-4')
139
self.assertInHistoryIs(1, 'r1', '-100')
141
def test_positive(self):
142
self.assertInHistoryIs(0, 'null:', 'revno:0')
143
self.assertInHistoryIs(1, 'r1', 'revno:1')
144
self.assertInHistoryIs(2, 'r2', 'revno:2')
146
self.assertInvalid('revno:3')
148
def test_negative(self):
149
self.assertInHistoryIs(2, 'r2', 'revno:-1')
150
self.assertInHistoryIs(1, 'r1', 'revno:-2')
152
self.assertInHistoryIs(1, 'r1', 'revno:-3')
153
self.assertInHistoryIs(1, 'r1', 'revno:-4')
155
def test_invalid_number(self):
156
# Get the right exception text
159
except ValueError, e:
161
self.assertInvalid('revno:X', extra='\n' + str(e))
163
def test_missing_number_and_branch(self):
164
self.assertInvalid('revno::',
165
extra='\ncannot have an empty revno and no branch')
167
def test_invalid_number_with_branch(self):
170
except ValueError, e:
172
self.assertInvalid('revno:X:tree2', extra='\n' + str(e))
174
def test_non_exact_branch(self):
175
# It seems better to require an exact path to the branch
176
# Branch.open() rather than using Branch.open_containing()
177
spec = RevisionSpec.from_string('revno:2:tree2/a')
178
self.assertRaises(errors.NotBranchError,
179
spec.in_history, self.tree.branch)
181
def test_with_branch(self):
182
# Passing a URL overrides the supplied branch path
183
revinfo = self.get_in_history('revno:2:tree2')
184
self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
185
self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
186
self.assertEqual(2, revinfo.revno)
187
self.assertEqual('alt_r2', revinfo.rev_id)
189
def test_int_with_branch(self):
190
revinfo = self.get_in_history('2:tree2')
191
self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
192
self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
193
self.assertEqual(2, revinfo.revno)
194
self.assertEqual('alt_r2', revinfo.rev_id)
196
def test_with_url(self):
197
url = self.get_url() + '/tree2'
198
revinfo = self.get_in_history('revno:2:%s' % (url,))
199
self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
200
self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
201
self.assertEqual(2, revinfo.revno)
202
self.assertEqual('alt_r2', revinfo.rev_id)
204
def test_negative_with_url(self):
205
url = self.get_url() + '/tree2'
206
revinfo = self.get_in_history('revno:-1:%s' % (url,))
207
self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
208
self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
209
self.assertEqual(2, revinfo.revno)
210
self.assertEqual('alt_r2', revinfo.rev_id)
212
def test_different_history_lengths(self):
213
# Make sure we use the revisions and offsets in the supplied branch
214
# not the ones in the original branch.
215
self.tree2.commit('three', rev_id='r3')
216
self.assertInHistoryIs(3, 'r3', 'revno:3:tree2')
217
self.assertInHistoryIs(3, 'r3', 'revno:-1:tree2')
219
def test_invalid_branch(self):
220
self.assertRaises(errors.NotBranchError,
221
self.get_in_history, 'revno:-1:tree3')
223
def test_invalid_revno_in_branch(self):
224
self.tree.commit('three', rev_id='r3')
225
self.assertInvalid('revno:3:tree2')
227
def test_revno_n_path(self):
228
"""Old revno:N:path tests"""
229
wta = self.make_branch_and_tree('a')
232
wta.commit('Commit one', rev_id='a@r-0-1')
233
wta.commit('Commit two', rev_id='a@r-0-2')
234
wta.commit('Commit three', rev_id='a@r-0-3')
236
wtb = self.make_branch_and_tree('b')
239
wtb.commit('Commit one', rev_id='b@r-0-1')
240
wtb.commit('Commit two', rev_id='b@r-0-2')
241
wtb.commit('Commit three', rev_id='b@r-0-3')
244
self.assertEqual((1, 'a@r-0-1'),
245
spec_in_history('revno:1:a/', ba))
246
# The argument of in_history should be ignored since it is
247
# redundant with the path in the spec.
248
self.assertEqual((1, 'a@r-0-1'),
249
spec_in_history('revno:1:a/', None))
250
self.assertEqual((1, 'a@r-0-1'),
251
spec_in_history('revno:1:a/', bb))
252
self.assertEqual((2, 'b@r-0-2'),
253
spec_in_history('revno:2:b/', None))
255
def test_as_revision_id(self):
256
self.assertAsRevisionId('null:', '0')
257
self.assertAsRevisionId('r1', '1')
258
self.assertAsRevisionId('r2', '2')
259
self.assertAsRevisionId('r1', '-2')
260
self.assertAsRevisionId('r2', '-1')
261
self.assertAsRevisionId('alt_r2', '1.1.1')
264
class TestRevisionSpec_revid(TestRevisionSpec):
266
def test_in_history(self):
267
# We should be able to access revisions that are directly
269
self.assertInHistoryIs(1, 'r1', 'revid:r1')
270
self.assertInHistoryIs(2, 'r2', 'revid:r2')
272
def test_missing(self):
273
self.assertInvalid('revid:r3')
275
def test_merged(self):
276
"""We can reach revisions in the ancestry"""
277
self.assertInHistoryIs(None, 'alt_r2', 'revid:alt_r2')
279
def test_not_here(self):
280
self.tree2.commit('alt third', rev_id='alt_r3')
281
# It exists in tree2, but not in tree
282
self.assertInvalid('revid:alt_r3')
284
def test_in_repository(self):
285
"""We can get any revision id in the repository"""
286
# XXX: This may change in the future, but for now, it is true
287
self.tree2.commit('alt third', rev_id='alt_r3')
288
self.tree.branch.repository.fetch(self.tree2.branch.repository,
289
revision_id='alt_r3')
290
self.assertInHistoryIs(None, 'alt_r3', 'revid:alt_r3')
292
def test_unicode(self):
293
"""We correctly convert a unicode ui string to an encoded revid."""
294
revision_id = u'\N{SNOWMAN}'.encode('utf-8')
295
self.tree.commit('unicode', rev_id=revision_id)
296
self.assertInHistoryIs(3, revision_id, u'revid:\N{SNOWMAN}')
297
self.assertInHistoryIs(3, revision_id, 'revid:' + revision_id)
299
def test_as_revision_id(self):
300
self.assertAsRevisionId('r1', 'revid:r1')
301
self.assertAsRevisionId('r2', 'revid:r2')
302
self.assertAsRevisionId('alt_r2', 'revid:alt_r2')
305
class TestRevisionSpec_last(TestRevisionSpec):
307
def test_positive(self):
308
self.assertInHistoryIs(2, 'r2', 'last:1')
309
self.assertInHistoryIs(1, 'r1', 'last:2')
310
self.assertInHistoryIs(0, 'null:', 'last:3')
312
def test_empty(self):
313
self.assertInHistoryIs(2, 'r2', 'last:')
315
def test_negative(self):
316
self.assertInvalid('last:-1',
317
extra='\nyou must supply a positive value')
319
def test_missing(self):
320
self.assertInvalid('last:4')
322
def test_no_history(self):
323
tree = self.make_branch_and_tree('tree3')
325
self.assertRaises(errors.NoCommits,
326
spec_in_history, 'last:', tree.branch)
328
def test_not_a_number(self):
331
except ValueError, e:
333
self.assertInvalid('last:Y', extra='\n' + str(e))
335
def test_as_revision_id(self):
336
self.assertAsRevisionId('r2', 'last:1')
337
self.assertAsRevisionId('r1', 'last:2')
340
class TestRevisionSpec_before(TestRevisionSpec):
343
self.assertInHistoryIs(1, 'r1', 'before:2')
344
self.assertInHistoryIs(1, 'r1', 'before:-1')
346
def test_before_one(self):
347
self.assertInHistoryIs(0, 'null:', 'before:1')
349
def test_before_none(self):
350
self.assertInvalid('before:0',
351
extra='\ncannot go before the null: revision')
353
def test_revid(self):
354
self.assertInHistoryIs(1, 'r1', 'before:revid:r2')
357
self.assertInHistoryIs(1, 'r1', 'before:last:1')
359
def test_alt_revid(self):
360
# This will grab the left-most ancestor for alternate histories
361
self.assertInHistoryIs(1, 'r1', 'before:revid:alt_r2')
363
def test_alt_no_parents(self):
364
new_tree = self.make_branch_and_tree('new_tree')
365
new_tree.commit('first', rev_id='new_r1')
366
self.tree.branch.repository.fetch(new_tree.branch.repository,
367
revision_id='new_r1')
368
self.assertInHistoryIs(0, 'null:', 'before:revid:new_r1')
370
def test_as_revision_id(self):
371
self.assertAsRevisionId('r1', 'before:revid:r2')
372
self.assertAsRevisionId('r1', 'before:2')
373
self.assertAsRevisionId('r1', 'before:1.1.1')
374
self.assertAsRevisionId('r1', 'before:revid:alt_r2')
377
class TestRevisionSpec_tag(TestRevisionSpec):
379
def make_branch_and_tree(self, relpath):
380
# override format as the default one may not support tags
381
return TestRevisionSpec.make_branch_and_tree(
382
self, relpath, format='dirstate-tags')
384
def test_from_string_tag(self):
385
spec = RevisionSpec.from_string('tag:bzr-0.14')
386
self.assertIsInstance(spec, RevisionSpec_tag)
387
self.assertEqual(spec.spec, 'bzr-0.14')
389
def test_lookup_tag(self):
390
self.tree.branch.tags.set_tag('bzr-0.14', 'r1')
391
self.assertInHistoryIs(1, 'r1', 'tag:bzr-0.14')
392
self.tree.branch.tags.set_tag('null_rev', 'null:')
393
self.assertInHistoryIs(0, 'null:', 'tag:null_rev')
395
def test_failed_lookup(self):
396
# tags that don't exist give a specific message: arguably we should
397
# just give InvalidRevisionSpec but I think this is more helpful
398
self.assertRaises(errors.NoSuchTag,
400
'tag:some-random-tag')
402
def test_as_revision_id(self):
403
self.tree.branch.tags.set_tag('my-tag', 'r2')
404
self.tree.branch.tags.set_tag('null_rev', 'null:')
405
self.assertAsRevisionId('r2', 'tag:my-tag')
406
self.assertAsRevisionId('null:', 'tag:null_rev')
407
self.assertAsRevisionId('r1', 'before:tag:my-tag')
410
class TestRevisionSpec_date(TestRevisionSpec):
413
super(TestRevisionSpec, self).setUp()
415
new_tree = self.make_branch_and_tree('new_tree')
416
new_tree.commit('Commit one', rev_id='new_r1',
417
timestamp=time.time() - 60*60*24)
418
new_tree.commit('Commit two', rev_id='new_r2')
419
new_tree.commit('Commit three', rev_id='new_r3')
423
def test_tomorrow(self):
424
self.assertInvalid('date:tomorrow')
426
def test_today(self):
427
self.assertInHistoryIs(2, 'new_r2', 'date:today')
428
self.assertInHistoryIs(1, 'new_r1', 'before:date:today')
430
def test_yesterday(self):
431
self.assertInHistoryIs(1, 'new_r1', 'date:yesterday')
433
def test_invalid(self):
434
self.assertInvalid('date:foobar', extra='\ninvalid date')
435
# You must have '-' between year/month/day
436
self.assertInvalid('date:20040404', extra='\ninvalid date')
437
# Need 2 digits for each date piece
438
self.assertInvalid('date:2004-4-4', extra='\ninvalid date')
441
now = datetime.datetime.now()
442
self.assertInHistoryIs(2, 'new_r2',
443
'date:%04d-%02d-%02d' % (now.year, now.month, now.day))
445
def test_as_revision_id(self):
446
self.assertAsRevisionId('new_r2', 'date:today')
449
class TestRevisionSpec_ancestor(TestRevisionSpec):
451
def test_non_exact_branch(self):
452
# It seems better to require an exact path to the branch
453
# Branch.open() rather than using Branch.open_containing()
454
self.assertRaises(errors.NotBranchError,
455
self.get_in_history, 'ancestor:tree2/a')
457
def test_simple(self):
458
# Common ancestor of trees is 'alt_r2'
459
self.assertInHistoryIs(None, 'alt_r2', 'ancestor:tree2')
461
# Going the other way, we get a valid revno
463
self.tree = self.tree2
465
self.assertInHistoryIs(2, 'alt_r2', 'ancestor:tree')
468
self.assertInHistoryIs(2, 'r2', 'ancestor:tree')
470
def test_unrelated(self):
471
new_tree = self.make_branch_and_tree('new_tree')
473
new_tree.commit('Commit one', rev_id='new_r1')
474
new_tree.commit('Commit two', rev_id='new_r2')
475
new_tree.commit('Commit three', rev_id='new_r3')
477
# With no common ancestor, we should raise another user error
478
self.assertRaises(errors.NoCommonAncestor,
479
self.get_in_history, 'ancestor:new_tree')
481
def test_no_commits(self):
482
new_tree = self.make_branch_and_tree('new_tree')
483
self.assertRaises(errors.NoCommits,
484
spec_in_history, 'ancestor:new_tree',
487
self.assertRaises(errors.NoCommits,
488
spec_in_history, 'ancestor:tree',
491
def test_as_revision_id(self):
492
self.assertAsRevisionId('alt_r2', 'ancestor:tree2')
495
class TestRevisionSpec_branch(TestRevisionSpec):
497
def test_non_exact_branch(self):
498
# It seems better to require an exact path to the branch
499
# Branch.open() rather than using Branch.open_containing()
500
self.assertRaises(errors.NotBranchError,
501
self.get_in_history, 'branch:tree2/a')
503
def test_simple(self):
504
self.assertInHistoryIs(None, 'alt_r2', 'branch:tree2')
507
self.assertInHistoryIs(2, 'r2', 'branch:tree')
509
def test_unrelated(self):
510
new_tree = self.make_branch_and_tree('new_tree')
512
new_tree.commit('Commit one', rev_id='new_r1')
513
new_tree.commit('Commit two', rev_id='new_r2')
514
new_tree.commit('Commit three', rev_id='new_r3')
516
self.assertInHistoryIs(None, 'new_r3', 'branch:new_tree')
518
# XXX: Right now, we use fetch() to make sure the remote revisions
519
# have been pulled into the local branch. We may change that
520
# behavior in the future.
521
self.failUnless(self.tree.branch.repository.has_revision('new_r3'))
523
def test_no_commits(self):
524
new_tree = self.make_branch_and_tree('new_tree')
525
self.assertRaises(errors.NoCommits,
526
self.get_in_history, 'branch:new_tree')
528
def test_as_revision_id(self):
529
self.assertAsRevisionId('alt_r2', 'branch:tree2')
532
class TestRevisionSpec_submit(TestRevisionSpec):
534
def test_submit_branch(self):
535
# Common ancestor of trees is 'alt_r2'
536
self.assertRaises(errors.NoSubmitBranch, self.get_in_history,
538
self.tree.branch.set_parent('../tree2')
539
self.assertInHistoryIs(None, 'alt_r2', 'submit:')
540
self.tree.branch.set_parent('bogus')
541
self.assertRaises(errors.NotBranchError, self.get_in_history,
543
# submit branch overrides parent branch
544
self.tree.branch.set_submit_branch('tree2')
545
self.assertInHistoryIs(None, 'alt_r2', 'submit:')
547
def test_as_revision_id(self):
548
self.tree.branch.set_submit_branch('tree2')
549
self.assertAsRevisionId('alt_r2', 'branch:tree2')
20
from bzrlib.builtins import merge
21
from bzrlib.branch import Branch
22
from bzrlib.tests import TestCaseWithTransport
23
from bzrlib.errors import NoCommonAncestor, NoCommits
24
from bzrlib.errors import NoSuchRevision
25
from bzrlib.revisionspec import RevisionSpec
28
class TestRevisionNamespaces(TestCaseWithTransport):
30
def test_revision_namespaces(self):
31
"""Test revision specifiers.
33
These identify revisions by date, etc."""
34
wt = self.make_branch_and_tree('.')
37
wt.commit('Commit one', rev_id='a@r-0-1', timestamp=time.time() - 60*60*24)
38
wt.commit('Commit two', rev_id='a@r-0-2')
39
wt.commit('Commit three', rev_id='a@r-0-3')
41
self.assertEquals(RevisionSpec(None).in_history(b), (0, None))
42
self.assertEquals(RevisionSpec(1).in_history(b), (1, 'a@r-0-1'))
43
self.assertEquals(RevisionSpec('revno:1').in_history(b),
45
self.assertEquals(RevisionSpec('revid:a@r-0-1').in_history(b),
47
self.assertRaises(NoSuchRevision,
48
RevisionSpec('revid:a@r-0-0').in_history, b)
49
self.assertRaises(TypeError, RevisionSpec, object)
51
self.assertEquals(RevisionSpec('date:today').in_history(b),
53
self.assertEquals(RevisionSpec('date:yesterday').in_history(b),
55
self.assertEquals(RevisionSpec('before:date:today').in_history(b),
58
self.assertEquals(RevisionSpec('last:1').in_history(b),
60
self.assertEquals(RevisionSpec('-1').in_history(b), (3, 'a@r-0-3'))
61
# self.assertEquals(b.get_revision_info('last:1'), (3, 'a@r-0-3'))
62
# self.assertEquals(b.get_revision_info('-1'), (3, 'a@r-0-3'))
64
self.assertEquals(RevisionSpec('ancestor:.').in_history(b).rev_id,
68
wt2 = self.make_branch_and_tree('newbranch')
70
self.assertRaises(NoCommits, RevisionSpec('ancestor:.').in_history, b2)
72
d3 = b.bzrdir.sprout('copy')
74
wt3 = d3.open_workingtree()
75
wt3.commit('Commit four', rev_id='b@r-0-4')
76
self.assertEquals(RevisionSpec('ancestor:.').in_history(b3).rev_id,
78
merge(['copy', -1], [None, None])
79
wt.commit('Commit five', rev_id='a@r-0-4')
80
self.assertEquals(RevisionSpec('ancestor:copy').in_history(b).rev_id,
82
self.assertEquals(RevisionSpec('ancestor:.').in_history(b3).rev_id,
85
# This should be in the revision store, but not in revision-history
86
self.assertEquals((None, 'b@r-0-4'),
87
RevisionSpec('revid:b@r-0-4').in_history(b))
89
def test_branch_namespace(self):
90
"""Ensure that the branch namespace pulls in the requisite content."""
91
self.build_tree(['branch1/', 'branch1/file', 'branch2/'])
92
wt = self.make_branch_and_tree('branch1')
96
d2 = branch.bzrdir.sprout('branch2')
97
print >> open('branch2/file', 'w'), 'new content'
98
branch2 = d2.open_branch()
99
d2.open_workingtree().commit('update file', rev_id='A')
100
spec = RevisionSpec('branch:./branch2/.bzr/../')
101
rev_info = spec.in_history(branch)
102
self.assertEqual(rev_info, (None, 'A'))