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
24
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):
25
from bzrlib.tests import TestCase, TestCaseWithTransport
26
from bzrlib.revisionspec import RevisionSpec, RevisionSpec_revno
29
def spec_in_history(spec, branch):
30
"""A simple helper to change a revision spec into a branch search"""
31
return RevisionSpec.from_string(spec).in_history(branch)
34
# Basic class, which just creates a really basic set of revisions
35
class TestRevisionSpec(TestCaseWithTransport):
38
super(TestRevisionSpec, self).setUp()
39
# this sets up a revision graph:
44
self.tree = self.make_branch_and_tree('tree')
45
self.build_tree(['tree/a'])
47
self.tree.commit('a', rev_id='r1')
49
self.tree2 = self.tree.bzrdir.sprout('tree2').open_workingtree()
50
self.tree2.commit('alt', rev_id='alt_r2')
52
self.tree.branch.repository.fetch(self.tree2.branch.repository,
54
self.tree.set_pending_merges(['alt_r2'])
55
self.tree.commit('second', rev_id='r2')
57
def get_in_history(self, revision_spec):
58
return spec_in_history(revision_spec, self.tree.branch)
60
def assertInHistoryIs(self, exp_revno, exp_revision_id, revision_spec):
61
rev_info = self.get_in_history(revision_spec)
62
self.assertEqual(exp_revno, rev_info.revno,
63
'Revision spec: %s returned wrong revno: %s != %s'
64
% (revision_spec, exp_revno, rev_info.revno))
65
self.assertEqual(exp_revision_id, rev_info.rev_id,
66
'Revision spec: %s returned wrong revision id:'
68
% (revision_spec, exp_revision_id, rev_info.rev_id))
70
def assertInvalid(self, revision_spec, extra=''):
72
self.get_in_history(revision_spec)
73
except errors.InvalidRevisionSpec, e:
74
self.assertEqual(revision_spec, e.spec)
75
self.assertEqual(extra, e.extra)
77
self.fail('Expected InvalidRevisionSpec to be raised for %s'
81
class TestOddRevisionSpec(TestRevisionSpec):
82
"""Test things that aren't normally thought of as revision specs"""
85
self.assertInHistoryIs(0, None, None)
87
def test_object(self):
88
self.assertRaises(TypeError, RevisionSpec.from_string, object())
90
def test_unregistered_spec(self):
91
self.assertRaises(errors.NoSuchRevisionSpec,
92
RevisionSpec.from_string, 'foo')
93
self.assertRaises(errors.NoSuchRevisionSpec,
94
RevisionSpec.from_string, '123a')
98
class TestRevnoFromString(TestCase):
100
def test_from_string_dotted_decimal(self):
101
self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '-1.1')
102
self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '.1')
103
self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '1..1')
104
self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '1.2..1')
105
self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '1.')
106
self.assertIsInstance(RevisionSpec.from_string('1.1'), RevisionSpec_revno)
107
self.assertIsInstance(RevisionSpec.from_string('1.1.3'), RevisionSpec_revno)
110
class TestRevisionSpec_revno(TestRevisionSpec):
112
def test_positive_int(self):
113
self.assertInHistoryIs(0, None, '0')
114
self.assertInHistoryIs(1, 'r1', '1')
115
self.assertInHistoryIs(2, 'r2', '2')
116
self.assertInvalid('3')
118
def test_dotted_decimal(self):
119
self.assertInHistoryIs(None, 'alt_r2', '1.1.1')
121
def test_negative_int(self):
122
self.assertInHistoryIs(2, 'r2', '-1')
123
self.assertInHistoryIs(1, 'r1', '-2')
125
self.assertInHistoryIs(1, 'r1', '-3')
126
self.assertInHistoryIs(1, 'r1', '-4')
127
self.assertInHistoryIs(1, 'r1', '-100')
129
def test_positive(self):
130
self.assertInHistoryIs(0, None, 'revno:0')
131
self.assertInHistoryIs(1, 'r1', 'revno:1')
132
self.assertInHistoryIs(2, 'r2', 'revno:2')
134
self.assertInvalid('revno:3')
136
def test_negative(self):
137
self.assertInHistoryIs(2, 'r2', 'revno:-1')
138
self.assertInHistoryIs(1, 'r1', 'revno:-2')
140
self.assertInHistoryIs(1, 'r1', 'revno:-3')
141
self.assertInHistoryIs(1, 'r1', 'revno:-4')
143
def test_invalid_number(self):
144
# Get the right exception text
147
except ValueError, e:
149
self.assertInvalid('revno:X', extra='\n' + str(e))
151
def test_missing_number_and_branch(self):
152
self.assertInvalid('revno::',
153
extra='\ncannot have an empty revno and no branch')
155
def test_invalid_number_with_branch(self):
158
except ValueError, e:
160
self.assertInvalid('revno:X:tree2', extra='\n' + str(e))
162
def test_non_exact_branch(self):
163
# It seems better to require an exact path to the branch
164
# Branch.open() rather than using Branch.open_containing()
165
spec = RevisionSpec.from_string('revno:2:tree2/a')
166
self.assertRaises(errors.NotBranchError,
167
spec.in_history, self.tree.branch)
169
def test_with_branch(self):
170
# Passing a URL overrides the supplied branch path
171
revinfo = self.get_in_history('revno:2:tree2')
172
self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
173
self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
174
self.assertEqual(2, revinfo.revno)
175
self.assertEqual('alt_r2', revinfo.rev_id)
177
def test_int_with_branch(self):
178
revinfo = self.get_in_history('2:tree2')
179
self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
180
self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
181
self.assertEqual(2, revinfo.revno)
182
self.assertEqual('alt_r2', revinfo.rev_id)
184
def test_with_url(self):
185
url = self.get_url() + '/tree2'
186
revinfo = self.get_in_history('revno:2:%s' % (url,))
187
self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
188
self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
189
self.assertEqual(2, revinfo.revno)
190
self.assertEqual('alt_r2', revinfo.rev_id)
192
def test_negative_with_url(self):
193
url = self.get_url() + '/tree2'
194
revinfo = self.get_in_history('revno:-1:%s' % (url,))
195
self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
196
self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
197
self.assertEqual(2, revinfo.revno)
198
self.assertEqual('alt_r2', revinfo.rev_id)
200
def test_different_history_lengths(self):
201
# Make sure we use the revisions and offsets in the supplied branch
202
# not the ones in the original branch.
203
self.tree2.commit('three', rev_id='r3')
204
self.assertInHistoryIs(3, 'r3', 'revno:3:tree2')
205
self.assertInHistoryIs(3, 'r3', 'revno:-1:tree2')
207
def test_invalid_branch(self):
208
self.assertRaises(errors.NotBranchError,
209
self.get_in_history, 'revno:-1:tree3')
211
def test_invalid_revno_in_branch(self):
212
self.tree.commit('three', rev_id='r3')
213
self.assertInvalid('revno:3:tree2')
30
215
def test_revno_n_path(self):
31
"""Test revision specifiers.
33
These identify revisions by date, etc."""
216
"""Old revno:N:path tests"""
34
217
wta = self.make_branch_and_tree('a')
45
228
wtb.commit('Commit two', rev_id='b@r-0-2')
46
229
wtb.commit('Commit three', rev_id='b@r-0-3')
48
self.assertEquals(RevisionSpec('revno:1:a/').in_history(ba),
232
self.assertEqual((1, 'a@r-0-1'),
233
spec_in_history('revno:1:a/', ba))
50
234
# The argument of in_history should be ignored since it is
51
235
# redundant with the path in the spec.
52
self.assertEquals(RevisionSpec('revno:1:a/').in_history(None),
54
self.assertEquals(RevisionSpec('revno:1:a/').in_history(bb),
56
self.assertEquals(RevisionSpec('revno:2:b/').in_history(None),
60
def test_revision_namespaces(self):
61
"""Test revision specifiers.
63
These identify revisions by date, etc."""
64
wt = self.make_branch_and_tree('.')
67
wt.commit('Commit one', rev_id='a@r-0-1', timestamp=time.time() - 60*60*24)
68
wt.commit('Commit two', rev_id='a@r-0-2')
69
wt.commit('Commit three', rev_id='a@r-0-3')
71
self.assertEquals(RevisionSpec(None).in_history(b), (0, None))
72
self.assertEquals(RevisionSpec(1).in_history(b), (1, 'a@r-0-1'))
73
self.assertEquals(RevisionSpec('revno:1').in_history(b),
75
self.assertEquals(RevisionSpec('revid:a@r-0-1').in_history(b),
77
self.assertRaises(NoSuchRevision,
78
RevisionSpec('revid:a@r-0-0').in_history, b)
79
self.assertRaises(TypeError, RevisionSpec, object)
81
self.assertEquals(RevisionSpec('date:today').in_history(b),
83
self.assertRaises(NoSuchRevision,
84
RevisionSpec('date:tomorrow').in_history, b)
85
self.assertEquals(RevisionSpec('date:yesterday').in_history(b),
87
self.assertEquals(RevisionSpec('before:date:today').in_history(b),
90
self.assertEquals(RevisionSpec('last:1').in_history(b),
92
self.assertEquals(RevisionSpec('-1').in_history(b), (3, 'a@r-0-3'))
93
# self.assertEquals(b.get_revision_info('last:1'), (3, 'a@r-0-3'))
94
# self.assertEquals(b.get_revision_info('-1'), (3, 'a@r-0-3'))
96
self.assertEquals(RevisionSpec('ancestor:.').in_history(b).rev_id,
100
wt2 = self.make_branch_and_tree('newbranch')
102
self.assertRaises(NoCommits, RevisionSpec('ancestor:.').in_history, b2)
104
d3 = b.bzrdir.sprout('copy')
105
b3 = d3.open_branch()
106
wt3 = d3.open_workingtree()
107
wt3.commit('Commit four', rev_id='b@r-0-4')
108
self.assertEquals(RevisionSpec('ancestor:.').in_history(b3).rev_id,
110
merge(['copy', -1], [None, None])
111
wt.commit('Commit five', rev_id='a@r-0-4')
112
self.assertEquals(RevisionSpec('ancestor:copy').in_history(b).rev_id,
114
self.assertEquals(RevisionSpec('ancestor:.').in_history(b3).rev_id,
117
# This should be in the revision store, but not in revision-history
118
self.assertEquals((None, 'b@r-0-4'),
119
RevisionSpec('revid:b@r-0-4').in_history(b))
121
def test_branch_namespace(self):
122
"""Ensure that the branch namespace pulls in the requisite content."""
123
self.build_tree(['branch1/', 'branch1/file', 'branch2/'])
124
wt = self.make_branch_and_tree('branch1')
127
wt.commit('add file')
128
d2 = branch.bzrdir.sprout('branch2')
129
print >> open('branch2/file', 'w'), 'new content'
130
branch2 = d2.open_branch()
131
d2.open_workingtree().commit('update file', rev_id='A')
132
spec = RevisionSpec('branch:./branch2/.bzr/../')
133
rev_info = spec.in_history(branch)
134
self.assertEqual(rev_info, (None, 'A'))
236
self.assertEqual((1, 'a@r-0-1'),
237
spec_in_history('revno:1:a/', None))
238
self.assertEqual((1, 'a@r-0-1'),
239
spec_in_history('revno:1:a/', bb))
240
self.assertEqual((2, 'b@r-0-2'),
241
spec_in_history('revno:2:b/', None))
245
class TestRevisionSpec_revid(TestRevisionSpec):
247
def test_in_history(self):
248
# We should be able to access revisions that are directly
250
self.assertInHistoryIs(1, 'r1', 'revid:r1')
251
self.assertInHistoryIs(2, 'r2', 'revid:r2')
253
def test_missing(self):
254
self.assertInvalid('revid:r3')
256
def test_merged(self):
257
"""We can reach revisions in the ancestry"""
258
self.assertInHistoryIs(None, 'alt_r2', 'revid:alt_r2')
260
def test_not_here(self):
261
self.tree2.commit('alt third', rev_id='alt_r3')
262
# It exists in tree2, but not in tree
263
self.assertInvalid('revid:alt_r3')
265
def test_in_repository(self):
266
"""We can get any revision id in the repository"""
267
# XXX: This may change in the future, but for now, it is true
268
self.tree2.commit('alt third', rev_id='alt_r3')
269
self.tree.branch.repository.fetch(self.tree2.branch.repository,
270
revision_id='alt_r3')
271
self.assertInHistoryIs(None, 'alt_r3', 'revid:alt_r3')
274
class TestRevisionSpec_last(TestRevisionSpec):
276
def test_positive(self):
277
self.assertInHistoryIs(2, 'r2', 'last:1')
278
self.assertInHistoryIs(1, 'r1', 'last:2')
279
self.assertInHistoryIs(0, None, 'last:3')
281
def test_empty(self):
282
self.assertInHistoryIs(2, 'r2', 'last:')
284
def test_negative(self):
285
self.assertInvalid('last:-1',
286
extra='\nyou must supply a positive value')
288
def test_missing(self):
289
self.assertInvalid('last:4')
291
def test_no_history(self):
292
tree = self.make_branch_and_tree('tree3')
294
self.assertRaises(errors.NoCommits,
295
spec_in_history, 'last:', tree.branch)
297
def test_not_a_number(self):
300
except ValueError, e:
302
self.assertInvalid('last:Y', extra='\n' + str(e))
305
class TestRevisionSpec_before(TestRevisionSpec):
308
self.assertInHistoryIs(1, 'r1', 'before:2')
309
self.assertInHistoryIs(1, 'r1', 'before:-1')
311
def test_before_one(self):
312
self.assertInHistoryIs(0, None, 'before:1')
314
def test_before_none(self):
315
self.assertInvalid('before:0',
316
extra='\ncannot go before the null: revision')
318
def test_revid(self):
319
self.assertInHistoryIs(1, 'r1', 'before:revid:r2')
322
self.assertInHistoryIs(1, 'r1', 'before:last:1')
324
def test_alt_revid(self):
325
# This will grab the left-most ancestor for alternate histories
326
self.assertInHistoryIs(1, 'r1', 'before:revid:alt_r2')
328
def test_alt_no_parents(self):
329
new_tree = self.make_branch_and_tree('new_tree')
330
new_tree.commit('first', rev_id='new_r1')
331
self.tree.branch.repository.fetch(new_tree.branch.repository,
332
revision_id='new_r1')
333
self.assertInHistoryIs(0, None, 'before:revid:new_r1')
336
class TestRevisionSpec_tag(TestRevisionSpec):
338
def test_invalid(self):
339
self.assertInvalid('tag:foo', extra='\ntag: namespace registered,'
340
' but not implemented')
343
class TestRevisionSpec_date(TestRevisionSpec):
346
super(TestRevisionSpec, self).setUp()
348
new_tree = self.make_branch_and_tree('new_tree')
349
new_tree.commit('Commit one', rev_id='new_r1',
350
timestamp=time.time() - 60*60*24)
351
new_tree.commit('Commit two', rev_id='new_r2')
352
new_tree.commit('Commit three', rev_id='new_r3')
356
def test_tomorrow(self):
357
self.assertInvalid('date:tomorrow')
359
def test_today(self):
360
self.assertInHistoryIs(2, 'new_r2', 'date:today')
361
self.assertInHistoryIs(1, 'new_r1', 'before:date:today')
363
def test_yesterday(self):
364
self.assertInHistoryIs(1, 'new_r1', 'date:yesterday')
366
def test_invalid(self):
367
self.assertInvalid('date:foobar', extra='\ninvalid date')
368
# You must have '-' between year/month/day
369
self.assertInvalid('date:20040404', extra='\ninvalid date')
370
# Need 2 digits for each date piece
371
self.assertInvalid('date:2004-4-4', extra='\ninvalid date')
374
now = datetime.datetime.now()
375
self.assertInHistoryIs(2, 'new_r2',
376
'date:%04d-%02d-%02d' % (now.year, now.month, now.day))
379
class TestRevisionSpec_ancestor(TestRevisionSpec):
381
def test_non_exact_branch(self):
382
# It seems better to require an exact path to the branch
383
# Branch.open() rather than using Branch.open_containing()
384
self.assertRaises(errors.NotBranchError,
385
self.get_in_history, 'ancestor:tree2/a')
387
def test_simple(self):
388
# Common ancestor of trees is 'alt_r2'
389
self.assertInHistoryIs(None, 'alt_r2', 'ancestor:tree2')
391
# Going the other way, we get a valid revno
393
self.tree = self.tree2
395
self.assertInHistoryIs(2, 'alt_r2', 'ancestor:tree')
398
self.assertInHistoryIs(2, 'r2', 'ancestor:tree')
400
def test_unrelated(self):
401
new_tree = self.make_branch_and_tree('new_tree')
403
new_tree.commit('Commit one', rev_id='new_r1')
404
new_tree.commit('Commit two', rev_id='new_r2')
405
new_tree.commit('Commit three', rev_id='new_r3')
407
# With no common ancestor, we should raise another user error
408
self.assertRaises(errors.NoCommonAncestor,
409
self.get_in_history, 'ancestor:new_tree')
411
def test_no_commits(self):
412
new_tree = self.make_branch_and_tree('new_tree')
413
self.assertRaises(errors.NoCommits,
414
spec_in_history, 'ancestor:new_tree',
417
self.assertRaises(errors.NoCommits,
418
spec_in_history, 'ancestor:tree',
422
class TestRevisionSpec_branch(TestRevisionSpec):
424
def test_non_exact_branch(self):
425
# It seems better to require an exact path to the branch
426
# Branch.open() rather than using Branch.open_containing()
427
self.assertRaises(errors.NotBranchError,
428
self.get_in_history, 'branch:tree2/a')
430
def test_simple(self):
431
self.assertInHistoryIs(None, 'alt_r2', 'branch:tree2')
434
self.assertInHistoryIs(2, 'r2', 'branch:tree')
436
def test_unrelated(self):
437
new_tree = self.make_branch_and_tree('new_tree')
439
new_tree.commit('Commit one', rev_id='new_r1')
440
new_tree.commit('Commit two', rev_id='new_r2')
441
new_tree.commit('Commit three', rev_id='new_r3')
443
self.assertInHistoryIs(None, 'new_r3', 'branch:new_tree')
445
# XXX: Right now, we use fetch() to make sure the remote revisions
446
# have been pulled into the local branch. We may change that
447
# behavior in the future.
448
self.failUnless(self.tree.branch.repository.has_revision('new_r3'))
450
def test_no_commits(self):
451
new_tree = self.make_branch_and_tree('new_tree')
452
self.assertRaises(errors.NoCommits,
453
self.get_in_history, 'branch:new_tree')