~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/testworkingtree.py

Don't ignore bzrlib/plugins -- really bad idea because they can have bad 
old plugins lying around!

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# (C) 2005 Canonical Ltd
2
2
# Authors:  Robert Collins <robert.collins@canonical.com>
3
3
#
4
4
# This program is free software; you can redistribute it and/or modify
15
15
# along with this program; if not, write to the Free Software
16
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
17
 
18
 
from cStringIO import StringIO
19
18
import os
20
 
 
21
 
from bzrlib import ignores
22
 
import bzrlib
23
19
from bzrlib.branch import Branch
24
 
from bzrlib import bzrdir, conflicts, errors, workingtree
25
 
from bzrlib.bzrdir import BzrDir
26
20
from bzrlib.errors import NotBranchError, NotVersionedError
27
 
from bzrlib.lockdir import LockDir
28
 
from bzrlib.mutabletree import needs_tree_write_lock
29
 
from bzrlib.osutils import pathjoin, getcwd, has_symlinks
30
 
from bzrlib.tests import TestCase, TestCaseWithTransport, TestSkipped
 
21
from bzrlib.selftest import TestCaseInTempDir
31
22
from bzrlib.trace import mutter
32
 
from bzrlib.transport import get_transport
33
 
from bzrlib.workingtree import (
34
 
    TreeEntry,
35
 
    TreeDirectory,
36
 
    TreeFile,
37
 
    TreeLink,
38
 
    WorkingTree,
39
 
    )
 
23
from bzrlib.workingtree import (TreeEntry, TreeDirectory, TreeFile, TreeLink,
 
24
                                WorkingTree)
40
25
 
41
 
class TestTreeDirectory(TestCaseWithTransport):
 
26
class TestTreeDirectory(TestCaseInTempDir):
42
27
 
43
28
    def test_kind_character(self):
44
29
        self.assertEqual(TreeDirectory().kind_character(), '/')
45
30
 
46
31
 
47
 
class TestTreeEntry(TestCaseWithTransport):
 
32
class TestTreeEntry(TestCaseInTempDir):
48
33
 
49
34
    def test_kind_character(self):
50
35
        self.assertEqual(TreeEntry().kind_character(), '???')
51
36
 
52
37
 
53
 
class TestTreeFile(TestCaseWithTransport):
 
38
class TestTreeFile(TestCaseInTempDir):
54
39
 
55
40
    def test_kind_character(self):
56
41
        self.assertEqual(TreeFile().kind_character(), '')
57
42
 
58
43
 
59
 
class TestTreeLink(TestCaseWithTransport):
 
44
class TestTreeLink(TestCaseInTempDir):
60
45
 
61
46
    def test_kind_character(self):
62
47
        self.assertEqual(TreeLink().kind_character(), '')
63
48
 
64
49
 
65
 
class TestDefaultFormat(TestCaseWithTransport):
66
 
 
67
 
    def test_get_set_default_format(self):
68
 
        old_format = workingtree.WorkingTreeFormat.get_default_format()
69
 
        # default is 3
70
 
        self.assertTrue(isinstance(old_format, workingtree.WorkingTreeFormat3))
71
 
        workingtree.WorkingTreeFormat.set_default_format(SampleTreeFormat())
72
 
        try:
73
 
            # the default branch format is used by the meta dir format
74
 
            # which is not the default bzrdir format at this point
75
 
            dir = bzrdir.BzrDirMetaFormat1().initialize('.')
76
 
            dir.create_repository()
77
 
            dir.create_branch()
78
 
            result = dir.create_workingtree()
79
 
            self.assertEqual(result, 'A tree')
80
 
        finally:
81
 
            workingtree.WorkingTreeFormat.set_default_format(old_format)
82
 
        self.assertEqual(old_format, workingtree.WorkingTreeFormat.get_default_format())
83
 
 
84
 
 
85
 
class SampleTreeFormat(workingtree.WorkingTreeFormat):
86
 
    """A sample format
87
 
 
88
 
    this format is initializable, unsupported to aid in testing the 
89
 
    open and open_downlevel routines.
90
 
    """
91
 
 
92
 
    def get_format_string(self):
93
 
        """See WorkingTreeFormat.get_format_string()."""
94
 
        return "Sample tree format."
95
 
 
96
 
    def initialize(self, a_bzrdir, revision_id=None):
97
 
        """Sample branches cannot be created."""
98
 
        t = a_bzrdir.get_workingtree_transport(self)
99
 
        t.put_bytes('format', self.get_format_string())
100
 
        return 'A tree'
101
 
 
102
 
    def is_supported(self):
103
 
        return False
104
 
 
105
 
    def open(self, transport, _found=False):
106
 
        return "opened tree."
107
 
 
108
 
 
109
 
class TestWorkingTreeFormat(TestCaseWithTransport):
110
 
    """Tests for the WorkingTreeFormat facility."""
111
 
 
112
 
    def test_find_format(self):
113
 
        # is the right format object found for a working tree?
114
 
        # create a branch with a few known format objects.
115
 
        self.build_tree(["foo/", "bar/"])
116
 
        def check_format(format, url):
117
 
            dir = format._matchingbzrdir.initialize(url)
118
 
            dir.create_repository()
119
 
            dir.create_branch()
120
 
            format.initialize(dir)
121
 
            t = get_transport(url)
122
 
            found_format = workingtree.WorkingTreeFormat.find_format(dir)
123
 
            self.failUnless(isinstance(found_format, format.__class__))
124
 
        check_format(workingtree.WorkingTreeFormat3(), "bar")
125
 
        
126
 
    def test_find_format_no_tree(self):
127
 
        dir = bzrdir.BzrDirMetaFormat1().initialize('.')
128
 
        self.assertRaises(errors.NoWorkingTree,
129
 
                          workingtree.WorkingTreeFormat.find_format,
130
 
                          dir)
131
 
 
132
 
    def test_find_format_unknown_format(self):
133
 
        dir = bzrdir.BzrDirMetaFormat1().initialize('.')
134
 
        dir.create_repository()
135
 
        dir.create_branch()
136
 
        SampleTreeFormat().initialize(dir)
137
 
        self.assertRaises(errors.UnknownFormatError,
138
 
                          workingtree.WorkingTreeFormat.find_format,
139
 
                          dir)
140
 
 
141
 
    def test_register_unregister_format(self):
142
 
        format = SampleTreeFormat()
143
 
        # make a control dir
144
 
        dir = bzrdir.BzrDirMetaFormat1().initialize('.')
145
 
        dir.create_repository()
146
 
        dir.create_branch()
147
 
        # make a branch
148
 
        format.initialize(dir)
149
 
        # register a format for it.
150
 
        workingtree.WorkingTreeFormat.register_format(format)
151
 
        # which branch.Open will refuse (not supported)
152
 
        self.assertRaises(errors.UnsupportedFormatError, workingtree.WorkingTree.open, '.')
153
 
        # but open_downlevel will work
154
 
        self.assertEqual(format.open(dir), workingtree.WorkingTree.open_downlevel('.'))
155
 
        # unregister the format
156
 
        workingtree.WorkingTreeFormat.unregister_format(format)
157
 
 
158
 
 
159
 
class TestWorkingTreeFormat3(TestCaseWithTransport):
160
 
    """Tests specific to WorkingTreeFormat3."""
161
 
 
162
 
    def test_disk_layout(self):
163
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
164
 
        control.create_repository()
165
 
        control.create_branch()
166
 
        tree = workingtree.WorkingTreeFormat3().initialize(control)
167
 
        # we want:
168
 
        # format 'Bazaar-NG Working Tree format 3'
169
 
        # inventory = blank inventory
170
 
        # pending-merges = ''
171
 
        # stat-cache = ??
172
 
        # no inventory.basis yet
173
 
        t = control.get_workingtree_transport(None)
174
 
        self.assertEqualDiff('Bazaar-NG Working Tree format 3',
175
 
                             t.get('format').read())
176
 
        self.assertEqualDiff('<inventory format="5">\n'
177
 
                             '</inventory>\n',
178
 
                             t.get('inventory').read())
179
 
        self.assertEqualDiff('### bzr hashcache v5\n',
180
 
                             t.get('stat-cache').read())
181
 
        self.assertFalse(t.has('inventory.basis'))
182
 
        # no last-revision file means 'None' or 'NULLREVISION'
183
 
        self.assertFalse(t.has('last-revision'))
184
 
        # TODO RBC 20060210 do a commit, check the inventory.basis is created 
185
 
        # correctly and last-revision file becomes present.
186
 
 
187
 
    def test_uses_lockdir(self):
188
 
        """WorkingTreeFormat3 uses its own LockDir:
189
 
            
190
 
            - lock is a directory
191
 
            - when the WorkingTree is locked, LockDir can see that
192
 
        """
193
 
        t = self.get_transport()
194
 
        url = self.get_url()
195
 
        dir = bzrdir.BzrDirMetaFormat1().initialize(url)
196
 
        repo = dir.create_repository()
197
 
        branch = dir.create_branch()
198
 
        try:
199
 
            tree = workingtree.WorkingTreeFormat3().initialize(dir)
200
 
        except errors.NotLocalUrl:
201
 
            raise TestSkipped('Not a local URL')
202
 
        self.assertIsDirectory('.bzr', t)
203
 
        self.assertIsDirectory('.bzr/checkout', t)
204
 
        self.assertIsDirectory('.bzr/checkout/lock', t)
205
 
        our_lock = LockDir(t, '.bzr/checkout/lock')
206
 
        self.assertEquals(our_lock.peek(), None)
 
50
class TestWorkingTree(TestCaseInTempDir):
 
51
 
 
52
    def test_listfiles(self):
 
53
        branch = Branch.initialize('.')
 
54
        os.mkdir('dir')
 
55
        print >> open('file', 'w'), "content"
 
56
        os.symlink('target', 'symlink')
 
57
        tree = branch.working_tree()
 
58
        files = list(tree.list_files())
 
59
        self.assertEqual(files[0], ('dir', '?', 'directory', None, TreeDirectory()))
 
60
        self.assertEqual(files[1], ('file', '?', 'file', None, TreeFile()))
 
61
        self.assertEqual(files[2], ('symlink', '?', 'symlink', None, TreeLink()))
 
62
 
 
63
    def test_open_containing(self):
 
64
        branch = Branch.initialize('.')
 
65
        wt, relpath = WorkingTree.open_containing()
 
66
        self.assertEqual('', relpath)
 
67
        self.assertEqual(wt.basedir, branch.base)
 
68
        wt, relpath = WorkingTree.open_containing('.')
 
69
        self.assertEqual('', relpath)
 
70
        self.assertEqual(wt.basedir, branch.base)
 
71
        wt, relpath = WorkingTree.open_containing('./foo')
 
72
        self.assertEqual('foo', relpath)
 
73
        self.assertEqual(wt.basedir, branch.base)
 
74
        # paths that are urls are just plain wrong for working trees.
 
75
        self.assertRaises(NotBranchError,
 
76
                          WorkingTree.open_containing, 
 
77
                          'file:///' + os.getcwdu())
 
78
 
 
79
    def test_construct_with_branch(self):
 
80
        branch = Branch.initialize('.')
 
81
        tree = WorkingTree(branch.base, branch)
 
82
        self.assertEqual(branch, tree.branch)
 
83
        self.assertEqual(branch.base, tree.basedir)
 
84
    
 
85
    def test_construct_without_branch(self):
 
86
        branch = Branch.initialize('.')
 
87
        tree = WorkingTree(branch.base)
 
88
        self.assertEqual(branch.base, tree.branch.base)
 
89
        self.assertEqual(branch.base, tree.basedir)
 
90
 
 
91
    def test_basic_relpath(self):
 
92
        # for comprehensive relpath tests, see whitebox.py.
 
93
        branch = Branch.initialize('.')
 
94
        tree = WorkingTree(branch.base)
 
95
        self.assertEqual('child',
 
96
                         tree.relpath(os.path.join(os.getcwd(), 'child')))
 
97
 
 
98
    def test_lock_locks_branch(self):
 
99
        branch = Branch.initialize('.')
 
100
        tree = WorkingTree(branch.base)
 
101
        tree.lock_read()
 
102
        self.assertEqual(1, tree.branch._lock_count)
 
103
        self.assertEqual('r', tree.branch._lock_mode)
 
104
        tree.unlock()
 
105
        self.assertEqual(None, tree.branch._lock_count)
207
106
        tree.lock_write()
208
 
        self.assertTrue(our_lock.peek())
 
107
        self.assertEqual(1, tree.branch._lock_count)
 
108
        self.assertEqual('w', tree.branch._lock_mode)
209
109
        tree.unlock()
210
 
        self.assertEquals(our_lock.peek(), None)
211
 
 
212
 
    def test_missing_pending_merges(self):
213
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
214
 
        control.create_repository()
215
 
        control.create_branch()
216
 
        tree = workingtree.WorkingTreeFormat3().initialize(control)
217
 
        tree._control_files._transport.delete("pending-merges")
218
 
        self.assertEqual([], tree.get_parent_ids())
219
 
 
220
 
 
221
 
class TestFormat2WorkingTree(TestCaseWithTransport):
222
 
    """Tests that are specific to format 2 trees."""
223
 
 
224
 
    def create_format2_tree(self, url):
225
 
        return self.make_branch_and_tree(
226
 
            url, format=bzrlib.bzrdir.BzrDirFormat6())
227
 
 
228
 
    def test_conflicts(self):
229
 
        # test backwards compatability
230
 
        tree = self.create_format2_tree('.')
231
 
        self.assertRaises(errors.UnsupportedOperation, tree.set_conflicts,
232
 
                          None)
233
 
        file('lala.BASE', 'wb').write('labase')
234
 
        expected = conflicts.ContentsConflict('lala')
235
 
        self.assertEqual(list(tree.conflicts()), [expected])
236
 
        file('lala', 'wb').write('la')
237
 
        tree.add('lala', 'lala-id')
238
 
        expected = conflicts.ContentsConflict('lala', file_id='lala-id')
239
 
        self.assertEqual(list(tree.conflicts()), [expected])
240
 
        file('lala.THIS', 'wb').write('lathis')
241
 
        file('lala.OTHER', 'wb').write('laother')
242
 
        # When "text conflict"s happen, stem, THIS and OTHER are text
243
 
        expected = conflicts.TextConflict('lala', file_id='lala-id')
244
 
        self.assertEqual(list(tree.conflicts()), [expected])
245
 
        os.unlink('lala.OTHER')
246
 
        os.mkdir('lala.OTHER')
247
 
        expected = conflicts.ContentsConflict('lala', file_id='lala-id')
248
 
        self.assertEqual(list(tree.conflicts()), [expected])
249
 
 
250
 
 
251
 
class TestNonFormatSpecificCode(TestCaseWithTransport):
252
 
    """This class contains tests of workingtree that are not format specific."""
253
 
 
254
 
    
255
 
    def test_gen_file_id(self):
256
 
        gen_file_id = bzrlib.workingtree.gen_file_id
257
 
 
258
 
        # We try to use the filename if possible
259
 
        self.assertStartsWith(gen_file_id('bar'), 'bar-')
260
 
 
261
 
        # but we squash capitalization, and remove non word characters
262
 
        self.assertStartsWith(gen_file_id('Mwoo oof\t m'), 'mwoooofm-')
263
 
 
264
 
        # We also remove leading '.' characters to prevent hidden file-ids
265
 
        self.assertStartsWith(gen_file_id('..gam.py'), 'gam.py-')
266
 
        self.assertStartsWith(gen_file_id('..Mwoo oof\t m'), 'mwoooofm-')
267
 
 
268
 
        # we remove unicode characters, and still don't end up with a 
269
 
        # hidden file id
270
 
        self.assertStartsWith(gen_file_id(u'\xe5\xb5.txt'), 'txt-')
271
 
        
272
 
        # Our current method of generating unique ids adds 33 characters
273
 
        # plus an serial number (log10(N) characters)
274
 
        # to the end of the filename. We now restrict the filename portion to
275
 
        # be <= 20 characters, so the maximum length should now be approx < 60
276
 
 
277
 
        # Test both case squashing and length restriction
278
 
        fid = gen_file_id('A'*50 + '.txt')
279
 
        self.assertStartsWith(fid, 'a'*20 + '-')
280
 
        self.failUnless(len(fid) < 60)
281
 
 
282
 
        # restricting length happens after the other actions, so
283
 
        # we preserve as much as possible
284
 
        fid = gen_file_id('\xe5\xb5..aBcd\tefGhijKLMnop\tqrstuvwxyz')
285
 
        self.assertStartsWith(fid, 'abcdefghijklmnopqrst-')
286
 
        self.failUnless(len(fid) < 60)
287
 
 
288
 
    def test_next_id_suffix(self):
289
 
        bzrlib.workingtree._gen_id_suffix = None
290
 
        bzrlib.workingtree._next_id_suffix()
291
 
        self.assertNotEqual(None, bzrlib.workingtree._gen_id_suffix)
292
 
        bzrlib.workingtree._gen_id_suffix = "foo-"
293
 
        bzrlib.workingtree._gen_id_serial = 1
294
 
        self.assertEqual("foo-2", bzrlib.workingtree._next_id_suffix())
295
 
        self.assertEqual("foo-3", bzrlib.workingtree._next_id_suffix())
296
 
        self.assertEqual("foo-4", bzrlib.workingtree._next_id_suffix())
297
 
        self.assertEqual("foo-5", bzrlib.workingtree._next_id_suffix())
298
 
        self.assertEqual("foo-6", bzrlib.workingtree._next_id_suffix())
299
 
        self.assertEqual("foo-7", bzrlib.workingtree._next_id_suffix())
300
 
        self.assertEqual("foo-8", bzrlib.workingtree._next_id_suffix())
301
 
        self.assertEqual("foo-9", bzrlib.workingtree._next_id_suffix())
302
 
        self.assertEqual("foo-10", bzrlib.workingtree._next_id_suffix())
303
 
 
304
 
    def test__translate_ignore_rule(self):
305
 
        tree = self.make_branch_and_tree('.')
306
 
        # translation should return the regex, the number of groups in it,
307
 
        # and the original rule in a tuple.
308
 
        # there are three sorts of ignore rules:
309
 
        # root only - regex is the rule itself without the leading ./
310
 
        self.assertEqual(
311
 
            "(rootdirrule$)", 
312
 
            tree._translate_ignore_rule("./rootdirrule"))
313
 
        # full path - regex is the rule itself
314
 
        self.assertEqual(
315
 
            "(path\\/to\\/file$)",
316
 
            tree._translate_ignore_rule("path/to/file"))
317
 
        # basename only rule - regex is a rule that ignores everything up
318
 
        # to the last / in the filename
319
 
        self.assertEqual(
320
 
            "((?:.*/)?(?!.*/)basenamerule$)",
321
 
            tree._translate_ignore_rule("basenamerule"))
322
 
 
323
 
    def test__combine_ignore_rules(self):
324
 
        tree = self.make_branch_and_tree('.')
325
 
        # the combined ignore regexs need the outer group indices
326
 
        # placed in a dictionary with the rules that were combined.
327
 
        # an empty set of rules
328
 
        # this is returned as a list of combined regex,rule sets, because
329
 
        # python has a limit of 100 combined regexes.
330
 
        compiled_rules = tree._combine_ignore_rules([])
331
 
        self.assertEqual([], compiled_rules)
332
 
        # one of each type of rule.
333
 
        compiled_rules = tree._combine_ignore_rules(
334
 
            ["rule1", "rule/two", "./three"])[0]
335
 
        # what type *is* the compiled regex to do an isinstance of ?
336
 
        self.assertEqual(3, compiled_rules[0].groups)
337
 
        self.assertEqual(
338
 
            {0:"rule1",1:"rule/two",2:"./three"},
339
 
            compiled_rules[1])
340
 
 
341
 
    def test__combine_ignore_rules_grouping(self):
342
 
        tree = self.make_branch_and_tree('.')
343
 
        # when there are too many rules, the output is split into groups of 100
344
 
        rules = []
345
 
        for index in range(198):
346
 
            rules.append('foo')
347
 
        self.assertEqual(2, len(tree._combine_ignore_rules(rules)))
348
 
 
349
 
    def test__get_ignore_rules_as_regex(self):
350
 
        tree = self.make_branch_and_tree('.')
351
 
        # Setup the default ignore list to be empty
352
 
        ignores._set_user_ignores([])
353
 
 
354
 
        # some plugins (shelf) modifies the DEFAULT_IGNORE list in memory
355
 
        # which causes this test to fail so force the DEFAULT_IGNORE
356
 
        # list to be empty
357
 
        orig_default = bzrlib.DEFAULT_IGNORE
358
 
        # Also make sure the runtime ignore list is empty
359
 
        orig_runtime = ignores._runtime_ignores
360
 
        try:
361
 
            bzrlib.DEFAULT_IGNORE = []
362
 
            ignores._runtime_ignores = set()
363
 
 
364
 
            self.build_tree_contents([('.bzrignore', 'CVS\n.hg\n')])
365
 
            reference_output = tree._combine_ignore_rules(
366
 
                                    set(['CVS', '.hg']))[0]
367
 
            regex_rules = tree._get_ignore_rules_as_regex()[0]
368
 
            self.assertEqual(len(reference_output[1]), regex_rules[0].groups)
369
 
            self.assertEqual(reference_output[1], regex_rules[1])
370
 
        finally:
371
 
            bzrlib.DEFAULT_IGNORE = orig_default
372
 
            ignores._runtime_ignores = orig_runtime
373
 
 
374
 
 
375
 
class InstrumentedTree(object):
376
 
    """A instrumented tree to check the needs_tree_write_lock decorator."""
377
 
 
378
 
    def __init__(self):
379
 
        self._locks = []
380
 
 
381
 
    def lock_tree_write(self):
382
 
        self._locks.append('t')
383
 
 
384
 
    @needs_tree_write_lock
385
 
    def method_with_tree_write_lock(self, *args, **kwargs):
386
 
        """A lock_tree_write decorated method that returns its arguments."""
387
 
        return args, kwargs
388
 
 
389
 
    @needs_tree_write_lock
390
 
    def method_that_raises(self):
391
 
        """This method causes an exception when called with parameters.
392
 
        
393
 
        This allows the decorator code to be checked - it should still call
394
 
        unlock.
395
 
        """
396
 
 
397
 
    def unlock(self):
398
 
        self._locks.append('u')
399
 
 
400
 
 
401
 
class TestInstrumentedTree(TestCase):
402
 
 
403
 
    def test_needs_tree_write_lock(self):
404
 
        """@needs_tree_write_lock should be semantically transparent."""
405
 
        tree = InstrumentedTree()
406
 
        self.assertEqual(
407
 
            'method_with_tree_write_lock',
408
 
            tree.method_with_tree_write_lock.__name__)
409
 
        self.assertEqual(
410
 
            "A lock_tree_write decorated method that returns its arguments.",
411
 
            tree.method_with_tree_write_lock.__doc__)
412
 
        args = (1, 2, 3)
413
 
        kwargs = {'a':'b'}
414
 
        result = tree.method_with_tree_write_lock(1,2,3, a='b')
415
 
        self.assertEqual((args, kwargs), result)
416
 
        self.assertEqual(['t', 'u'], tree._locks)
417
 
        self.assertRaises(TypeError, tree.method_that_raises, 'foo')
418
 
        self.assertEqual(['t', 'u', 't', 'u'], tree._locks)
 
110
        self.assertEqual(None, tree.branch._lock_count)
 
111
 
 
112
    def get_pullable_branches(self):
 
113
        self.build_tree(['from/', 'from/file', 'to/'])
 
114
        br_a = Branch.initialize('from')
 
115
        tree = br_a.working_tree()
 
116
        tree.add('file')
 
117
        tree.commit('foo', rev_id='A')
 
118
        br_b = Branch.initialize('to')
 
119
        return br_a, br_b
 
120
 
 
121
    def test_pull(self):
 
122
        br_a, br_b = self.get_pullable_branches()
 
123
        br_b.working_tree().pull(br_a)
 
124
        self.failUnless(br_b.has_revision('A'))
 
125
        self.assertEqual(['A'], br_b.revision_history())
 
126
 
 
127
    def test_pull_overwrites(self):
 
128
        br_a, br_b = self.get_pullable_branches()
 
129
        br_b.working_tree().commit('foo', rev_id='B')
 
130
        self.assertEqual(['B'], br_b.revision_history())
 
131
        br_b.working_tree().pull(br_a, overwrite=True)
 
132
        self.failUnless(br_b.has_revision('A'))
 
133
        self.failUnless(br_b.has_revision('B'))
 
134
        self.assertEqual(['A'], br_b.revision_history())
 
135
 
 
136
    def test_revert(self):
 
137
        """Test selected-file revert"""
 
138
        b = Branch.initialize('.')
 
139
 
 
140
        self.build_tree(['hello.txt'])
 
141
        file('hello.txt', 'w').write('initial hello')
 
142
 
 
143
        self.assertRaises(NotVersionedError,
 
144
                          b.working_tree().revert, ['hello.txt'])
 
145
        tree = WorkingTree(b.base, b)
 
146
        tree.add(['hello.txt'])
 
147
        tree.commit('create initial hello.txt')
 
148
 
 
149
        self.check_file_contents('hello.txt', 'initial hello')
 
150
        file('hello.txt', 'w').write('new hello')
 
151
        self.check_file_contents('hello.txt', 'new hello')
 
152
 
 
153
        # revert file modified since last revision
 
154
        tree.revert(['hello.txt'])
 
155
        self.check_file_contents('hello.txt', 'initial hello')
 
156
        self.check_file_contents('hello.txt~', 'new hello')
 
157
 
 
158
        # reverting again does not clobber the backup
 
159
        tree.revert(['hello.txt'])
 
160
        self.check_file_contents('hello.txt', 'initial hello')
 
161
        self.check_file_contents('hello.txt~', 'new hello')
 
162
 
 
163
    def test_unknowns(self):
 
164
        b = Branch.initialize('.')
 
165
        tree = WorkingTree('.', b)
 
166
        self.build_tree(['hello.txt',
 
167
                         'hello.txt~'])
 
168
        self.assertEquals(list(tree.unknowns()),
 
169
                          ['hello.txt'])
 
170