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
25
from bzrlib.tests import TestCaseWithTransport
23
from bzrlib.errors import NoCommonAncestor, NoCommits
24
from bzrlib.errors import NoSuchRevision
25
26
from bzrlib.revisionspec import RevisionSpec
28
class TestRevisionNamespaces(TestCaseWithTransport):
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()
40
self.tree = self.make_branch_and_tree('tree')
41
self.build_tree(['tree/a'])
43
self.tree.commit('a', rev_id='r1')
45
self.tree2 = self.tree.bzrdir.sprout('tree2').open_workingtree()
46
self.tree2.commit('alt', rev_id='alt_r2')
48
self.tree.branch.repository.fetch(self.tree2.branch.repository,
50
self.tree.set_pending_merges(['alt_r2'])
51
self.tree.commit('second', rev_id='r2')
53
def get_in_history(self, revision_spec):
54
return spec_in_history(revision_spec, self.tree.branch)
56
def assertInHistoryIs(self, exp_revno, exp_revision_id, revision_spec):
57
rev_info = self.get_in_history(revision_spec)
58
self.assertEqual(exp_revno, rev_info.revno,
59
'Revision spec: %s returned wrong revno: %s != %s'
60
% (revision_spec, exp_revno, rev_info.revno))
61
self.assertEqual(exp_revision_id, rev_info.rev_id,
62
'Revision spec: %s returned wrong revision id:'
64
% (revision_spec, exp_revision_id, rev_info.rev_id))
66
def assertInvalid(self, revision_spec, extra=''):
68
self.get_in_history(revision_spec)
69
except errors.InvalidRevisionSpec, e:
70
self.assertEqual(revision_spec, e.spec)
71
self.assertEqual(extra, e.extra)
73
self.fail('Expected InvalidRevisionSpec to be raised for %s'
77
class TestOddRevisionSpec(TestRevisionSpec):
78
"""Test things that aren't normally thought of as revision specs"""
81
self.assertInHistoryIs(0, None, None)
83
def test_object(self):
84
self.assertRaises(TypeError, RevisionSpec.from_string, object())
86
def test_unregistered_spec(self):
87
self.assertRaises(errors.NoSuchRevisionSpec,
88
RevisionSpec.from_string, 'foo')
89
self.assertRaises(errors.NoSuchRevisionSpec,
90
RevisionSpec.from_string, '123a')
93
class TestRevisionSpec_revno(TestRevisionSpec):
95
def test_positive_int(self):
96
self.assertInHistoryIs(0, None, '0')
97
self.assertInHistoryIs(1, 'r1', '1')
98
self.assertInHistoryIs(2, 'r2', '2')
100
self.assertInvalid('3')
102
def test_negative_int(self):
103
self.assertInHistoryIs(2, 'r2', '-1')
104
self.assertInHistoryIs(1, 'r1', '-2')
106
self.assertInHistoryIs(1, 'r1', '-3')
107
self.assertInHistoryIs(1, 'r1', '-4')
108
self.assertInHistoryIs(1, 'r1', '-100')
110
def test_positive(self):
111
self.assertInHistoryIs(0, None, 'revno:0')
112
self.assertInHistoryIs(1, 'r1', 'revno:1')
113
self.assertInHistoryIs(2, 'r2', 'revno:2')
115
self.assertInvalid('revno:3')
117
def test_negative(self):
118
self.assertInHistoryIs(2, 'r2', 'revno:-1')
119
self.assertInHistoryIs(1, 'r1', 'revno:-2')
121
self.assertInHistoryIs(1, 'r1', 'revno:-3')
122
self.assertInHistoryIs(1, 'r1', 'revno:-4')
124
def test_invalid_number(self):
125
# Get the right exception text
128
except ValueError, e:
130
self.assertInvalid('revno:X', extra='\n' + str(e))
132
def test_missing_number_and_branch(self):
133
self.assertInvalid('revno::',
134
extra='\ncannot have an empty revno and no branch')
136
def test_invalid_number_with_branch(self):
139
except ValueError, e:
141
self.assertInvalid('revno:X:tree2', extra='\n' + str(e))
143
def test_non_exact_branch(self):
144
# It seems better to require an exact path to the branch
145
# Branch.open() rather than using Branch.open_containing()
146
spec = RevisionSpec.from_string('revno:2:tree2/a')
147
self.assertRaises(errors.NotBranchError,
148
spec.in_history, self.tree.branch)
150
def test_with_branch(self):
151
# Passing a URL overrides the supplied branch path
152
revinfo = self.get_in_history('revno:2:tree2')
153
self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
154
self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
155
self.assertEqual(2, revinfo.revno)
156
self.assertEqual('alt_r2', revinfo.rev_id)
158
def test_int_with_branch(self):
159
revinfo = self.get_in_history('2:tree2')
160
self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
161
self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
162
self.assertEqual(2, revinfo.revno)
163
self.assertEqual('alt_r2', revinfo.rev_id)
165
def test_with_url(self):
166
url = self.get_url() + '/tree2'
167
revinfo = self.get_in_history('revno:2:%s' % (url,))
168
self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
169
self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
170
self.assertEqual(2, revinfo.revno)
171
self.assertEqual('alt_r2', revinfo.rev_id)
173
def test_negative_with_url(self):
174
url = self.get_url() + '/tree2'
175
revinfo = self.get_in_history('revno:-1:%s' % (url,))
176
self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
177
self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
178
self.assertEqual(2, revinfo.revno)
179
self.assertEqual('alt_r2', revinfo.rev_id)
181
def test_different_history_lengths(self):
182
# Make sure we use the revisions and offsets in the supplied branch
183
# not the ones in the original branch.
184
self.tree2.commit('three', rev_id='r3')
185
self.assertInHistoryIs(3, 'r3', 'revno:3:tree2')
186
self.assertInHistoryIs(3, 'r3', 'revno:-1:tree2')
188
def test_invalid_branch(self):
189
self.assertRaises(errors.NotBranchError,
190
self.get_in_history, 'revno:-1:tree3')
192
def test_invalid_revno_in_branch(self):
193
self.tree.commit('three', rev_id='r3')
194
self.assertInvalid('revno:3:tree2')
30
196
def test_revno_n_path(self):
31
"""Test revision specifiers.
33
These identify revisions by date, etc."""
197
"""Old revno:N:path tests"""
34
198
wta = self.make_branch_and_tree('a')
45
209
wtb.commit('Commit two', rev_id='b@r-0-2')
46
210
wtb.commit('Commit three', rev_id='b@r-0-3')
48
self.assertEquals(RevisionSpec('revno:1:a/').in_history(ba),
213
self.assertEqual((1, 'a@r-0-1'),
214
spec_in_history('revno:1:a/', ba))
50
215
# The argument of in_history should be ignored since it is
51
216
# 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'))
217
self.assertEqual((1, 'a@r-0-1'),
218
spec_in_history('revno:1:a/', None))
219
self.assertEqual((1, 'a@r-0-1'),
220
spec_in_history('revno:1:a/', bb))
221
self.assertEqual((2, 'b@r-0-2'),
222
spec_in_history('revno:2:b/', None))
226
class TestRevisionSpec_revid(TestRevisionSpec):
228
def test_in_history(self):
229
# We should be able to access revisions that are directly
231
self.assertInHistoryIs(1, 'r1', 'revid:r1')
232
self.assertInHistoryIs(2, 'r2', 'revid:r2')
234
def test_missing(self):
235
self.assertInvalid('revid:r3')
237
def test_merged(self):
238
"""We can reach revisions in the ancestry"""
239
self.assertInHistoryIs(None, 'alt_r2', 'revid:alt_r2')
241
def test_not_here(self):
242
self.tree2.commit('alt third', rev_id='alt_r3')
243
# It exists in tree2, but not in tree
244
self.assertInvalid('revid:alt_r3')
246
def test_in_repository(self):
247
"""We can get any revision id in the repository"""
248
# XXX: This may change in the future, but for now, it is true
249
self.tree2.commit('alt third', rev_id='alt_r3')
250
self.tree.branch.repository.fetch(self.tree2.branch.repository,
251
revision_id='alt_r3')
252
self.assertInHistoryIs(None, 'alt_r3', 'revid:alt_r3')
255
class TestRevisionSpec_last(TestRevisionSpec):
257
def test_positive(self):
258
self.assertInHistoryIs(2, 'r2', 'last:1')
259
self.assertInHistoryIs(1, 'r1', 'last:2')
260
self.assertInHistoryIs(0, None, 'last:3')
262
def test_empty(self):
263
self.assertInHistoryIs(2, 'r2', 'last:')
265
def test_negative(self):
266
self.assertInvalid('last:-1',
267
extra='\nyou must supply a positive value')
269
def test_missing(self):
270
self.assertInvalid('last:4')
272
def test_no_history(self):
273
tree = self.make_branch_and_tree('tree3')
275
self.assertRaises(errors.NoCommits,
276
spec_in_history, 'last:', tree.branch)
278
def test_not_a_number(self):
281
except ValueError, e:
283
self.assertInvalid('last:Y', extra='\n' + str(e))
286
class TestRevisionSpec_before(TestRevisionSpec):
289
self.assertInHistoryIs(1, 'r1', 'before:2')
290
self.assertInHistoryIs(1, 'r1', 'before:-1')
292
def test_before_one(self):
293
self.assertInHistoryIs(0, None, 'before:1')
295
def test_before_none(self):
296
self.assertInvalid('before:0',
297
extra='\ncannot go before the null: revision')
299
def test_revid(self):
300
self.assertInHistoryIs(1, 'r1', 'before:revid:r2')
303
self.assertInHistoryIs(1, 'r1', 'before:last:1')
305
def test_alt_revid(self):
306
# This will grab the left-most ancestor for alternate histories
307
self.assertInHistoryIs(1, 'r1', 'before:revid:alt_r2')
309
def test_alt_no_parents(self):
310
new_tree = self.make_branch_and_tree('new_tree')
311
new_tree.commit('first', rev_id='new_r1')
312
self.tree.branch.repository.fetch(new_tree.branch.repository,
313
revision_id='new_r1')
314
self.assertInHistoryIs(0, None, 'before:revid:new_r1')
317
class TestRevisionSpec_tag(TestRevisionSpec):
319
def test_invalid(self):
320
self.assertInvalid('tag:foo', extra='\ntag: namespace registered,'
321
' but not implemented')
324
class TestRevisionSpec_date(TestRevisionSpec):
327
super(TestRevisionSpec, self).setUp()
329
new_tree = self.make_branch_and_tree('new_tree')
330
new_tree.commit('Commit one', rev_id='new_r1',
331
timestamp=time.time() - 60*60*24)
332
new_tree.commit('Commit two', rev_id='new_r2')
333
new_tree.commit('Commit three', rev_id='new_r3')
337
def test_tomorrow(self):
338
self.assertInvalid('date:tomorrow')
340
def test_today(self):
341
self.assertInHistoryIs(2, 'new_r2', 'date:today')
342
self.assertInHistoryIs(1, 'new_r1', 'before:date:today')
344
def test_yesterday(self):
345
self.assertInHistoryIs(1, 'new_r1', 'date:yesterday')
347
def test_invalid(self):
348
self.assertInvalid('date:foobar', extra='\ninvalid date')
349
# You must have '-' between year/month/day
350
self.assertInvalid('date:20040404', extra='\ninvalid date')
351
# Need 2 digits for each date piece
352
self.assertInvalid('date:2004-4-4', extra='\ninvalid date')
355
now = datetime.datetime.now()
356
self.assertInHistoryIs(2, 'new_r2',
357
'date:%04d-%02d-%02d' % (now.year, now.month, now.day))
360
class TestRevisionSpec_ancestor(TestRevisionSpec):
362
def test_non_exact_branch(self):
363
# It seems better to require an exact path to the branch
364
# Branch.open() rather than using Branch.open_containing()
365
self.assertRaises(errors.NotBranchError,
366
self.get_in_history, 'ancestor:tree2/a')
368
def test_simple(self):
369
# Common ancestor of trees is 'alt_r2'
370
self.assertInHistoryIs(None, 'alt_r2', 'ancestor:tree2')
372
# Going the other way, we get a valid revno
374
self.tree = self.tree2
376
self.assertInHistoryIs(2, 'alt_r2', 'ancestor:tree')
379
self.assertInHistoryIs(2, 'r2', 'ancestor:tree')
381
def test_unrelated(self):
382
new_tree = self.make_branch_and_tree('new_tree')
384
new_tree.commit('Commit one', rev_id='new_r1')
385
new_tree.commit('Commit two', rev_id='new_r2')
386
new_tree.commit('Commit three', rev_id='new_r3')
388
# With no common ancestor, we should raise another user error
389
self.assertRaises(errors.NoCommonAncestor,
390
self.get_in_history, 'ancestor:new_tree')
392
def test_no_commits(self):
393
new_tree = self.make_branch_and_tree('new_tree')
394
self.assertRaises(errors.NoCommits,
395
spec_in_history, 'ancestor:new_tree',
398
self.assertRaises(errors.NoCommits,
399
spec_in_history, 'ancestor:tree',
403
class TestRevisionSpec_branch(TestRevisionSpec):
405
def test_non_exact_branch(self):
406
# It seems better to require an exact path to the branch
407
# Branch.open() rather than using Branch.open_containing()
408
self.assertRaises(errors.NotBranchError,
409
self.get_in_history, 'branch:tree2/a')
411
def test_simple(self):
412
self.assertInHistoryIs(None, 'alt_r2', 'branch:tree2')
415
self.assertInHistoryIs(2, 'r2', 'branch:tree')
417
def test_unrelated(self):
418
new_tree = self.make_branch_and_tree('new_tree')
420
new_tree.commit('Commit one', rev_id='new_r1')
421
new_tree.commit('Commit two', rev_id='new_r2')
422
new_tree.commit('Commit three', rev_id='new_r3')
424
self.assertInHistoryIs(None, 'new_r3', 'branch:new_tree')
426
# XXX: Right now, we use fetch() to make sure the remote revisions
427
# have been pulled into the local branch. We may change that
428
# behavior in the future.
429
self.failUnless(self.tree.branch.repository.has_revision('new_r3'))
431
def test_no_commits(self):
432
new_tree = self.make_branch_and_tree('new_tree')
433
self.assertRaises(errors.NoCommits,
434
self.get_in_history, 'branch:new_tree')