~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_workingtree.py

Basic BzrDir support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# (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
20
20
 
21
21
import bzrlib
22
22
from bzrlib.branch import Branch
23
 
import bzrlib.bzrdir as bzrdir
24
 
from bzrlib.bzrdir import BzrDir
25
23
import bzrlib.errors as errors
26
24
from bzrlib.errors import NotBranchError, NotVersionedError
27
 
from bzrlib.osutils import pathjoin, getcwd, has_symlinks
28
25
from bzrlib.tests import TestCaseWithTransport
29
26
from bzrlib.trace import mutter
30
 
from bzrlib.transport import get_transport
31
 
import bzrlib.workingtree as workingtree
 
27
from bzrlib.osutils import pathjoin, getcwd, has_symlinks
32
28
from bzrlib.workingtree import (TreeEntry, TreeDirectory, TreeFile, TreeLink,
33
29
                                WorkingTree)
34
30
 
56
52
        self.assertEqual(TreeLink().kind_character(), '')
57
53
 
58
54
 
59
 
class TestDefaultFormat(TestCaseWithTransport):
60
 
 
61
 
    def test_get_set_default_format(self):
62
 
        old_format = workingtree.WorkingTreeFormat.get_default_format()
63
 
        # default is 3
64
 
        self.assertTrue(isinstance(old_format, workingtree.WorkingTreeFormat3))
65
 
        workingtree.WorkingTreeFormat.set_default_format(SampleTreeFormat())
66
 
        try:
67
 
            # the default branch format is used by the meta dir format
68
 
            # which is not the default bzrdir format at this point
69
 
            dir = bzrdir.BzrDirMetaFormat1().initialize('.')
70
 
            dir.create_repository()
71
 
            dir.create_branch()
72
 
            result = dir.create_workingtree()
73
 
            self.assertEqual(result, 'A tree')
74
 
        finally:
75
 
            workingtree.WorkingTreeFormat.set_default_format(old_format)
76
 
        self.assertEqual(old_format, workingtree.WorkingTreeFormat.get_default_format())
77
 
 
78
 
 
79
 
class SampleTreeFormat(workingtree.WorkingTreeFormat):
80
 
    """A sample format
81
 
 
82
 
    this format is initializable, unsupported to aid in testing the 
83
 
    open and open_downlevel routines.
84
 
    """
85
 
 
86
 
    def get_format_string(self):
87
 
        """See WorkingTreeFormat.get_format_string()."""
88
 
        return "Sample tree format."
89
 
 
90
 
    def initialize(self, a_bzrdir):
91
 
        """Sample branches cannot be created."""
92
 
        t = a_bzrdir.get_workingtree_transport(self)
93
 
        t.put('format', StringIO(self.get_format_string()))
94
 
        return 'A tree'
95
 
 
96
 
    def is_supported(self):
97
 
        return False
98
 
 
99
 
    def open(self, transport, _found=False):
100
 
        return "opened tree."
101
 
 
102
 
 
103
 
class TestWorkingTreeFormat(TestCaseWithTransport):
104
 
    """Tests for the WorkingTreeFormat facility."""
105
 
 
106
 
    def test_find_format(self):
107
 
        # is the right format object found for a working tree?
108
 
        # create a branch with a few known format objects.
109
 
        self.build_tree(["foo/", "bar/"])
110
 
        def check_format(format, url):
111
 
            dir = format._matchingbzrdir.initialize(url)
112
 
            dir.create_repository()
113
 
            dir.create_branch()
114
 
            format.initialize(dir)
115
 
            t = get_transport(url)
116
 
            found_format = workingtree.WorkingTreeFormat.find_format(dir)
117
 
            self.failUnless(isinstance(found_format, format.__class__))
118
 
        check_format(workingtree.WorkingTreeFormat3(), "bar")
119
 
        
120
 
    def test_find_format_no_tree(self):
121
 
        dir = bzrdir.BzrDirMetaFormat1().initialize('.')
122
 
        self.assertRaises(errors.NoWorkingTree,
123
 
                          workingtree.WorkingTreeFormat.find_format,
124
 
                          dir)
125
 
 
126
 
    def test_find_format_unknown_format(self):
127
 
        dir = bzrdir.BzrDirMetaFormat1().initialize('.')
128
 
        dir.create_repository()
129
 
        dir.create_branch()
130
 
        SampleTreeFormat().initialize(dir)
131
 
        self.assertRaises(errors.UnknownFormatError,
132
 
                          workingtree.WorkingTreeFormat.find_format,
133
 
                          dir)
134
 
 
135
 
    def test_register_unregister_format(self):
136
 
        format = SampleTreeFormat()
137
 
        # make a control dir
138
 
        dir = bzrdir.BzrDirMetaFormat1().initialize('.')
139
 
        dir.create_repository()
140
 
        dir.create_branch()
141
 
        # make a branch
142
 
        format.initialize(dir)
143
 
        # register a format for it.
144
 
        workingtree.WorkingTreeFormat.register_format(format)
145
 
        # which branch.Open will refuse (not supported)
146
 
        self.assertRaises(errors.UnsupportedFormatError, workingtree.WorkingTree.open, '.')
147
 
        # but open_downlevel will work
148
 
        self.assertEqual(format.open(dir), workingtree.WorkingTree.open_downlevel('.'))
149
 
        # unregister the format
150
 
        workingtree.WorkingTreeFormat.unregister_format(format)
151
 
 
152
 
 
153
 
class TestWorkingTreeFormat3(TestCaseWithTransport):
154
 
    """Tests specific to WorkingTreeFormat3."""
155
 
 
156
 
    def test_disk_layout(self):
157
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
158
 
        control.create_repository()
159
 
        control.create_branch()
160
 
        tree = workingtree.WorkingTreeFormat3().initialize(control)
161
 
        # we want:
162
 
        # format 'Bazaar-NG Working Tree format 3'
163
 
        # lock ''
164
 
        # inventory = blank inventory
165
 
        # pending-merges = ''
166
 
        # stat-cache = ??
167
 
        # no inventory.basis yet
168
 
        t = control.get_workingtree_transport(None)
169
 
        self.assertEqualDiff('Bazaar-NG Working Tree format 3',
170
 
                             t.get('format').read())
171
 
        self.assertEqualDiff('', t.get('lock').read())
172
 
        self.assertEqualDiff('<inventory format="5">\n'
173
 
                             '</inventory>\n',
174
 
                             t.get('inventory').read())
175
 
        self.assertEqualDiff('### bzr hashcache v5\n',
176
 
                             t.get('stat-cache').read())
177
 
        self.assertFalse(t.has('inventory.basis'))
178
 
        # no last-revision file means 'None' or 'NULLREVISION'
179
 
        self.assertFalse(t.has('last-revision'))
180
 
        # TODO RBC 20060210 do a commit, check the inventory.basis is created 
181
 
        # correctly and last-revision file becomes present.
 
55
class TestWorkingTree(TestCaseWithTransport):
 
56
 
 
57
    def test_listfiles(self):
 
58
        tree = WorkingTree.create_standalone('.')
 
59
        os.mkdir('dir')
 
60
        print >> open('file', 'w'), "content"
 
61
        if has_symlinks():
 
62
            os.symlink('target', 'symlink')
 
63
        files = list(tree.list_files())
 
64
        self.assertEqual(files[0], ('dir', '?', 'directory', None, TreeDirectory()))
 
65
        self.assertEqual(files[1], ('file', '?', 'file', None, TreeFile()))
 
66
        if has_symlinks():
 
67
            self.assertEqual(files[2], ('symlink', '?', 'symlink', None, TreeLink()))
 
68
 
 
69
    def test_open_containing(self):
 
70
        branch = WorkingTree.create_standalone('.').branch
 
71
        wt, relpath = WorkingTree.open_containing()
 
72
        self.assertEqual('', relpath)
 
73
        self.assertEqual(wt.basedir + '/', branch.base)
 
74
        wt, relpath = WorkingTree.open_containing(u'.')
 
75
        self.assertEqual('', relpath)
 
76
        self.assertEqual(wt.basedir + '/', branch.base)
 
77
        wt, relpath = WorkingTree.open_containing('./foo')
 
78
        self.assertEqual('foo', relpath)
 
79
        self.assertEqual(wt.basedir + '/', branch.base)
 
80
        # paths that are urls are just plain wrong for working trees.
 
81
        self.assertRaises(NotBranchError,
 
82
                          WorkingTree.open_containing, 
 
83
                          'file:///' + getcwd())
 
84
 
 
85
    def test_construct_with_branch(self):
 
86
        branch = WorkingTree.create_standalone('.').branch
 
87
        tree = WorkingTree(branch.base, branch)
 
88
        self.assertEqual(branch, tree.branch)
 
89
        self.assertEqual(branch.base, tree.basedir + '/')
 
90
    
 
91
    def test_construct_without_branch(self):
 
92
        branch = WorkingTree.create_standalone('.').branch
 
93
        tree = WorkingTree(branch.base)
 
94
        self.assertEqual(branch.base, tree.branch.base)
 
95
        self.assertEqual(branch.base, tree.basedir + '/')
 
96
 
 
97
    def test_basic_relpath(self):
 
98
        # for comprehensive relpath tests, see whitebox.py.
 
99
        tree = WorkingTree.create_standalone('.')
 
100
        self.assertEqual('child',
 
101
                         tree.relpath(pathjoin(getcwd(), 'child')))
 
102
 
 
103
    def test_lock_locks_branch(self):
 
104
        tree = WorkingTree.create_standalone('.')
 
105
        tree.lock_read()
 
106
        self.assertEqual('r', tree.branch.peek_lock_mode())
 
107
        tree.unlock()
 
108
        self.assertEqual(None, tree.branch.peek_lock_mode())
 
109
        tree.lock_write()
 
110
        self.assertEqual('w', tree.branch.peek_lock_mode())
 
111
        tree.unlock()
 
112
        self.assertEqual(None, tree.branch.peek_lock_mode())
 
113
 
 
114
    def get_pullable_trees(self):
 
115
        self.build_tree(['from/', 'from/file', 'to/'])
 
116
        tree = WorkingTree.create_standalone('from')
 
117
        tree.add('file')
 
118
        tree.commit('foo', rev_id='A')
 
119
        tree_b = WorkingTree.create_standalone('to')
 
120
        return tree, tree_b
 
121
 
 
122
    def test_pull(self):
 
123
        tree_a, tree_b = self.get_pullable_trees()
 
124
        tree_b.pull(tree_a.branch)
 
125
        self.failUnless(tree_b.branch.repository.has_revision('A'))
 
126
        self.assertEqual(['A'], tree_b.branch.revision_history())
 
127
 
 
128
    def test_pull_overwrites(self):
 
129
        tree_a, tree_b = self.get_pullable_trees()
 
130
        tree_b.commit('foo', rev_id='B')
 
131
        self.assertEqual(['B'], tree_b.branch.revision_history())
 
132
        tree_b.pull(tree_a.branch, overwrite=True)
 
133
        self.failUnless(tree_b.branch.repository.has_revision('A'))
 
134
        self.failUnless(tree_b.branch.repository.has_revision('B'))
 
135
        self.assertEqual(['A'], tree_b.branch.revision_history())
 
136
 
 
137
    def test_revert(self):
 
138
        """Test selected-file revert"""
 
139
        tree = WorkingTree.create_standalone('.')
 
140
 
 
141
        self.build_tree(['hello.txt'])
 
142
        file('hello.txt', 'w').write('initial hello')
 
143
 
 
144
        self.assertRaises(NotVersionedError,
 
145
                          tree.revert, ['hello.txt'])
 
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
        tree = WorkingTree.create_standalone('.')
 
165
        self.build_tree(['hello.txt',
 
166
                         'hello.txt~'])
 
167
        self.assertEquals(list(tree.unknowns()),
 
168
                          ['hello.txt'])
 
169
 
 
170
    def test_hashcache(self):
 
171
        from bzrlib.tests.test_hashcache import pause
 
172
        tree = WorkingTree.create_standalone('.')
 
173
        self.build_tree(['hello.txt',
 
174
                         'hello.txt~'])
 
175
        tree.add('hello.txt')
 
176
        pause()
 
177
        sha = tree.get_file_sha1(tree.path2id('hello.txt'))
 
178
        self.assertEqual(1, tree._hashcache.miss_count)
 
179
        tree2 = WorkingTree('.', tree.branch)
 
180
        sha2 = tree2.get_file_sha1(tree2.path2id('hello.txt'))
 
181
        self.assertEqual(0, tree2._hashcache.miss_count)
 
182
        self.assertEqual(1, tree2._hashcache.hit_count)
 
183
 
 
184
    def test_checkout(self):
 
185
        # at this point as we dont have checkout versions, checkout simply
 
186
        # populates the required files for a working tree at the dir.
 
187
        self.build_tree(['branch/'])
 
188
        b = Branch.create('branch')
 
189
        t = WorkingTree.create(b, 'tree')
 
190
        # as we are moving the ownership to working tree, we will check here
 
191
        # that its split out correctly
 
192
        self.failIfExists('branch/.bzr/inventory')
 
193
        self.failIfExists('branch/.bzr/pending-merges')
 
194
        sio = StringIO()
 
195
        bzrlib.xml5.serializer_v5.write_inventory(bzrlib.inventory.Inventory(),
 
196
                                                  sio)
 
197
        self.assertFileEqual(sio.getvalue(), 'tree/.bzr/inventory')
 
198
        self.assertFileEqual('', 'tree/.bzr/pending-merges')
 
199
 
 
200
    def test_initialize(self):
 
201
        # initialize should create a working tree and branch in an existing dir
 
202
        t = WorkingTree.create_standalone('.')
 
203
        b = Branch.open('.')
 
204
        self.assertEqual(t.branch.base, b.base)
 
205
        t2 = WorkingTree('.')
 
206
        self.assertEqual(t.basedir, t2.basedir)
 
207
        self.assertEqual(b.base, t2.branch.base)
 
208
        # TODO maybe we should check the branch format? not sure if its
 
209
        # appropriate here.
 
210
 
 
211
    def test_rename_dirs(self):
 
212
        """Test renaming directories and the files within them."""
 
213
        wt = self.make_branch_and_tree('.')
 
214
        b = wt.branch
 
215
        self.build_tree(['dir/', 'dir/sub/', 'dir/sub/file'])
 
216
        wt.add(['dir', 'dir/sub', 'dir/sub/file'])
 
217
 
 
218
        wt.commit('create initial state')
 
219
 
 
220
        revid = b.revision_history()[0]
 
221
        self.log('first revision_id is {%s}' % revid)
 
222
        
 
223
        inv = b.repository.get_revision_inventory(revid)
 
224
        self.log('contents of inventory: %r' % inv.entries())
 
225
 
 
226
        self.check_inventory_shape(inv,
 
227
                                   ['dir', 'dir/sub', 'dir/sub/file'])
 
228
 
 
229
        wt.rename_one('dir', 'newdir')
 
230
 
 
231
        self.check_inventory_shape(wt.read_working_inventory(),
 
232
                                   ['newdir', 'newdir/sub', 'newdir/sub/file'])
 
233
 
 
234
        wt.rename_one('newdir/sub', 'newdir/newsub')
 
235
        self.check_inventory_shape(wt.read_working_inventory(),
 
236
                                   ['newdir', 'newdir/newsub',
 
237
                                    'newdir/newsub/file'])
 
238
 
 
239
    def test_add_in_unversioned(self):
 
240
        """Try to add a file in an unversioned directory.
 
241
 
 
242
        "bzr add" adds the parent as necessary, but simple working tree add
 
243
        doesn't do that.
 
244
        """
 
245
        from bzrlib.errors import NotVersionedError
 
246
        wt = self.make_branch_and_tree('.')
 
247
        self.build_tree(['foo/',
 
248
                         'foo/hello'])
 
249
        self.assertRaises(NotVersionedError,
 
250
                          wt.add,
 
251
                          'foo/hello')
 
252
 
 
253
    def test_add_missing(self):
 
254
        # adding a msising file -> NoSuchFile
 
255
        wt = self.make_branch_and_tree('.')
 
256
        self.assertRaises(errors.NoSuchFile, wt.add, 'fpp')
 
257
 
 
258
    def test_remove_verbose(self):
 
259
        #FIXME the remove api should not print or otherwise depend on the
 
260
        # text UI - RBC 20060124
 
261
        wt = self.make_branch_and_tree('.')
 
262
        self.build_tree(['hello'])
 
263
        wt.add(['hello'])
 
264
        wt.commit(message='add hello')
 
265
        stdout = StringIO()
 
266
        stderr = StringIO()
 
267
        self.assertEqual(None, self.apply_redirected(None, stdout, stderr,
 
268
                                                     wt.remove,
 
269
                                                     ['hello'],
 
270
                                                     verbose=True))
 
271
        self.assertEqual('?       hello\n', stdout.getvalue())
 
272
        self.assertEqual('', stderr.getvalue())
 
273
 
 
274
    def test_clone_trivial(self):
 
275
        wt = self.make_branch_and_tree('source')
 
276
        cloned = wt.clone('target')
 
277
        self.assertEqual(cloned.last_revision(), wt.last_revision())
 
278
 
 
279
    def test_last_revision(self):
 
280
        wt = self.make_branch_and_tree('source')
 
281
        self.assertEqual(None, wt.last_revision())
 
282
        wt.commit('A', allow_pointless=True, rev_id='A')
 
283
        self.assertEqual('A', wt.last_revision())
 
284
 
 
285
    def test_set_last_revision(self):
 
286
        wt = self.make_branch_and_tree('source')
 
287
        self.assertEqual(None, wt.last_revision())
 
288
        # cannot set the last revision to one not in the branch
 
289
        self.assertRaises(errors.NoSuchRevision, wt.set_last_revision, 'A')
 
290
        wt.commit('A', allow_pointless=True, rev_id='A')
 
291
        self.assertEqual('A', wt.last_revision())
 
292
        # None is aways in the branch
 
293
        wt.set_last_revision(None)
 
294
        self.assertEqual(None, wt.last_revision())
 
295
        # and now we can set it to 'A'
 
296
        # because the current format mutates the branch to set it,
 
297
        # we need to alter the branch to let this pass.
 
298
        wt.branch.set_revision_history(['A', 'B'])
 
299
        wt.set_last_revision('A')
 
300
        self.assertEqual('A', wt.last_revision())
 
301
 
 
302
    def test_clone_and_commit_preserves_last_revision(self):
 
303
        wt = self.make_branch_and_tree('source')
 
304
        cloned = wt.clone('target')
 
305
        wt.commit('A', allow_pointless=True, rev_id='A')
 
306
        self.assertNotEqual(cloned.last_revision(), wt.last_revision())
 
307
        
 
308
    def test_basis_tree_returns_last_revision(self):
 
309
        wt = self.make_branch_and_tree('.')
 
310
        self.build_tree(['foo'])
 
311
        wt.add('foo', 'foo-id')
 
312
        wt.commit('A', rev_id='A')
 
313
        wt.rename_one('foo', 'bar')
 
314
        wt.commit('B', rev_id='B')
 
315
        wt.set_last_revision('B')
 
316
        tree = wt.basis_tree()
 
317
        self.failUnless(tree.has_filename('bar'))
 
318
        wt.set_last_revision('A')
 
319
        tree = wt.basis_tree()
 
320
        self.failUnless(tree.has_filename('foo'))