~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_revisionspec.py

  • Committer: Jelmer Vernooij
  • Date: 2011-08-24 09:31:18 UTC
  • mto: This revision was merged to the branch mainline in revision 6094.
  • Revision ID: jelmer@samba.org-20110824093118-ewjpuuc9onlkn3la
Document --keep-tags.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005 by Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005-2011 Canonical Ltd
 
2
#
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.
7
 
 
 
7
#
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.
12
 
 
 
12
#
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
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
import os
 
17
import datetime
18
18
import time
19
19
 
20
 
from bzrlib.branch import Branch
21
 
from bzrlib.selftest import TestCaseInTempDir
22
 
from bzrlib.errors import NoCommonAncestor, NoCommits
23
 
from bzrlib.errors import NoSuchRevision
24
 
from bzrlib.clone import copy_branch
25
 
from bzrlib.merge import merge
26
 
from bzrlib.revisionspec import RevisionSpec
27
 
 
28
 
class TestRevisionNamespaces(TestCaseInTempDir):
29
 
 
30
 
    def test_revision_namespaces(self):
31
 
        """Test revision specifiers.
32
 
 
33
 
        These identify revisions by date, etc."""
34
 
 
35
 
        b = Branch.initialize('.')
36
 
 
37
 
        b.commit('Commit one', rev_id='a@r-0-1', timestamp=time.time() - 60*60*24)
38
 
        b.commit('Commit two', rev_id='a@r-0-2')
39
 
        b.commit('Commit three', rev_id='a@r-0-3')
40
 
 
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),
44
 
                          (1, 'a@r-0-1'))
45
 
        self.assertEquals(RevisionSpec('revid:a@r-0-1').in_history(b),
46
 
                          (1, 'a@r-0-1'))
47
 
        self.assertRaises(NoSuchRevision,
48
 
                          RevisionSpec('revid:a@r-0-0').in_history, b)
49
 
        self.assertRaises(TypeError, RevisionSpec, object)
50
 
 
51
 
        self.assertEquals(RevisionSpec('date:today').in_history(b),
52
 
                          (2, 'a@r-0-2'))
53
 
        self.assertEquals(RevisionSpec('date:yesterday').in_history(b),
54
 
                          (1, 'a@r-0-1'))
55
 
        self.assertEquals(RevisionSpec('before:date:today').in_history(b),
56
 
                          (1, 'a@r-0-1'))
57
 
 
58
 
        self.assertEquals(RevisionSpec('last:1').in_history(b),
59
 
                          (3, 'a@r-0-3'))
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'))
63
 
 
64
 
        self.assertEquals(RevisionSpec('ancestor:.').in_history(b).rev_id,
65
 
                          'a@r-0-3')
66
 
 
67
 
        os.mkdir('newbranch')
68
 
        b2 = Branch.initialize('newbranch')
69
 
        self.assertRaises(NoCommits, RevisionSpec('ancestor:.').in_history, b2)
70
 
 
71
 
        os.mkdir('copy')
72
 
        b3 = copy_branch(b, 'copy')
73
 
        b3.commit('Commit four', rev_id='b@r-0-4')
74
 
        self.assertEquals(RevisionSpec('ancestor:.').in_history(b3).rev_id,
75
 
                          'a@r-0-3')
76
 
        merge(['copy', -1], [None, None])
77
 
        b.commit('Commit five', rev_id='a@r-0-4')
78
 
        self.assertEquals(RevisionSpec('ancestor:copy').in_history(b).rev_id,
79
 
                          'b@r-0-4')
80
 
        self.assertEquals(RevisionSpec('ancestor:.').in_history(b3).rev_id,
81
 
                          'b@r-0-4')
82
 
 
83
 
    def test_branch_namespace(self):
84
 
        """Ensure that the branch namespace pulls in the requisite content."""
85
 
        self.build_tree(['branch1/', 'branch1/file', 'branch2/'])
86
 
        branch = Branch.initialize('branch1')
87
 
        branch.add(['file'])
88
 
        branch.commit('add file')
89
 
        copy_branch(branch, 'branch2')
90
 
        print >> open('branch2/file', 'w'), 'new content'
91
 
        branch2 = Branch.open('branch2')
92
 
        branch2.commit('update file', rev_id='A')
93
 
        spec = RevisionSpec('branch:./branch2/.bzr/../')
94
 
        rev_info = spec.in_history(branch)
95
 
        self.assertEqual(rev_info, (None, 'A'))
96
 
 
 
20
from bzrlib import (
 
21
    errors,
 
22
    revision as _mod_revision,
 
23
    )
 
24
from bzrlib.tests import TestCaseWithTransport
 
25
from bzrlib.revisionspec import (
 
26
    RevisionInfo,
 
27
    RevisionSpec,
 
28
    RevisionSpec_dwim,
 
29
    RevisionSpec_tag,
 
30
    )
 
31
 
 
32
 
 
33
def spec_in_history(spec, branch):
 
34
    """A simple helper to change a revision spec into a branch search"""
 
35
    return RevisionSpec.from_string(spec).in_history(branch)
 
36
 
 
37
 
 
38
# Basic class, which just creates a really basic set of revisions
 
39
class TestRevisionSpec(TestCaseWithTransport):
 
40
 
 
41
    def setUp(self):
 
42
        super(TestRevisionSpec, self).setUp()
 
43
        # this sets up a revision graph:
 
44
        # r1: []             1
 
45
        # alt_r2: [r1]       1.1.1
 
46
        # r2: [r1, alt_r2]   2
 
47
 
 
48
        self.tree = self.make_branch_and_tree('tree')
 
49
        self.build_tree(['tree/a'])
 
50
        self.tree.lock_write()
 
51
        self.addCleanup(self.tree.unlock)
 
52
        self.tree.add(['a'])
 
53
        self.tree.commit('a', rev_id='r1')
 
54
 
 
55
        self.tree2 = self.tree.bzrdir.sprout('tree2').open_workingtree()
 
56
        self.tree2.commit('alt', rev_id='alt_r2')
 
57
 
 
58
        self.tree.merge_from_branch(self.tree2.branch)
 
59
        self.tree.commit('second', rev_id='r2')
 
60
 
 
61
    def get_in_history(self, revision_spec):
 
62
        return spec_in_history(revision_spec, self.tree.branch)
 
63
 
 
64
    def assertInHistoryIs(self, exp_revno, exp_revision_id, revision_spec):
 
65
        rev_info = self.get_in_history(revision_spec)
 
66
        self.assertEqual(exp_revno, rev_info.revno,
 
67
                         'Revision spec: %r returned wrong revno: %r != %r'
 
68
                         % (revision_spec, exp_revno, rev_info.revno))
 
69
        self.assertEqual(exp_revision_id, rev_info.rev_id,
 
70
                         'Revision spec: %r returned wrong revision id:'
 
71
                         ' %r != %r'
 
72
                         % (revision_spec, exp_revision_id, rev_info.rev_id))
 
73
 
 
74
    def assertInvalid(self, revision_spec, extra='',
 
75
                      invalid_as_revision_id=True):
 
76
        try:
 
77
            self.get_in_history(revision_spec)
 
78
        except errors.InvalidRevisionSpec, e:
 
79
            self.assertEqual(revision_spec, e.spec)
 
80
            self.assertEqual(extra, e.extra)
 
81
        else:
 
82
            self.fail('Expected InvalidRevisionSpec to be raised for'
 
83
                      ' %r.in_history' % (revision_spec,))
 
84
        if invalid_as_revision_id:
 
85
            try:
 
86
                spec = RevisionSpec.from_string(revision_spec)
 
87
                spec.as_revision_id(self.tree.branch)
 
88
            except errors.InvalidRevisionSpec, e:
 
89
                self.assertEqual(revision_spec, e.spec)
 
90
                self.assertEqual(extra, e.extra)
 
91
            else:
 
92
                self.fail('Expected InvalidRevisionSpec to be raised for'
 
93
                          ' %r.as_revision_id' % (revision_spec,))
 
94
 
 
95
    def assertAsRevisionId(self, revision_id, revision_spec):
 
96
        """Calling as_revision_id() should return the specified id."""
 
97
        spec = RevisionSpec.from_string(revision_spec)
 
98
        self.assertEqual(revision_id,
 
99
                         spec.as_revision_id(self.tree.branch))
 
100
 
 
101
    def get_as_tree(self, revision_spec, tree=None):
 
102
        if tree is None:
 
103
            tree = self.tree
 
104
        spec = RevisionSpec.from_string(revision_spec)
 
105
        return spec.as_tree(tree.branch)
 
106
 
 
107
 
 
108
class RevisionSpecMatchOnTrap(RevisionSpec):
 
109
 
 
110
    def _match_on(self, branch, revs):
 
111
        self.last_call = (branch, revs)
 
112
        return super(RevisionSpecMatchOnTrap, self)._match_on(branch, revs)
 
113
 
 
114
 
 
115
class TestRevisionSpecBase(TestRevisionSpec):
 
116
 
 
117
    def test_wants_revision_history(self):
 
118
        # If wants_revision_history = True, then _match_on should get the
 
119
        # branch revision history
 
120
        spec = RevisionSpecMatchOnTrap('foo', _internal=True)
 
121
        spec.in_history(self.tree.branch)
 
122
 
 
123
        self.assertEqual((self.tree.branch, ['r1' ,'r2']),
 
124
                         spec.last_call)
 
125
 
 
126
    def test_wants_no_revision_history(self):
 
127
        # If wants_revision_history = False, then _match_on should get None for
 
128
        # the branch revision history
 
129
        spec = RevisionSpecMatchOnTrap('foo', _internal=True)
 
130
        spec.wants_revision_history = False
 
131
        spec.in_history(self.tree.branch)
 
132
 
 
133
        self.assertEqual((self.tree.branch, None), spec.last_call)
 
134
 
 
135
 
 
136
 
 
137
class TestOddRevisionSpec(TestRevisionSpec):
 
138
    """Test things that aren't normally thought of as revision specs"""
 
139
 
 
140
    def test_none(self):
 
141
        self.assertInHistoryIs(None, None, None)
 
142
 
 
143
    def test_object(self):
 
144
        self.assertRaises(TypeError, RevisionSpec.from_string, object())
 
145
 
 
146
 
 
147
class RevisionSpec_bork(RevisionSpec):
 
148
 
 
149
    prefix = 'irrelevant:'
 
150
 
 
151
    def _match_on(self, branch, revs):
 
152
        if self.spec == "bork":
 
153
            return RevisionInfo.from_revision_id(branch, "r1", revs)
 
154
        else:
 
155
            raise errors.InvalidRevisionSpec(self.spec, branch)
 
156
 
 
157
 
 
158
class TestRevisionSpec_dwim(TestRevisionSpec):
 
159
 
 
160
    # Don't need to test revno's explicitly since TRS_revno already
 
161
    # covers that well for us
 
162
    def test_dwim_spec_revno(self):
 
163
        self.assertInHistoryIs(2, 'r2', '2')
 
164
        self.assertAsRevisionId('alt_r2', '1.1.1')
 
165
 
 
166
    def test_dwim_spec_revid(self):
 
167
        self.assertInHistoryIs(2, 'r2', 'r2')
 
168
 
 
169
    def test_dwim_spec_tag(self):
 
170
        self.tree.branch.tags.set_tag('footag', 'r1')
 
171
        self.assertAsRevisionId('r1', 'footag')
 
172
        self.tree.branch.tags.delete_tag('footag')
 
173
        self.assertRaises(errors.InvalidRevisionSpec,
 
174
                          self.get_in_history, 'footag')
 
175
 
 
176
    def test_dwim_spec_tag_that_looks_like_revno(self):
 
177
        # Test that we slip past revno with things that look like revnos,
 
178
        # but aren't.  Tags are convenient for testing this since we can
 
179
        # make them look however we want.
 
180
        self.tree.branch.tags.set_tag('3', 'r2')
 
181
        self.assertAsRevisionId('r2', '3')
 
182
        self.build_tree(['tree/b'])
 
183
        self.tree.add(['b'])
 
184
        self.tree.commit('b', rev_id='r3')
 
185
        self.assertAsRevisionId('r3', '3')
 
186
 
 
187
    def test_dwim_spec_date(self):
 
188
        self.assertAsRevisionId('r1', 'today')
 
189
 
 
190
    def test_dwim_spec_branch(self):
 
191
        self.assertInHistoryIs(None, 'alt_r2', 'tree2')
 
192
 
 
193
    def test_dwim_spec_nonexistent(self):
 
194
        self.assertInvalid('somethingrandom', invalid_as_revision_id=False)
 
195
        self.assertInvalid('-1.1', invalid_as_revision_id=False)
 
196
        self.assertInvalid('.1', invalid_as_revision_id=False)
 
197
        self.assertInvalid('1..1', invalid_as_revision_id=False)
 
198
        self.assertInvalid('1.2..1', invalid_as_revision_id=False)
 
199
        self.assertInvalid('1.', invalid_as_revision_id=False)
 
200
 
 
201
    def test_append_dwim_revspec(self):
 
202
        original_dwim_revspecs = list(RevisionSpec_dwim._possible_revspecs)
 
203
        def reset_dwim_revspecs():
 
204
            RevisionSpec_dwim._possible_revspecs = original_dwim_revspecs
 
205
        self.addCleanup(reset_dwim_revspecs)
 
206
        RevisionSpec_dwim.append_possible_revspec(RevisionSpec_bork)
 
207
        self.assertAsRevisionId('r1', 'bork')
 
208
 
 
209
    def test_append_lazy_dwim_revspec(self):
 
210
        original_dwim_revspecs = list(RevisionSpec_dwim._possible_revspecs)
 
211
        def reset_dwim_revspecs():
 
212
            RevisionSpec_dwim._possible_revspecs = original_dwim_revspecs
 
213
        self.addCleanup(reset_dwim_revspecs)
 
214
        RevisionSpec_dwim.append_possible_lazy_revspec(
 
215
            "bzrlib.tests.test_revisionspec", "RevisionSpec_bork")
 
216
        self.assertAsRevisionId('r1', 'bork')
 
217
 
 
218
 
 
219
class TestRevisionSpec_revno(TestRevisionSpec):
 
220
 
 
221
    def test_positive_int(self):
 
222
        self.assertInHistoryIs(0, 'null:', '0')
 
223
        self.assertInHistoryIs(1, 'r1', '1')
 
224
        self.assertInHistoryIs(2, 'r2', '2')
 
225
        self.assertInvalid('3')
 
226
 
 
227
    def test_dotted_decimal(self):
 
228
        self.assertInHistoryIs(None, 'alt_r2', '1.1.1')
 
229
        self.assertInvalid('1.1.123')
 
230
 
 
231
    def test_negative_int(self):
 
232
        self.assertInHistoryIs(2, 'r2', '-1')
 
233
        self.assertInHistoryIs(1, 'r1', '-2')
 
234
 
 
235
        self.assertInHistoryIs(1, 'r1', '-3')
 
236
        self.assertInHistoryIs(1, 'r1', '-4')
 
237
        self.assertInHistoryIs(1, 'r1', '-100')
 
238
 
 
239
    def test_positive(self):
 
240
        self.assertInHistoryIs(0, 'null:', 'revno:0')
 
241
        self.assertInHistoryIs(1, 'r1', 'revno:1')
 
242
        self.assertInHistoryIs(2, 'r2', 'revno:2')
 
243
 
 
244
        self.assertInvalid('revno:3')
 
245
 
 
246
    def test_negative(self):
 
247
        self.assertInHistoryIs(2, 'r2', 'revno:-1')
 
248
        self.assertInHistoryIs(1, 'r1', 'revno:-2')
 
249
 
 
250
        self.assertInHistoryIs(1, 'r1', 'revno:-3')
 
251
        self.assertInHistoryIs(1, 'r1', 'revno:-4')
 
252
 
 
253
    def test_invalid_number(self):
 
254
        # Get the right exception text
 
255
        try:
 
256
            int('X')
 
257
        except ValueError, e:
 
258
            pass
 
259
        self.assertInvalid('revno:X', extra='\n' + str(e))
 
260
 
 
261
    def test_missing_number_and_branch(self):
 
262
        self.assertInvalid('revno::',
 
263
                           extra='\ncannot have an empty revno and no branch')
 
264
 
 
265
    def test_invalid_number_with_branch(self):
 
266
        try:
 
267
            int('X')
 
268
        except ValueError, e:
 
269
            pass
 
270
        self.assertInvalid('revno:X:tree2', extra='\n' + str(e))
 
271
 
 
272
    def test_non_exact_branch(self):
 
273
        # It seems better to require an exact path to the branch
 
274
        # Branch.open() rather than using Branch.open_containing()
 
275
        spec = RevisionSpec.from_string('revno:2:tree2/a')
 
276
        self.assertRaises(errors.NotBranchError,
 
277
                          spec.in_history, self.tree.branch)
 
278
 
 
279
    def test_with_branch(self):
 
280
        # Passing a URL overrides the supplied branch path
 
281
        revinfo = self.get_in_history('revno:2:tree2')
 
282
        self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
 
283
        self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
 
284
        self.assertEqual(2, revinfo.revno)
 
285
        self.assertEqual('alt_r2', revinfo.rev_id)
 
286
 
 
287
    def test_int_with_branch(self):
 
288
        revinfo = self.get_in_history('2:tree2')
 
289
        self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
 
290
        self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
 
291
        self.assertEqual(2, revinfo.revno)
 
292
        self.assertEqual('alt_r2', revinfo.rev_id)
 
293
 
 
294
    def test_with_url(self):
 
295
        url = self.get_url() + '/tree2'
 
296
        revinfo = self.get_in_history('revno:2:%s' % (url,))
 
297
        self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
 
298
        self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
 
299
        self.assertEqual(2, revinfo.revno)
 
300
        self.assertEqual('alt_r2', revinfo.rev_id)
 
301
 
 
302
    def test_negative_with_url(self):
 
303
        url = self.get_url() + '/tree2'
 
304
        revinfo = self.get_in_history('revno:-1:%s' % (url,))
 
305
        self.assertNotEqual(self.tree.branch.base, revinfo.branch.base)
 
306
        self.assertEqual(self.tree2.branch.base, revinfo.branch.base)
 
307
        self.assertEqual(2, revinfo.revno)
 
308
        self.assertEqual('alt_r2', revinfo.rev_id)
 
309
 
 
310
    def test_different_history_lengths(self):
 
311
        # Make sure we use the revisions and offsets in the supplied branch
 
312
        # not the ones in the original branch.
 
313
        self.tree2.commit('three', rev_id='r3')
 
314
        self.assertInHistoryIs(3, 'r3', 'revno:3:tree2')
 
315
        self.assertInHistoryIs(3, 'r3', 'revno:-1:tree2')
 
316
 
 
317
    def test_invalid_branch(self):
 
318
        self.assertRaises(errors.NotBranchError,
 
319
                          self.get_in_history, 'revno:-1:tree3')
 
320
 
 
321
    def test_invalid_revno_in_branch(self):
 
322
        self.tree.commit('three', rev_id='r3')
 
323
        self.assertInvalid('revno:3:tree2')
 
324
 
 
325
    def test_revno_n_path(self):
 
326
        """Old revno:N:path tests"""
 
327
        wta = self.make_branch_and_tree('a')
 
328
        ba = wta.branch
 
329
 
 
330
        wta.commit('Commit one', rev_id='a@r-0-1')
 
331
        wta.commit('Commit two', rev_id='a@r-0-2')
 
332
        wta.commit('Commit three', rev_id='a@r-0-3')
 
333
 
 
334
        wtb = self.make_branch_and_tree('b')
 
335
        bb = wtb.branch
 
336
 
 
337
        wtb.commit('Commit one', rev_id='b@r-0-1')
 
338
        wtb.commit('Commit two', rev_id='b@r-0-2')
 
339
        wtb.commit('Commit three', rev_id='b@r-0-3')
 
340
 
 
341
 
 
342
        self.assertEqual((1, 'a@r-0-1'),
 
343
                         spec_in_history('revno:1:a/', ba))
 
344
        # The argument of in_history should be ignored since it is
 
345
        # redundant with the path in the spec.
 
346
        self.assertEqual((1, 'a@r-0-1'),
 
347
                         spec_in_history('revno:1:a/', None))
 
348
        self.assertEqual((1, 'a@r-0-1'),
 
349
                         spec_in_history('revno:1:a/', bb))
 
350
        self.assertEqual((2, 'b@r-0-2'),
 
351
                         spec_in_history('revno:2:b/', None))
 
352
 
 
353
    def test_as_revision_id(self):
 
354
        self.assertAsRevisionId('null:', '0')
 
355
        self.assertAsRevisionId('r1', '1')
 
356
        self.assertAsRevisionId('r2', '2')
 
357
        self.assertAsRevisionId('r1', '-2')
 
358
        self.assertAsRevisionId('r2', '-1')
 
359
        self.assertAsRevisionId('alt_r2', '1.1.1')
 
360
 
 
361
    def test_as_tree(self):
 
362
        tree = self.get_as_tree('0')
 
363
        self.assertEquals(_mod_revision.NULL_REVISION, tree.get_revision_id())
 
364
        tree = self.get_as_tree('1')
 
365
        self.assertEquals('r1', tree.get_revision_id())
 
366
        tree = self.get_as_tree('2')
 
367
        self.assertEquals('r2', tree.get_revision_id())
 
368
        tree = self.get_as_tree('-2')
 
369
        self.assertEquals('r1', tree.get_revision_id())
 
370
        tree = self.get_as_tree('-1')
 
371
        self.assertEquals('r2', tree.get_revision_id())
 
372
        tree = self.get_as_tree('1.1.1')
 
373
        self.assertEquals('alt_r2', tree.get_revision_id())
 
374
 
 
375
 
 
376
class TestRevisionSpec_revid(TestRevisionSpec):
 
377
 
 
378
    def test_in_history(self):
 
379
        # We should be able to access revisions that are directly
 
380
        # in the history.
 
381
        self.assertInHistoryIs(1, 'r1', 'revid:r1')
 
382
        self.assertInHistoryIs(2, 'r2', 'revid:r2')
 
383
 
 
384
    def test_missing(self):
 
385
        self.assertInvalid('revid:r3', invalid_as_revision_id=False)
 
386
 
 
387
    def test_merged(self):
 
388
        """We can reach revisions in the ancestry"""
 
389
        self.assertInHistoryIs(None, 'alt_r2', 'revid:alt_r2')
 
390
 
 
391
    def test_not_here(self):
 
392
        self.tree2.commit('alt third', rev_id='alt_r3')
 
393
        # It exists in tree2, but not in tree
 
394
        self.assertInvalid('revid:alt_r3', invalid_as_revision_id=False)
 
395
 
 
396
    def test_in_repository(self):
 
397
        """We can get any revision id in the repository"""
 
398
        # XXX: This may change in the future, but for now, it is true
 
399
        self.tree2.commit('alt third', rev_id='alt_r3')
 
400
        self.tree.branch.fetch(self.tree2.branch, 'alt_r3')
 
401
        self.assertInHistoryIs(None, 'alt_r3', 'revid:alt_r3')
 
402
 
 
403
    def test_unicode(self):
 
404
        """We correctly convert a unicode ui string to an encoded revid."""
 
405
        revision_id = u'\N{SNOWMAN}'.encode('utf-8')
 
406
        self.tree.commit('unicode', rev_id=revision_id)
 
407
        self.assertInHistoryIs(3, revision_id, u'revid:\N{SNOWMAN}')
 
408
        self.assertInHistoryIs(3, revision_id, 'revid:' + revision_id)
 
409
 
 
410
    def test_as_revision_id(self):
 
411
        self.assertAsRevisionId('r1', 'revid:r1')
 
412
        self.assertAsRevisionId('r2', 'revid:r2')
 
413
        self.assertAsRevisionId('alt_r2', 'revid:alt_r2')
 
414
 
 
415
 
 
416
class TestRevisionSpec_last(TestRevisionSpec):
 
417
 
 
418
    def test_positive(self):
 
419
        self.assertInHistoryIs(2, 'r2', 'last:1')
 
420
        self.assertInHistoryIs(1, 'r1', 'last:2')
 
421
        self.assertInHistoryIs(0, 'null:', 'last:3')
 
422
 
 
423
    def test_empty(self):
 
424
        self.assertInHistoryIs(2, 'r2', 'last:')
 
425
 
 
426
    def test_negative(self):
 
427
        self.assertInvalid('last:-1',
 
428
                           extra='\nyou must supply a positive value')
 
429
 
 
430
    def test_missing(self):
 
431
        self.assertInvalid('last:4')
 
432
 
 
433
    def test_no_history(self):
 
434
        tree = self.make_branch_and_tree('tree3')
 
435
 
 
436
        self.assertRaises(errors.NoCommits,
 
437
                          spec_in_history, 'last:', tree.branch)
 
438
 
 
439
    def test_not_a_number(self):
 
440
        try:
 
441
            int('Y')
 
442
        except ValueError, e:
 
443
            pass
 
444
        self.assertInvalid('last:Y', extra='\n' + str(e))
 
445
 
 
446
    def test_as_revision_id(self):
 
447
        self.assertAsRevisionId('r2', 'last:1')
 
448
        self.assertAsRevisionId('r1', 'last:2')
 
449
 
 
450
 
 
451
class TestRevisionSpec_before(TestRevisionSpec):
 
452
 
 
453
    def test_int(self):
 
454
        self.assertInHistoryIs(1, 'r1', 'before:2')
 
455
        self.assertInHistoryIs(1, 'r1', 'before:-1')
 
456
 
 
457
    def test_before_one(self):
 
458
        self.assertInHistoryIs(0, 'null:', 'before:1')
 
459
 
 
460
    def test_before_none(self):
 
461
        self.assertInvalid('before:0',
 
462
                           extra='\ncannot go before the null: revision')
 
463
 
 
464
    def test_revid(self):
 
465
        self.assertInHistoryIs(1, 'r1', 'before:revid:r2')
 
466
 
 
467
    def test_last(self):
 
468
        self.assertInHistoryIs(1, 'r1', 'before:last:1')
 
469
 
 
470
    def test_alt_revid(self):
 
471
        # This will grab the left-most ancestor for alternate histories
 
472
        self.assertInHistoryIs(1, 'r1', 'before:revid:alt_r2')
 
473
 
 
474
    def test_alt_no_parents(self):
 
475
        new_tree = self.make_branch_and_tree('new_tree')
 
476
        new_tree.commit('first', rev_id='new_r1')
 
477
        self.tree.branch.fetch(new_tree.branch, 'new_r1')
 
478
        self.assertInHistoryIs(0, 'null:', 'before:revid:new_r1')
 
479
 
 
480
    def test_as_revision_id(self):
 
481
        self.assertAsRevisionId('r1', 'before:revid:r2')
 
482
        self.assertAsRevisionId('r1', 'before:2')
 
483
        self.assertAsRevisionId('r1', 'before:1.1.1')
 
484
        self.assertAsRevisionId('r1', 'before:revid:alt_r2')
 
485
 
 
486
 
 
487
class TestRevisionSpec_tag(TestRevisionSpec):
 
488
 
 
489
    def make_branch_and_tree(self, relpath):
 
490
        # override format as the default one may not support tags
 
491
        return TestRevisionSpec.make_branch_and_tree(
 
492
            self, relpath, format='dirstate-tags')
 
493
 
 
494
    def test_from_string_tag(self):
 
495
        spec = RevisionSpec.from_string('tag:bzr-0.14')
 
496
        self.assertIsInstance(spec, RevisionSpec_tag)
 
497
        self.assertEqual(spec.spec, 'bzr-0.14')
 
498
 
 
499
    def test_lookup_tag(self):
 
500
        self.tree.branch.tags.set_tag('bzr-0.14', 'r1')
 
501
        self.assertInHistoryIs(1, 'r1', 'tag:bzr-0.14')
 
502
        self.tree.branch.tags.set_tag('null_rev', 'null:')
 
503
        self.assertInHistoryIs(0, 'null:', 'tag:null_rev')
 
504
 
 
505
    def test_failed_lookup(self):
 
506
        # tags that don't exist give a specific message: arguably we should
 
507
        # just give InvalidRevisionSpec but I think this is more helpful
 
508
        self.assertRaises(errors.NoSuchTag,
 
509
            self.get_in_history,
 
510
            'tag:some-random-tag')
 
511
 
 
512
    def test_as_revision_id(self):
 
513
        self.tree.branch.tags.set_tag('my-tag', 'r2')
 
514
        self.tree.branch.tags.set_tag('null_rev', 'null:')
 
515
        self.assertAsRevisionId('r2', 'tag:my-tag')
 
516
        self.assertAsRevisionId('null:', 'tag:null_rev')
 
517
        self.assertAsRevisionId('r1', 'before:tag:my-tag')
 
518
 
 
519
 
 
520
class TestRevisionSpec_date(TestRevisionSpec):
 
521
 
 
522
    def setUp(self):
 
523
        super(TestRevisionSpec, self).setUp()
 
524
 
 
525
        new_tree = self.make_branch_and_tree('new_tree')
 
526
        new_tree.commit('Commit one', rev_id='new_r1',
 
527
                        timestamp=time.time() - 60*60*24)
 
528
        new_tree.commit('Commit two', rev_id='new_r2')
 
529
        new_tree.commit('Commit three', rev_id='new_r3')
 
530
 
 
531
        self.tree = new_tree
 
532
 
 
533
    def test_tomorrow(self):
 
534
        self.assertInvalid('date:tomorrow')
 
535
 
 
536
    def test_today(self):
 
537
        self.assertInHistoryIs(2, 'new_r2', 'date:today')
 
538
        self.assertInHistoryIs(1, 'new_r1', 'before:date:today')
 
539
 
 
540
    def test_yesterday(self):
 
541
        self.assertInHistoryIs(1, 'new_r1', 'date:yesterday')
 
542
 
 
543
    def test_invalid(self):
 
544
        self.assertInvalid('date:foobar', extra='\ninvalid date')
 
545
        # You must have '-' between year/month/day
 
546
        self.assertInvalid('date:20040404', extra='\ninvalid date')
 
547
        # Need 2 digits for each date piece
 
548
        self.assertInvalid('date:2004-4-4', extra='\ninvalid date')
 
549
 
 
550
    def test_day(self):
 
551
        now = datetime.datetime.now()
 
552
        self.assertInHistoryIs(2, 'new_r2',
 
553
            'date:%04d-%02d-%02d' % (now.year, now.month, now.day))
 
554
 
 
555
    def test_as_revision_id(self):
 
556
        self.assertAsRevisionId('new_r2', 'date:today')
 
557
 
 
558
 
 
559
class TestRevisionSpec_ancestor(TestRevisionSpec):
 
560
 
 
561
    def test_non_exact_branch(self):
 
562
        # It seems better to require an exact path to the branch
 
563
        # Branch.open() rather than using Branch.open_containing()
 
564
        self.assertRaises(errors.NotBranchError,
 
565
                          self.get_in_history, 'ancestor:tree2/a')
 
566
 
 
567
    def test_simple(self):
 
568
        # Common ancestor of trees is 'alt_r2'
 
569
        self.assertInHistoryIs(None, 'alt_r2', 'ancestor:tree2')
 
570
 
 
571
        # Going the other way, we get a valid revno
 
572
        tmp = self.tree
 
573
        self.tree = self.tree2
 
574
        self.tree2 = tmp
 
575
        self.assertInHistoryIs(2, 'alt_r2', 'ancestor:tree')
 
576
 
 
577
    def test_self(self):
 
578
        self.assertInHistoryIs(2, 'r2', 'ancestor:tree')
 
579
 
 
580
    def test_unrelated(self):
 
581
        new_tree = self.make_branch_and_tree('new_tree')
 
582
 
 
583
        new_tree.commit('Commit one', rev_id='new_r1')
 
584
        new_tree.commit('Commit two', rev_id='new_r2')
 
585
        new_tree.commit('Commit three', rev_id='new_r3')
 
586
 
 
587
        # With no common ancestor, we should raise another user error
 
588
        self.assertRaises(errors.NoCommonAncestor,
 
589
                          self.get_in_history, 'ancestor:new_tree')
 
590
 
 
591
    def test_no_commits(self):
 
592
        new_tree = self.make_branch_and_tree('new_tree')
 
593
        self.assertRaises(errors.NoCommits,
 
594
                          spec_in_history, 'ancestor:new_tree',
 
595
                                           self.tree.branch)
 
596
 
 
597
        self.assertRaises(errors.NoCommits,
 
598
                          spec_in_history, 'ancestor:tree',
 
599
                                           new_tree.branch)
 
600
 
 
601
    def test_as_revision_id(self):
 
602
        self.assertAsRevisionId('alt_r2', 'ancestor:tree2')
 
603
 
 
604
    def test_default(self):
 
605
        # We don't have a parent to default to
 
606
        self.assertRaises(errors.NotBranchError, self.get_in_history,
 
607
                          'ancestor:')
 
608
 
 
609
        # Create a branch with a parent to default to
 
610
        tree3 = self.tree.bzrdir.sprout('tree3').open_workingtree()
 
611
        tree3.commit('foo', rev_id='r3')
 
612
        self.tree = tree3
 
613
        self.assertInHistoryIs(2, 'r2', 'ancestor:')
 
614
 
 
615
 
 
616
class TestRevisionSpec_branch(TestRevisionSpec):
 
617
 
 
618
    def test_non_exact_branch(self):
 
619
        # It seems better to require an exact path to the branch
 
620
        # Branch.open() rather than using Branch.open_containing()
 
621
        self.assertRaises(errors.NotBranchError,
 
622
                          self.get_in_history, 'branch:tree2/a')
 
623
 
 
624
    def test_simple(self):
 
625
        self.assertInHistoryIs(None, 'alt_r2', 'branch:tree2')
 
626
 
 
627
    def test_self(self):
 
628
        self.assertInHistoryIs(2, 'r2', 'branch:tree')
 
629
 
 
630
    def test_unrelated(self):
 
631
        new_tree = self.make_branch_and_tree('new_tree')
 
632
 
 
633
        new_tree.commit('Commit one', rev_id='new_r1')
 
634
        new_tree.commit('Commit two', rev_id='new_r2')
 
635
        new_tree.commit('Commit three', rev_id='new_r3')
 
636
 
 
637
        self.assertInHistoryIs(None, 'new_r3', 'branch:new_tree')
 
638
 
 
639
        # XXX: Right now, we use fetch() to make sure the remote revisions
 
640
        # have been pulled into the local branch. We may change that
 
641
        # behavior in the future.
 
642
        self.assertTrue(self.tree.branch.repository.has_revision('new_r3'))
 
643
 
 
644
    def test_no_commits(self):
 
645
        new_tree = self.make_branch_and_tree('new_tree')
 
646
        self.assertRaises(errors.NoCommits,
 
647
                          self.get_in_history, 'branch:new_tree')
 
648
        self.assertRaises(errors.NoCommits,
 
649
                          self.get_as_tree, 'branch:new_tree')
 
650
 
 
651
    def test_as_revision_id(self):
 
652
        self.assertAsRevisionId('alt_r2', 'branch:tree2')
 
653
 
 
654
    def test_as_tree(self):
 
655
        tree = self.get_as_tree('branch:tree', self.tree2)
 
656
        self.assertEquals('r2', tree.get_revision_id())
 
657
        self.assertFalse(self.tree2.branch.repository.has_revision('r2'))
 
658
 
 
659
 
 
660
class TestRevisionSpec_submit(TestRevisionSpec):
 
661
 
 
662
    def test_submit_branch(self):
 
663
        # Common ancestor of trees is 'alt_r2'
 
664
        self.assertRaises(errors.NoSubmitBranch, self.get_in_history,
 
665
                          'submit:')
 
666
        self.tree.branch.set_parent('../tree2')
 
667
        self.assertInHistoryIs(None, 'alt_r2', 'submit:')
 
668
        self.tree.branch.set_parent('bogus')
 
669
        self.assertRaises(errors.NotBranchError, self.get_in_history,
 
670
            'submit:')
 
671
        # submit branch overrides parent branch
 
672
        self.tree.branch.set_submit_branch('tree2')
 
673
        self.assertInHistoryIs(None, 'alt_r2', 'submit:')
 
674
 
 
675
    def test_as_revision_id(self):
 
676
        self.tree.branch.set_submit_branch('tree2')
 
677
        self.assertAsRevisionId('alt_r2', 'branch:tree2')
 
678
 
 
679
 
 
680
class TestRevisionSpec_mainline(TestRevisionSpec):
 
681
 
 
682
    def test_as_revision_id(self):
 
683
        self.assertAsRevisionId('r1', 'mainline:1')
 
684
        self.assertAsRevisionId('r2', 'mainline:1.1.1')
 
685
        self.assertAsRevisionId('r2', 'mainline:revid:alt_r2')
 
686
        spec = RevisionSpec.from_string('mainline:revid:alt_r22')
 
687
        e = self.assertRaises(errors.InvalidRevisionSpec,
 
688
                              spec.as_revision_id, self.tree.branch)
 
689
        self.assertContainsRe(str(e),
 
690
            "Requested revision: 'mainline:revid:alt_r22' does not exist in"
 
691
            " branch: ")
 
692
 
 
693
    def test_in_history(self):
 
694
        self.assertInHistoryIs(2, 'r2', 'mainline:revid:alt_r2')
 
695
 
 
696
 
 
697
class TestRevisionSpec_annotate(TestRevisionSpec):
 
698
 
 
699
    def setUp(self):
 
700
        TestRevisionSpec.setUp(self)
 
701
        self.tree = self.make_branch_and_tree('annotate-tree')
 
702
        self.build_tree_contents([('annotate-tree/file1', '1\n')])
 
703
        self.tree.add('file1')
 
704
        self.tree.commit('r1', rev_id='r1')
 
705
        self.build_tree_contents([('annotate-tree/file1', '2\n1\n')])
 
706
        self.tree.commit('r2', rev_id='r2')
 
707
        self.build_tree_contents([('annotate-tree/file1', '2\n1\n3\n')])
 
708
 
 
709
    def test_as_revision_id_r1(self):
 
710
        self.assertAsRevisionId('r1', 'annotate:annotate-tree/file1:2')
 
711
 
 
712
    def test_as_revision_id_r2(self):
 
713
        self.assertAsRevisionId('r2', 'annotate:annotate-tree/file1:1')
 
714
 
 
715
    def test_as_revision_id_uncommitted(self):
 
716
        spec = RevisionSpec.from_string('annotate:annotate-tree/file1:3')
 
717
        e = self.assertRaises(errors.InvalidRevisionSpec,
 
718
                              spec.as_revision_id, self.tree.branch)
 
719
        self.assertContainsRe(str(e),
 
720
            r"Requested revision: \'annotate:annotate-tree/file1:3\' does not"
 
721
            " exist in branch: .*\nLine 3 has not been committed.")
 
722
 
 
723
    def test_non_existent_line(self):
 
724
        spec = RevisionSpec.from_string('annotate:annotate-tree/file1:4')
 
725
        e = self.assertRaises(errors.InvalidRevisionSpec,
 
726
                              spec.as_revision_id, self.tree.branch)
 
727
        self.assertContainsRe(str(e),
 
728
            r"Requested revision: \'annotate:annotate-tree/file1:4\' does not"
 
729
            " exist in branch: .*\nNo such line: 4")
 
730
 
 
731
    def test_invalid_line(self):
 
732
        spec = RevisionSpec.from_string('annotate:annotate-tree/file1:q')
 
733
        e = self.assertRaises(errors.InvalidRevisionSpec,
 
734
                              spec.as_revision_id, self.tree.branch)
 
735
        self.assertContainsRe(str(e),
 
736
            r"Requested revision: \'annotate:annotate-tree/file1:q\' does not"
 
737
            " exist in branch: .*\nNo such line: q")
 
738
 
 
739
    def test_no_such_file(self):
 
740
        spec = RevisionSpec.from_string('annotate:annotate-tree/file2:1')
 
741
        e = self.assertRaises(errors.InvalidRevisionSpec,
 
742
                              spec.as_revision_id, self.tree.branch)
 
743
        self.assertContainsRe(str(e),
 
744
            r"Requested revision: \'annotate:annotate-tree/file2:1\' does not"
 
745
            " exist in branch: .*\nFile 'file2' is not versioned")
 
746
 
 
747
    def test_no_such_file_with_colon(self):
 
748
        spec = RevisionSpec.from_string('annotate:annotate-tree/fi:le2:1')
 
749
        e = self.assertRaises(errors.InvalidRevisionSpec,
 
750
                              spec.as_revision_id, self.tree.branch)
 
751
        self.assertContainsRe(str(e),
 
752
            r"Requested revision: \'annotate:annotate-tree/fi:le2:1\' does not"
 
753
            " exist in branch: .*\nFile 'fi:le2' is not versioned")