1
# Copyright (C) 2004, 2005 by Canonical Ltd
1
# Copyright (C) 2004, 2005, 2006, 2007 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
20
from bzrlib.branch import Branch
21
from bzrlib.tests import TestCaseInTempDir
22
from bzrlib.errors import NoCommonAncestor, NoCommits
23
from bzrlib.errors import NoSuchRevision
24
from bzrlib.merge import merge
25
from bzrlib.revisionspec import RevisionSpec
27
class TestRevisionNamespaces(TestCaseInTempDir):
29
def test_revision_namespaces(self):
30
"""Test revision specifiers.
32
These identify revisions by date, etc."""
34
b = Branch.initialize(u'.')
36
b.working_tree().commit('Commit one', rev_id='a@r-0-1', timestamp=time.time() - 60*60*24)
37
b.working_tree().commit('Commit two', rev_id='a@r-0-2')
38
b.working_tree().commit('Commit three', rev_id='a@r-0-3')
40
self.assertEquals(RevisionSpec(None).in_history(b), (0, None))
41
self.assertEquals(RevisionSpec(1).in_history(b), (1, 'a@r-0-1'))
42
self.assertEquals(RevisionSpec('revno:1').in_history(b),
44
self.assertEquals(RevisionSpec('revid:a@r-0-1').in_history(b),
46
self.assertRaises(NoSuchRevision,
47
RevisionSpec('revid:a@r-0-0').in_history, b)
48
self.assertRaises(TypeError, RevisionSpec, object)
50
self.assertEquals(RevisionSpec('date:today').in_history(b),
52
self.assertEquals(RevisionSpec('date:yesterday').in_history(b),
54
self.assertEquals(RevisionSpec('before:date:today').in_history(b),
57
self.assertEquals(RevisionSpec('last:1').in_history(b),
59
self.assertEquals(RevisionSpec('-1').in_history(b), (3, 'a@r-0-3'))
60
# self.assertEquals(b.get_revision_info('last:1'), (3, 'a@r-0-3'))
61
# self.assertEquals(b.get_revision_info('-1'), (3, 'a@r-0-3'))
63
self.assertEquals(RevisionSpec('ancestor:.').in_history(b).rev_id,
67
b2 = Branch.initialize('newbranch')
68
self.assertRaises(NoCommits, RevisionSpec('ancestor:.').in_history, b2)
72
b3.working_tree().commit('Commit four', rev_id='b@r-0-4')
73
self.assertEquals(RevisionSpec('ancestor:.').in_history(b3).rev_id,
75
merge(['copy', -1], [None, None])
76
b.working_tree().commit('Commit five', rev_id='a@r-0-4')
77
self.assertEquals(RevisionSpec('ancestor:copy').in_history(b).rev_id,
79
self.assertEquals(RevisionSpec('ancestor:.').in_history(b3).rev_id,
82
# This should be in the revision store, but not in revision-history
83
self.assertEquals((None, 'b@r-0-4'),
84
RevisionSpec('revid:b@r-0-4').in_history(b))
86
def test_branch_namespace(self):
87
"""Ensure that the branch namespace pulls in the requisite content."""
88
self.build_tree(['branch1/', 'branch1/file', 'branch2/'])
89
branch = Branch.initialize('branch1')
90
branch.working_tree().add(['file'])
91
branch.working_tree().commit('add file')
92
branch.clone('branch2')
93
print >> open('branch2/file', 'w'), 'new content'
94
branch2 = Branch.open('branch2')
95
branch2.working_tree().commit('update file', rev_id='A')
96
spec = RevisionSpec('branch:./branch2/.bzr/../')
97
rev_info = spec.in_history(branch)
98
self.assertEqual(rev_info, (None, 'A'))
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'])
53
self.tree.commit('a', rev_id='r1')
55
self.tree2 = self.tree.bzrdir.sprout('tree2').open_workingtree()
56
self.tree2.commit('alt', rev_id='alt_r2')
58
self.tree.branch.repository.fetch(self.tree2.branch.repository,
60
self.tree.set_pending_merges(['alt_r2'])
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'
87
class TestOddRevisionSpec(TestRevisionSpec):
88
"""Test things that aren't normally thought of as revision specs"""
91
self.assertInHistoryIs(0, None, None)
93
def test_object(self):
94
self.assertRaises(TypeError, RevisionSpec.from_string, object())
96
def test_unregistered_spec(self):
97
self.assertRaises(errors.NoSuchRevisionSpec,
98
RevisionSpec.from_string, 'foo')
99
self.assertRaises(errors.NoSuchRevisionSpec,
100
RevisionSpec.from_string, '123a')
104
class TestRevnoFromString(TestCase):
106
def test_from_string_dotted_decimal(self):
107
self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '-1.1')
108
self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '.1')
109
self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '1..1')
110
self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '1.2..1')
111
self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '1.')
112
self.assertIsInstance(RevisionSpec.from_string('1.1'), RevisionSpec_revno)
113
self.assertIsInstance(RevisionSpec.from_string('1.1.3'), RevisionSpec_revno)
116
class TestRevisionSpec_revno(TestRevisionSpec):
118
def test_positive_int(self):
119
self.assertInHistoryIs(0, None, '0')
120
self.assertInHistoryIs(1, 'r1', '1')
121
self.assertInHistoryIs(2, 'r2', '2')
122
self.assertInvalid('3')
124
def test_dotted_decimal(self):
125
self.assertInHistoryIs(None, 'alt_r2', '1.1.1')
127
def test_negative_int(self):
128
self.assertInHistoryIs(2, 'r2', '-1')
129
self.assertInHistoryIs(1, 'r1', '-2')
131
self.assertInHistoryIs(1, 'r1', '-3')
132
self.assertInHistoryIs(1, 'r1', '-4')
133
self.assertInHistoryIs(1, 'r1', '-100')
135
def test_positive(self):
136
self.assertInHistoryIs(0, None, 'revno:0')
137
self.assertInHistoryIs(1, 'r1', 'revno:1')
138
self.assertInHistoryIs(2, 'r2', 'revno:2')
140
self.assertInvalid('revno:3')
142
def test_negative(self):
143
self.assertInHistoryIs(2, 'r2', 'revno:-1')
144
self.assertInHistoryIs(1, 'r1', 'revno:-2')
146
self.assertInHistoryIs(1, 'r1', 'revno:-3')
147
self.assertInHistoryIs(1, 'r1', 'revno:-4')
149
def test_invalid_number(self):
150
# Get the right exception text
153
except ValueError, e:
155
self.assertInvalid('revno:X', extra='\n' + str(e))
157
def test_missing_number_and_branch(self):
158
self.assertInvalid('revno::',
159
extra='\ncannot have an empty revno and no branch')
161
def test_invalid_number_with_branch(self):
164
except ValueError, e:
166
self.assertInvalid('revno:X:tree2', extra='\n' + str(e))
168
def test_non_exact_branch(self):
169
# It seems better to require an exact path to the branch
170
# Branch.open() rather than using Branch.open_containing()
171
spec = RevisionSpec.from_string('revno:2:tree2/a')
172
self.assertRaises(errors.NotBranchError,
173
spec.in_history, self.tree.branch)
175
def test_with_branch(self):
176
# Passing a URL overrides the supplied branch path
177
revinfo = self.get_in_history('revno:2:tree2')
178
self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
179
self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
180
self.assertEqual(2, revinfo.revno)
181
self.assertEqual('alt_r2', revinfo.rev_id)
183
def test_int_with_branch(self):
184
revinfo = self.get_in_history('2:tree2')
185
self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
186
self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
187
self.assertEqual(2, revinfo.revno)
188
self.assertEqual('alt_r2', revinfo.rev_id)
190
def test_with_url(self):
191
url = self.get_url() + '/tree2'
192
revinfo = self.get_in_history('revno:2:%s' % (url,))
193
self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
194
self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
195
self.assertEqual(2, revinfo.revno)
196
self.assertEqual('alt_r2', revinfo.rev_id)
198
def test_negative_with_url(self):
199
url = self.get_url() + '/tree2'
200
revinfo = self.get_in_history('revno:-1:%s' % (url,))
201
self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
202
self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
203
self.assertEqual(2, revinfo.revno)
204
self.assertEqual('alt_r2', revinfo.rev_id)
206
def test_different_history_lengths(self):
207
# Make sure we use the revisions and offsets in the supplied branch
208
# not the ones in the original branch.
209
self.tree2.commit('three', rev_id='r3')
210
self.assertInHistoryIs(3, 'r3', 'revno:3:tree2')
211
self.assertInHistoryIs(3, 'r3', 'revno:-1:tree2')
213
def test_invalid_branch(self):
214
self.assertRaises(errors.NotBranchError,
215
self.get_in_history, 'revno:-1:tree3')
217
def test_invalid_revno_in_branch(self):
218
self.tree.commit('three', rev_id='r3')
219
self.assertInvalid('revno:3:tree2')
221
def test_revno_n_path(self):
222
"""Old revno:N:path tests"""
223
wta = self.make_branch_and_tree('a')
226
wta.commit('Commit one', rev_id='a@r-0-1')
227
wta.commit('Commit two', rev_id='a@r-0-2')
228
wta.commit('Commit three', rev_id='a@r-0-3')
230
wtb = self.make_branch_and_tree('b')
233
wtb.commit('Commit one', rev_id='b@r-0-1')
234
wtb.commit('Commit two', rev_id='b@r-0-2')
235
wtb.commit('Commit three', rev_id='b@r-0-3')
238
self.assertEqual((1, 'a@r-0-1'),
239
spec_in_history('revno:1:a/', ba))
240
# The argument of in_history should be ignored since it is
241
# redundant with the path in the spec.
242
self.assertEqual((1, 'a@r-0-1'),
243
spec_in_history('revno:1:a/', None))
244
self.assertEqual((1, 'a@r-0-1'),
245
spec_in_history('revno:1:a/', bb))
246
self.assertEqual((2, 'b@r-0-2'),
247
spec_in_history('revno:2:b/', None))
251
class TestRevisionSpec_revid(TestRevisionSpec):
253
def test_in_history(self):
254
# We should be able to access revisions that are directly
256
self.assertInHistoryIs(1, 'r1', 'revid:r1')
257
self.assertInHistoryIs(2, 'r2', 'revid:r2')
259
def test_missing(self):
260
self.assertInvalid('revid:r3')
262
def test_merged(self):
263
"""We can reach revisions in the ancestry"""
264
self.assertInHistoryIs(None, 'alt_r2', 'revid:alt_r2')
266
def test_not_here(self):
267
self.tree2.commit('alt third', rev_id='alt_r3')
268
# It exists in tree2, but not in tree
269
self.assertInvalid('revid:alt_r3')
271
def test_in_repository(self):
272
"""We can get any revision id in the repository"""
273
# XXX: This may change in the future, but for now, it is true
274
self.tree2.commit('alt third', rev_id='alt_r3')
275
self.tree.branch.repository.fetch(self.tree2.branch.repository,
276
revision_id='alt_r3')
277
self.assertInHistoryIs(None, 'alt_r3', 'revid:alt_r3')
279
def test_unicode(self):
280
"""We correctly convert a unicode ui string to an encoded revid."""
281
revision_id = u'\N{SNOWMAN}'.encode('utf-8')
282
self.tree.commit('unicode', rev_id=revision_id)
283
self.assertInHistoryIs(3, revision_id, u'revid:\N{SNOWMAN}')
284
self.assertInHistoryIs(3, revision_id, 'revid:' + revision_id)
287
class TestRevisionSpec_last(TestRevisionSpec):
289
def test_positive(self):
290
self.assertInHistoryIs(2, 'r2', 'last:1')
291
self.assertInHistoryIs(1, 'r1', 'last:2')
292
self.assertInHistoryIs(0, None, 'last:3')
294
def test_empty(self):
295
self.assertInHistoryIs(2, 'r2', 'last:')
297
def test_negative(self):
298
self.assertInvalid('last:-1',
299
extra='\nyou must supply a positive value')
301
def test_missing(self):
302
self.assertInvalid('last:4')
304
def test_no_history(self):
305
tree = self.make_branch_and_tree('tree3')
307
self.assertRaises(errors.NoCommits,
308
spec_in_history, 'last:', tree.branch)
310
def test_not_a_number(self):
313
except ValueError, e:
315
self.assertInvalid('last:Y', extra='\n' + str(e))
318
class TestRevisionSpec_before(TestRevisionSpec):
321
self.assertInHistoryIs(1, 'r1', 'before:2')
322
self.assertInHistoryIs(1, 'r1', 'before:-1')
324
def test_before_one(self):
325
self.assertInHistoryIs(0, None, 'before:1')
327
def test_before_none(self):
328
self.assertInvalid('before:0',
329
extra='\ncannot go before the null: revision')
331
def test_revid(self):
332
self.assertInHistoryIs(1, 'r1', 'before:revid:r2')
335
self.assertInHistoryIs(1, 'r1', 'before:last:1')
337
def test_alt_revid(self):
338
# This will grab the left-most ancestor for alternate histories
339
self.assertInHistoryIs(1, 'r1', 'before:revid:alt_r2')
341
def test_alt_no_parents(self):
342
new_tree = self.make_branch_and_tree('new_tree')
343
new_tree.commit('first', rev_id='new_r1')
344
self.tree.branch.repository.fetch(new_tree.branch.repository,
345
revision_id='new_r1')
346
self.assertInHistoryIs(0, None, 'before:revid:new_r1')
349
class TestRevisionSpec_tag(TestRevisionSpec):
351
def make_branch_and_tree(self, relpath):
352
# override format as the default one may not support tags
353
control = bzrdir.BzrDir.create(relpath)
354
control.create_repository()
355
branch.BzrBranchExperimental.initialize(control)
356
return control.create_workingtree()
358
def test_from_string_tag(self):
359
spec = RevisionSpec.from_string('tag:bzr-0.14')
360
self.assertIsInstance(spec, RevisionSpec_tag)
361
self.assertEqual(spec.spec, 'bzr-0.14')
363
def test_lookup_tag(self):
364
self.tree.branch.tags.set_tag('bzr-0.14', 'r1')
365
self.assertInHistoryIs(1, 'r1', 'tag:bzr-0.14')
367
def test_failed_lookup(self):
368
# tags that don't exist give a specific message: arguably we should
369
# just give InvalidRevisionSpec but I think this is more helpful
370
self.assertRaises(errors.NoSuchTag,
372
'tag:some-random-tag')
375
class TestRevisionSpec_date(TestRevisionSpec):
378
super(TestRevisionSpec, self).setUp()
380
new_tree = self.make_branch_and_tree('new_tree')
381
new_tree.commit('Commit one', rev_id='new_r1',
382
timestamp=time.time() - 60*60*24)
383
new_tree.commit('Commit two', rev_id='new_r2')
384
new_tree.commit('Commit three', rev_id='new_r3')
388
def test_tomorrow(self):
389
self.assertInvalid('date:tomorrow')
391
def test_today(self):
392
self.assertInHistoryIs(2, 'new_r2', 'date:today')
393
self.assertInHistoryIs(1, 'new_r1', 'before:date:today')
395
def test_yesterday(self):
396
self.assertInHistoryIs(1, 'new_r1', 'date:yesterday')
398
def test_invalid(self):
399
self.assertInvalid('date:foobar', extra='\ninvalid date')
400
# You must have '-' between year/month/day
401
self.assertInvalid('date:20040404', extra='\ninvalid date')
402
# Need 2 digits for each date piece
403
self.assertInvalid('date:2004-4-4', extra='\ninvalid date')
406
now = datetime.datetime.now()
407
self.assertInHistoryIs(2, 'new_r2',
408
'date:%04d-%02d-%02d' % (now.year, now.month, now.day))
411
class TestRevisionSpec_ancestor(TestRevisionSpec):
413
def test_non_exact_branch(self):
414
# It seems better to require an exact path to the branch
415
# Branch.open() rather than using Branch.open_containing()
416
self.assertRaises(errors.NotBranchError,
417
self.get_in_history, 'ancestor:tree2/a')
419
def test_simple(self):
420
# Common ancestor of trees is 'alt_r2'
421
self.assertInHistoryIs(None, 'alt_r2', 'ancestor:tree2')
423
# Going the other way, we get a valid revno
425
self.tree = self.tree2
427
self.assertInHistoryIs(2, 'alt_r2', 'ancestor:tree')
430
self.assertInHistoryIs(2, 'r2', 'ancestor:tree')
432
def test_unrelated(self):
433
new_tree = self.make_branch_and_tree('new_tree')
435
new_tree.commit('Commit one', rev_id='new_r1')
436
new_tree.commit('Commit two', rev_id='new_r2')
437
new_tree.commit('Commit three', rev_id='new_r3')
439
# With no common ancestor, we should raise another user error
440
self.assertRaises(errors.NoCommonAncestor,
441
self.get_in_history, 'ancestor:new_tree')
443
def test_no_commits(self):
444
new_tree = self.make_branch_and_tree('new_tree')
445
self.assertRaises(errors.NoCommits,
446
spec_in_history, 'ancestor:new_tree',
449
self.assertRaises(errors.NoCommits,
450
spec_in_history, 'ancestor:tree',
454
class TestRevisionSpec_branch(TestRevisionSpec):
456
def test_non_exact_branch(self):
457
# It seems better to require an exact path to the branch
458
# Branch.open() rather than using Branch.open_containing()
459
self.assertRaises(errors.NotBranchError,
460
self.get_in_history, 'branch:tree2/a')
462
def test_simple(self):
463
self.assertInHistoryIs(None, 'alt_r2', 'branch:tree2')
466
self.assertInHistoryIs(2, 'r2', 'branch:tree')
468
def test_unrelated(self):
469
new_tree = self.make_branch_and_tree('new_tree')
471
new_tree.commit('Commit one', rev_id='new_r1')
472
new_tree.commit('Commit two', rev_id='new_r2')
473
new_tree.commit('Commit three', rev_id='new_r3')
475
self.assertInHistoryIs(None, 'new_r3', 'branch:new_tree')
477
# XXX: Right now, we use fetch() to make sure the remote revisions
478
# have been pulled into the local branch. We may change that
479
# behavior in the future.
480
self.failUnless(self.tree.branch.repository.has_revision('new_r3'))
482
def test_no_commits(self):
483
new_tree = self.make_branch_and_tree('new_tree')
484
self.assertRaises(errors.NoCommits,
485
self.get_in_history, 'branch:new_tree')
488
class TestRevisionSpec_submit(TestRevisionSpec):
490
def test_submit_branch(self):
491
# Common ancestor of trees is 'alt_r2'
492
self.assertRaises(errors.NoSubmitBranch, self.get_in_history,
494
self.tree.branch.set_parent('../tree2')
495
self.assertInHistoryIs(None, 'alt_r2', 'submit:')
496
self.tree.branch.set_parent('bogus')
497
self.assertRaises(errors.NotBranchError, self.get_in_history,
499
# submit branch overrides parent branch
500
self.tree.branch.set_submit_branch('tree2')
501
self.assertInHistoryIs(None, 'alt_r2', 'submit:')