~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_workingtree.py

  • Committer: Martin Pool
  • Date: 2005-09-30 05:56:05 UTC
  • mto: (1185.14.2)
  • mto: This revision was merged to the branch mainline in revision 1396.
  • Revision ID: mbp@sourcefrog.net-20050930055605-a2c534529b392a7d
- fix upgrade for transport changes

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
2
 
# Authors:  Robert Collins <robert.collins@canonical.com>
3
 
#
4
 
# This program is free software; you can redistribute it and/or modify
5
 
# it under the terms of the GNU General Public License as published by
6
 
# the Free Software Foundation; either version 2 of the License, or
7
 
# (at your option) any later version.
8
 
#
9
 
# This program is distributed in the hope that it will be useful,
10
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
# GNU General Public License for more details.
13
 
#
14
 
# You should have received a copy of the GNU General Public License
15
 
# along with this program; if not, write to the Free Software
16
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
 
 
18
 
from cStringIO import StringIO
19
 
import os
20
 
 
21
 
from bzrlib import dirstate, ignores
22
 
import bzrlib
23
 
from bzrlib.branch import Branch
24
 
from bzrlib import bzrdir, conflicts, errors, workingtree, workingtree_4
25
 
from bzrlib.bzrdir import BzrDir
26
 
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.symbol_versioning import zero_thirteen
31
 
from bzrlib.tests import TestCase, TestCaseWithTransport, TestSkipped
32
 
from bzrlib.trace import mutter
33
 
from bzrlib.transport import get_transport
34
 
from bzrlib.tree import InterTree
35
 
from bzrlib.workingtree import (
36
 
    TreeEntry,
37
 
    TreeDirectory,
38
 
    TreeFile,
39
 
    TreeLink,
40
 
    WorkingTree,
41
 
    )
42
 
 
43
 
class TestTreeDirectory(TestCaseWithTransport):
44
 
 
45
 
    def test_kind_character(self):
46
 
        self.assertEqual(TreeDirectory().kind_character(), '/')
47
 
 
48
 
 
49
 
class TestTreeEntry(TestCaseWithTransport):
50
 
 
51
 
    def test_kind_character(self):
52
 
        self.assertEqual(TreeEntry().kind_character(), '???')
53
 
 
54
 
 
55
 
class TestTreeFile(TestCaseWithTransport):
56
 
 
57
 
    def test_kind_character(self):
58
 
        self.assertEqual(TreeFile().kind_character(), '')
59
 
 
60
 
 
61
 
class TestTreeLink(TestCaseWithTransport):
62
 
 
63
 
    def test_kind_character(self):
64
 
        self.assertEqual(TreeLink().kind_character(), '')
65
 
 
66
 
 
67
 
class TestDefaultFormat(TestCaseWithTransport):
68
 
 
69
 
    def test_get_set_default_format(self):
70
 
        old_format = workingtree.WorkingTreeFormat.get_default_format()
71
 
        # default is 3
72
 
        self.assertTrue(isinstance(old_format, workingtree.WorkingTreeFormat3))
73
 
        workingtree.WorkingTreeFormat.set_default_format(SampleTreeFormat())
74
 
        try:
75
 
            # the default branch format is used by the meta dir format
76
 
            # which is not the default bzrdir format at this point
77
 
            dir = bzrdir.BzrDirMetaFormat1().initialize('.')
78
 
            dir.create_repository()
79
 
            dir.create_branch()
80
 
            result = dir.create_workingtree()
81
 
            self.assertEqual(result, 'A tree')
82
 
        finally:
83
 
            workingtree.WorkingTreeFormat.set_default_format(old_format)
84
 
        self.assertEqual(old_format, workingtree.WorkingTreeFormat.get_default_format())
85
 
 
86
 
 
87
 
class SampleTreeFormat(workingtree.WorkingTreeFormat):
88
 
    """A sample format
89
 
 
90
 
    this format is initializable, unsupported to aid in testing the 
91
 
    open and open_downlevel routines.
92
 
    """
93
 
 
94
 
    def get_format_string(self):
95
 
        """See WorkingTreeFormat.get_format_string()."""
96
 
        return "Sample tree format."
97
 
 
98
 
    def initialize(self, a_bzrdir, revision_id=None):
99
 
        """Sample branches cannot be created."""
100
 
        t = a_bzrdir.get_workingtree_transport(self)
101
 
        t.put_bytes('format', self.get_format_string())
102
 
        return 'A tree'
103
 
 
104
 
    def is_supported(self):
105
 
        return False
106
 
 
107
 
    def open(self, transport, _found=False):
108
 
        return "opened tree."
109
 
 
110
 
 
111
 
class TestWorkingTreeFormat(TestCaseWithTransport):
112
 
    """Tests for the WorkingTreeFormat facility."""
113
 
 
114
 
    def test_find_format(self):
115
 
        # is the right format object found for a working tree?
116
 
        # create a branch with a few known format objects.
117
 
        self.build_tree(["foo/", "bar/"])
118
 
        def check_format(format, url):
119
 
            dir = format._matchingbzrdir.initialize(url)
120
 
            dir.create_repository()
121
 
            dir.create_branch()
122
 
            format.initialize(dir)
123
 
            t = get_transport(url)
124
 
            found_format = workingtree.WorkingTreeFormat.find_format(dir)
125
 
            self.failUnless(isinstance(found_format, format.__class__))
126
 
        check_format(workingtree.WorkingTreeFormat3(), "bar")
127
 
        
128
 
    def test_find_format_no_tree(self):
129
 
        dir = bzrdir.BzrDirMetaFormat1().initialize('.')
130
 
        self.assertRaises(errors.NoWorkingTree,
131
 
                          workingtree.WorkingTreeFormat.find_format,
132
 
                          dir)
133
 
 
134
 
    def test_find_format_unknown_format(self):
135
 
        dir = bzrdir.BzrDirMetaFormat1().initialize('.')
136
 
        dir.create_repository()
137
 
        dir.create_branch()
138
 
        SampleTreeFormat().initialize(dir)
139
 
        self.assertRaises(errors.UnknownFormatError,
140
 
                          workingtree.WorkingTreeFormat.find_format,
141
 
                          dir)
142
 
 
143
 
    def test_register_unregister_format(self):
144
 
        format = SampleTreeFormat()
145
 
        # make a control dir
146
 
        dir = bzrdir.BzrDirMetaFormat1().initialize('.')
147
 
        dir.create_repository()
148
 
        dir.create_branch()
149
 
        # make a branch
150
 
        format.initialize(dir)
151
 
        # register a format for it.
152
 
        workingtree.WorkingTreeFormat.register_format(format)
153
 
        # which branch.Open will refuse (not supported)
154
 
        self.assertRaises(errors.UnsupportedFormatError, workingtree.WorkingTree.open, '.')
155
 
        # but open_downlevel will work
156
 
        self.assertEqual(format.open(dir), workingtree.WorkingTree.open_downlevel('.'))
157
 
        # unregister the format
158
 
        workingtree.WorkingTreeFormat.unregister_format(format)
159
 
 
160
 
 
161
 
class TestWorkingTreeFormat3(TestCaseWithTransport):
162
 
    """Tests specific to WorkingTreeFormat3."""
163
 
 
164
 
    def test_disk_layout(self):
165
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
166
 
        control.create_repository()
167
 
        control.create_branch()
168
 
        tree = workingtree.WorkingTreeFormat3().initialize(control)
169
 
        # we want:
170
 
        # format 'Bazaar-NG Working Tree format 3'
171
 
        # inventory = blank inventory
172
 
        # pending-merges = ''
173
 
        # stat-cache = ??
174
 
        # no inventory.basis yet
175
 
        t = control.get_workingtree_transport(None)
176
 
        self.assertEqualDiff('Bazaar-NG Working Tree format 3',
177
 
                             t.get('format').read())
178
 
        # self.assertContainsRe(t.get('inventory').read(), 
179
 
        #                       '<inventory file_id="[^"]*" format="5">\n'
180
 
        #                       '</inventory>\n',
181
 
        #                      )
182
 
        # WorkingTreeFormat3 doesn't default to creating a unique root id,
183
 
        # because it is incompatible with older bzr versions
184
 
        self.assertContainsRe(t.get('inventory').read(),
185
 
                              '<inventory format="5">\n'
186
 
                              '</inventory>\n',
187
 
                             )
188
 
        self.assertEqualDiff('### bzr hashcache v5\n',
189
 
                             t.get('stat-cache').read())
190
 
        self.assertFalse(t.has('inventory.basis'))
191
 
        # no last-revision file means 'None' or 'NULLREVISION'
192
 
        self.assertFalse(t.has('last-revision'))
193
 
        # TODO RBC 20060210 do a commit, check the inventory.basis is created 
194
 
        # correctly and last-revision file becomes present.
195
 
 
196
 
    def test_uses_lockdir(self):
197
 
        """WorkingTreeFormat3 uses its own LockDir:
198
 
            
199
 
            - lock is a directory
200
 
            - when the WorkingTree is locked, LockDir can see that
201
 
        """
202
 
        t = self.get_transport()
203
 
        url = self.get_url()
204
 
        dir = bzrdir.BzrDirMetaFormat1().initialize(url)
205
 
        repo = dir.create_repository()
206
 
        branch = dir.create_branch()
207
 
        try:
208
 
            tree = workingtree.WorkingTreeFormat3().initialize(dir)
209
 
        except errors.NotLocalUrl:
210
 
            raise TestSkipped('Not a local URL')
211
 
        self.assertIsDirectory('.bzr', t)
212
 
        self.assertIsDirectory('.bzr/checkout', t)
213
 
        self.assertIsDirectory('.bzr/checkout/lock', t)
214
 
        our_lock = LockDir(t, '.bzr/checkout/lock')
215
 
        self.assertEquals(our_lock.peek(), None)
216
 
        tree.lock_write()
217
 
        self.assertTrue(our_lock.peek())
218
 
        tree.unlock()
219
 
        self.assertEquals(our_lock.peek(), None)
220
 
 
221
 
    def test_missing_pending_merges(self):
222
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
223
 
        control.create_repository()
224
 
        control.create_branch()
225
 
        tree = workingtree.WorkingTreeFormat3().initialize(control)
226
 
        tree._control_files._transport.delete("pending-merges")
227
 
        self.assertEqual([], tree.get_parent_ids())
228
 
 
229
 
 
230
 
class TestWorkingTreeFormat4(TestCaseWithTransport):
231
 
    """Tests specific to WorkingTreeFormat4."""
232
 
 
233
 
    def test_disk_layout(self):
234
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
235
 
        control.create_repository()
236
 
        control.create_branch()
237
 
        tree = workingtree_4.WorkingTreeFormat4().initialize(control)
238
 
        # we want:
239
 
        # format 'Bazaar Working Tree format 4'
240
 
        # stat-cache = ??
241
 
        t = control.get_workingtree_transport(None)
242
 
        self.assertEqualDiff('Bazaar Working Tree format 4\n',
243
 
                             t.get('format').read())
244
 
        self.assertEqualDiff('### bzr hashcache v5\n',
245
 
                             t.get('stat-cache').read())
246
 
        self.assertFalse(t.has('inventory.basis'))
247
 
        # no last-revision file means 'None' or 'NULLREVISION'
248
 
        self.assertFalse(t.has('last-revision'))
249
 
        # TODO RBC 20060210 do a commit, check the inventory.basis is created 
250
 
        # correctly and last-revision file becomes present.
251
 
        # manually make a dirstate toc check the format is as desired.
252
 
        state = dirstate.DirState.on_file(t.local_abspath('dirstate'))
253
 
        self.assertEqual([], state.get_parent_ids())
254
 
 
255
 
    def test_uses_lockdir(self):
256
 
        """WorkingTreeFormat4 uses its own LockDir:
257
 
            
258
 
            - lock is a directory
259
 
            - when the WorkingTree is locked, LockDir can see that
260
 
        """
261
 
        # this test could be factored into a subclass of tests common to both
262
 
        # format 3 and 4, but for now its not much of an issue as there is only one in common.
263
 
        t = self.get_transport()
264
 
        tree = self.make_workingtree()
265
 
        self.assertIsDirectory('.bzr', t)
266
 
        self.assertIsDirectory('.bzr/checkout', t)
267
 
        self.assertIsDirectory('.bzr/checkout/lock', t)
268
 
        our_lock = LockDir(t, '.bzr/checkout/lock')
269
 
        self.assertEquals(our_lock.peek(), None)
270
 
        tree.lock_write()
271
 
        self.assertTrue(our_lock.peek())
272
 
        tree.unlock()
273
 
        self.assertEquals(our_lock.peek(), None)
274
 
 
275
 
    def make_workingtree(self, relpath=''):
276
 
        url = self.get_url(relpath)
277
 
        if relpath:
278
 
            self.build_tree([relpath + '/'])
279
 
        dir = bzrdir.BzrDirMetaFormat1().initialize(url)
280
 
        repo = dir.create_repository()
281
 
        branch = dir.create_branch()
282
 
        try:
283
 
            return workingtree_4.WorkingTreeFormat4().initialize(dir)
284
 
        except errors.NotLocalUrl:
285
 
            raise TestSkipped('Not a local URL')
286
 
 
287
 
    # TODO: test that dirstate also stores & retrieves the parent list of 
288
 
    # workingtree-parent revisions, including when they have multiple parents.
289
 
    # (in other words, the case when we're constructing a merge of 
290
 
    # revisions which are themselves merges.)
291
 
 
292
 
    # The simplest case is that the the workingtree's primary 
293
 
    # parent tree can be retrieved.  This is required for all WorkingTrees, 
294
 
    # and covered by the generic tests.
295
 
 
296
 
    def test_dirstate_stores_all_parent_inventories(self):
297
 
        tree = self.make_workingtree()
298
 
 
299
 
        # We're going to build in tree a working tree 
300
 
        # with three parent trees, with some files in common.  
301
 
    
302
 
        # We really don't want to do commit or merge in the new dirstate-based
303
 
        # tree, because that might not work yet.  So instead we build
304
 
        # revisions elsewhere and pull them across, doing by hand part of the
305
 
        # work that merge would do.
306
 
 
307
 
        subtree = self.make_branch_and_tree('subdir')
308
 
        # writelock the tree so its repository doesn't get readlocked by
309
 
        # the revision tree locks. This works around the bug where we dont
310
 
        # permit lock upgrading.
311
 
        subtree.lock_write()
312
 
        self.addCleanup(subtree.unlock)
313
 
        self.build_tree(['subdir/file-a',])
314
 
        subtree.add(['file-a'], ['id-a'])
315
 
        rev1 = subtree.commit('commit in subdir')
316
 
 
317
 
        subtree2 = subtree.bzrdir.sprout('subdir2').open_workingtree()
318
 
        self.build_tree(['subdir2/file-b'])
319
 
        subtree2.add(['file-b'], ['id-b'])
320
 
        rev2 = subtree2.commit('commit in subdir2')
321
 
 
322
 
        subtree.flush()
323
 
        subtree.merge_from_branch(subtree2.branch)
324
 
        rev3 = subtree.commit('merge from subdir2')
325
 
 
326
 
        repo = tree.branch.repository
327
 
        repo.fetch(subtree.branch.repository, rev3)
328
 
        # will also pull the others...
329
 
 
330
 
        # create repository based revision trees
331
 
        rev1_revtree = subtree.branch.repository.revision_tree(rev1)
332
 
        rev2_revtree = subtree2.branch.repository.revision_tree(rev2)
333
 
        rev3_revtree = subtree.branch.repository.revision_tree(rev3)
334
 
        # tree doesn't contain a text merge yet but we'll just
335
 
        # set the parents as if a merge had taken place. 
336
 
        # this should cause the tree data to be folded into the 
337
 
        # dirstate.
338
 
        tree.set_parent_trees([
339
 
            (rev1, rev1_revtree),
340
 
            (rev2, rev2_revtree),
341
 
            (rev3, rev3_revtree), ])
342
 
 
343
 
        # create tree-sourced revision trees
344
 
        rev1_tree = tree.revision_tree(rev1)
345
 
        rev1_tree.lock_read()
346
 
        self.addCleanup(rev1_tree.unlock)
347
 
        rev2_tree = tree.revision_tree(rev2)
348
 
        rev2_tree.lock_read()
349
 
        self.addCleanup(rev2_tree.unlock)
350
 
        rev3_tree = tree.revision_tree(rev3)
351
 
        rev3_tree.lock_read()
352
 
        self.addCleanup(rev3_tree.unlock)
353
 
 
354
 
        # now we should be able to get them back out
355
 
        self.assertTreesEqual(rev1_revtree, rev1_tree)
356
 
        self.assertTreesEqual(rev2_revtree, rev2_tree)
357
 
        self.assertTreesEqual(rev3_revtree, rev3_tree)
358
 
 
359
 
    def test_dirstate_doesnt_read_parents_from_repo_when_setting(self):
360
 
        """Setting parent trees on a dirstate working tree takes
361
 
        the trees it's given and doesn't need to read them from the 
362
 
        repository.
363
 
        """
364
 
        tree = self.make_workingtree()
365
 
 
366
 
        subtree = self.make_branch_and_tree('subdir')
367
 
        rev1 = subtree.commit('commit in subdir')
368
 
        rev1_tree = subtree.basis_tree()
369
 
        rev1_tree.lock_read()
370
 
        self.addCleanup(rev1_tree.unlock)
371
 
 
372
 
        tree.branch.pull(subtree.branch)
373
 
 
374
 
        # break the repository's legs to make sure it only uses the trees
375
 
        # it's given; any calls to forbidden methods will raise an 
376
 
        # AssertionError
377
 
        repo = tree.branch.repository
378
 
        repo.get_revision = self.fail
379
 
        repo.get_inventory = self.fail
380
 
        repo.get_inventory_xml = self.fail
381
 
        # try to set the parent trees.
382
 
        tree.set_parent_trees([(rev1, rev1_tree)])
383
 
 
384
 
    def test_dirstate_doesnt_read_from_repo_when_returning_cache_tree(self):
385
 
        """Getting parent trees from a dirstate tree does not read from the 
386
 
        repos inventory store. This is an important part of the dirstate
387
 
        performance optimisation work.
388
 
        """
389
 
        tree = self.make_workingtree()
390
 
 
391
 
        subtree = self.make_branch_and_tree('subdir')
392
 
        # writelock the tree so its repository doesn't get readlocked by
393
 
        # the revision tree locks. This works around the bug where we dont
394
 
        # permit lock upgrading.
395
 
        subtree.lock_write()
396
 
        self.addCleanup(subtree.unlock)
397
 
        rev1 = subtree.commit('commit in subdir')
398
 
        rev1_tree = subtree.basis_tree()
399
 
        rev1_tree.lock_read()
400
 
        rev1_tree.inventory
401
 
        self.addCleanup(rev1_tree.unlock)
402
 
        rev2 = subtree.commit('second commit in subdir', allow_pointless=True)
403
 
        rev2_tree = subtree.basis_tree()
404
 
        rev2_tree.lock_read()
405
 
        rev2_tree.inventory
406
 
        self.addCleanup(rev2_tree.unlock)
407
 
 
408
 
        tree.branch.pull(subtree.branch)
409
 
 
410
 
        # break the repository's legs to make sure it only uses the trees
411
 
        # it's given; any calls to forbidden methods will raise an 
412
 
        # AssertionError
413
 
        repo = tree.branch.repository
414
 
        # dont uncomment this: the revision object must be accessed to 
415
 
        # answer 'get_parent_ids' for the revision tree- dirstate does not 
416
 
        # cache the parents of a parent tree at this point.
417
 
        #repo.get_revision = self.fail
418
 
        repo.get_inventory = self.fail
419
 
        repo.get_inventory_xml = self.fail
420
 
        # set the parent trees.
421
 
        tree.set_parent_trees([(rev1, rev1_tree), (rev2, rev2_tree)])
422
 
        # read the first tree
423
 
        result_rev1_tree = tree.revision_tree(rev1)
424
 
        # read the second
425
 
        result_rev2_tree = tree.revision_tree(rev2)
426
 
        # compare - there should be no differences between the handed and 
427
 
        # returned trees
428
 
        self.assertTreesEqual(rev1_tree, result_rev1_tree)
429
 
        self.assertTreesEqual(rev2_tree, result_rev2_tree)
430
 
 
431
 
    def test_dirstate_doesnt_cache_non_parent_trees(self):
432
 
        """Getting parent trees from a dirstate tree does not read from the 
433
 
        repos inventory store. This is an important part of the dirstate
434
 
        performance optimisation work.
435
 
        """
436
 
        tree = self.make_workingtree()
437
 
 
438
 
        # make a tree that we can try for, which is able to be returned but
439
 
        # must not be
440
 
        subtree = self.make_branch_and_tree('subdir')
441
 
        rev1 = subtree.commit('commit in subdir')
442
 
        tree.branch.pull(subtree.branch)
443
 
        # check it fails
444
 
        self.assertRaises(errors.NoSuchRevision, tree.revision_tree, rev1)
445
 
 
446
 
    def test_no_dirstate_outside_lock(self):
447
 
        # temporary test until the code is mature enough to test from outside.
448
 
        """Getting a dirstate object fails if there is no lock."""
449
 
        def lock_and_call_current_dirstate(tree, lock_method):
450
 
            getattr(tree, lock_method)()
451
 
            tree.current_dirstate()
452
 
            tree.unlock()
453
 
        tree = self.make_workingtree()
454
 
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
455
 
        lock_and_call_current_dirstate(tree, 'lock_read')
456
 
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
457
 
        lock_and_call_current_dirstate(tree, 'lock_write')
458
 
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
459
 
        lock_and_call_current_dirstate(tree, 'lock_tree_write')
460
 
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
461
 
 
462
 
    def test_new_dirstate_on_new_lock(self):
463
 
        # until we have detection for when a dirstate can be reused, we
464
 
        # want to reparse dirstate on every new lock.
465
 
        known_dirstates = set()
466
 
        def lock_and_compare_all_current_dirstate(tree, lock_method):
467
 
            getattr(tree, lock_method)()
468
 
            state = tree.current_dirstate()
469
 
            self.assertFalse(state in known_dirstates)
470
 
            known_dirstates.add(state)
471
 
            tree.unlock()
472
 
        tree = self.make_workingtree()
473
 
        # lock twice with each type to prevent silly per-lock-type bugs.
474
 
        # each lock and compare looks for a unique state object.
475
 
        lock_and_compare_all_current_dirstate(tree, 'lock_read')
476
 
        lock_and_compare_all_current_dirstate(tree, 'lock_read')
477
 
        lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
478
 
        lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
479
 
        lock_and_compare_all_current_dirstate(tree, 'lock_write')
480
 
        lock_and_compare_all_current_dirstate(tree, 'lock_write')
481
 
 
482
 
    def test_revtree_to_revtree_not_interdirstate(self):
483
 
        # we should not get a dirstate optimiser for two repository sourced
484
 
        # revtrees. we can't prove a negative, so we dont do exhaustive tests
485
 
        # of all formats; though that could be written in the future it doesn't
486
 
        # seem well worth it.
487
 
        tree = self.make_workingtree()
488
 
        rev_id = tree.commit('first post')
489
 
        rev_id2 = tree.commit('second post')
490
 
        rev_tree = tree.branch.repository.revision_tree(rev_id)
491
 
        rev_tree2 = tree.branch.repository.revision_tree(rev_id2)
492
 
        optimiser = InterTree.get(rev_tree, rev_tree2)
493
 
        self.assertIsInstance(optimiser, InterTree)
494
 
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
495
 
        optimiser = InterTree.get(rev_tree2, rev_tree)
496
 
        self.assertIsInstance(optimiser, InterTree)
497
 
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
498
 
 
499
 
    def test_revtree_not_in_dirstate_to_dirstate_not_interdirstate(self):
500
 
        # we should not get a dirstate optimiser when the revision id for of
501
 
        # the source is not in the dirstate of the target.
502
 
        tree = self.make_workingtree()
503
 
        rev_id = tree.commit('first post')
504
 
        rev_id2 = tree.commit('second post')
505
 
        rev_tree = tree.branch.repository.revision_tree(rev_id)
506
 
        tree.lock_read()
507
 
        optimiser = InterTree.get(rev_tree, tree)
508
 
        self.assertIsInstance(optimiser, InterTree)
509
 
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
510
 
        optimiser = InterTree.get(tree, rev_tree)
511
 
        self.assertIsInstance(optimiser, InterTree)
512
 
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
513
 
        tree.unlock()
514
 
 
515
 
    def test_empty_basis_to_dirstate_tree(self):
516
 
        # we should get a InterDirStateTree for doing
517
 
        # 'changes_from' from the first basis dirstate revision tree to a
518
 
        # WorkingTree4.
519
 
        tree = self.make_workingtree()
520
 
        tree.lock_read()
521
 
        basis_tree = tree.basis_tree()
522
 
        basis_tree.lock_read()
523
 
        optimiser = InterTree.get(basis_tree, tree)
524
 
        tree.unlock()
525
 
        basis_tree.unlock()
526
 
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
527
 
 
528
 
    def test_nonempty_basis_to_dirstate_tree(self):
529
 
        # we should get a InterDirStateTree for doing
530
 
        # 'changes_from' from a non-null basis dirstate revision tree to a
531
 
        # WorkingTree4.
532
 
        tree = self.make_workingtree()
533
 
        tree.commit('first post')
534
 
        tree.lock_read()
535
 
        basis_tree = tree.basis_tree()
536
 
        basis_tree.lock_read()
537
 
        optimiser = InterTree.get(basis_tree, tree)
538
 
        tree.unlock()
539
 
        basis_tree.unlock()
540
 
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
541
 
 
542
 
    def test_empty_basis_revtree_to_dirstate_tree(self):
543
 
        # we should get a InterDirStateTree for doing
544
 
        # 'changes_from' from an empty repository based rev tree to a
545
 
        # WorkingTree4.
546
 
        tree = self.make_workingtree()
547
 
        tree.lock_read()
548
 
        basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
549
 
        basis_tree.lock_read()
550
 
        optimiser = InterTree.get(basis_tree, tree)
551
 
        tree.unlock()
552
 
        basis_tree.unlock()
553
 
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
554
 
 
555
 
    def test_nonempty_basis_revtree_to_dirstate_tree(self):
556
 
        # we should get a InterDirStateTree for doing
557
 
        # 'changes_from' from a non-null repository based rev tree to a
558
 
        # WorkingTree4.
559
 
        tree = self.make_workingtree()
560
 
        tree.commit('first post')
561
 
        tree.lock_read()
562
 
        basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
563
 
        basis_tree.lock_read()
564
 
        optimiser = InterTree.get(basis_tree, tree)
565
 
        tree.unlock()
566
 
        basis_tree.unlock()
567
 
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
568
 
 
569
 
    def test_tree_to_basis_in_other_tree(self):
570
 
        # we should get a InterDirStateTree when
571
 
        # the source revid is in the dirstate object of the target and
572
 
        # the dirstates are different. This is largely covered by testing
573
 
        # with repository revtrees, so is just for extra confidence.
574
 
        tree = self.make_workingtree('a')
575
 
        tree.commit('first post')
576
 
        tree2 = self.make_workingtree('b')
577
 
        tree2.pull(tree.branch)
578
 
        basis_tree = tree.basis_tree()
579
 
        tree2.lock_read()
580
 
        basis_tree.lock_read()
581
 
        optimiser = InterTree.get(basis_tree, tree2)
582
 
        tree2.unlock()
583
 
        basis_tree.unlock()
584
 
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
585
 
 
586
 
    def test_merged_revtree_to_tree(self):
587
 
        # we should get a InterDirStateTree when
588
 
        # the source tree is a merged tree present in the dirstate of target.
589
 
        tree = self.make_workingtree('a')
590
 
        tree.commit('first post')
591
 
        tree.commit('tree 1 commit 2')
592
 
        tree2 = self.make_workingtree('b')
593
 
        tree2.pull(tree.branch)
594
 
        tree2.commit('tree 2 commit 2')
595
 
        tree.merge_from_branch(tree2.branch)
596
 
        second_parent_tree = tree.revision_tree(tree.get_parent_ids()[1])
597
 
        second_parent_tree.lock_read()
598
 
        tree.lock_read()
599
 
        optimiser = InterTree.get(second_parent_tree, tree)
600
 
        tree.unlock()
601
 
        second_parent_tree.unlock()
602
 
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
603
 
 
604
 
 
605
 
class TestFormat2WorkingTree(TestCaseWithTransport):
606
 
    """Tests that are specific to format 2 trees."""
607
 
 
608
 
    def create_format2_tree(self, url):
609
 
        return self.make_branch_and_tree(
610
 
            url, format=bzrlib.bzrdir.BzrDirFormat6())
611
 
 
612
 
    def test_conflicts(self):
613
 
        # test backwards compatability
614
 
        tree = self.create_format2_tree('.')
615
 
        self.assertRaises(errors.UnsupportedOperation, tree.set_conflicts,
616
 
                          None)
617
 
        file('lala.BASE', 'wb').write('labase')
618
 
        expected = conflicts.ContentsConflict('lala')
619
 
        self.assertEqual(list(tree.conflicts()), [expected])
620
 
        file('lala', 'wb').write('la')
621
 
        tree.add('lala', 'lala-id')
622
 
        expected = conflicts.ContentsConflict('lala', file_id='lala-id')
623
 
        self.assertEqual(list(tree.conflicts()), [expected])
624
 
        file('lala.THIS', 'wb').write('lathis')
625
 
        file('lala.OTHER', 'wb').write('laother')
626
 
        # When "text conflict"s happen, stem, THIS and OTHER are text
627
 
        expected = conflicts.TextConflict('lala', file_id='lala-id')
628
 
        self.assertEqual(list(tree.conflicts()), [expected])
629
 
        os.unlink('lala.OTHER')
630
 
        os.mkdir('lala.OTHER')
631
 
        expected = conflicts.ContentsConflict('lala', file_id='lala-id')
632
 
        self.assertEqual(list(tree.conflicts()), [expected])
633
 
 
634
 
 
635
 
class TestNonFormatSpecificCode(TestCaseWithTransport):
636
 
    """This class contains tests of workingtree that are not format specific."""
637
 
 
638
 
    def test_gen_file_id(self):
639
 
        file_id = self.applyDeprecated(zero_thirteen, workingtree.gen_file_id,
640
 
                                      'filename')
641
 
        self.assertStartsWith(file_id, 'filename-')
642
 
 
643
 
    def test_gen_root_id(self):
644
 
        file_id = self.applyDeprecated(zero_thirteen, workingtree.gen_root_id)
645
 
        self.assertStartsWith(file_id, 'tree_root-')
646
 
        
647
 
 
648
 
class InstrumentedTree(object):
649
 
    """A instrumented tree to check the needs_tree_write_lock decorator."""
650
 
 
651
 
    def __init__(self):
652
 
        self._locks = []
653
 
 
654
 
    def lock_tree_write(self):
655
 
        self._locks.append('t')
656
 
 
657
 
    @needs_tree_write_lock
658
 
    def method_with_tree_write_lock(self, *args, **kwargs):
659
 
        """A lock_tree_write decorated method that returns its arguments."""
660
 
        return args, kwargs
661
 
 
662
 
    @needs_tree_write_lock
663
 
    def method_that_raises(self):
664
 
        """This method causes an exception when called with parameters.
665
 
        
666
 
        This allows the decorator code to be checked - it should still call
667
 
        unlock.
668
 
        """
669
 
 
670
 
    def unlock(self):
671
 
        self._locks.append('u')
672
 
 
673
 
 
674
 
class TestInstrumentedTree(TestCase):
675
 
 
676
 
    def test_needs_tree_write_lock(self):
677
 
        """@needs_tree_write_lock should be semantically transparent."""
678
 
        tree = InstrumentedTree()
679
 
        self.assertEqual(
680
 
            'method_with_tree_write_lock',
681
 
            tree.method_with_tree_write_lock.__name__)
682
 
        self.assertEqual(
683
 
            "A lock_tree_write decorated method that returns its arguments.",
684
 
            tree.method_with_tree_write_lock.__doc__)
685
 
        args = (1, 2, 3)
686
 
        kwargs = {'a':'b'}
687
 
        result = tree.method_with_tree_write_lock(1,2,3, a='b')
688
 
        self.assertEqual((args, kwargs), result)
689
 
        self.assertEqual(['t', 'u'], tree._locks)
690
 
        self.assertRaises(TypeError, tree.method_that_raises, 'foo')
691
 
        self.assertEqual(['t', 'u', 't', 'u'], tree._locks)