/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

More work on roundtrip push support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 Canonical Ltd
2
 
#
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2 of the License, or
6
 
# (at your option) any later version.
7
 
#
8
 
# This program is distributed in the hope that it will be useful,
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
# GNU General Public License for more details.
12
 
#
13
 
# You should have received a copy of the GNU General Public License
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
16
 
 
17
 
import os
18
 
import stat
19
 
import sys
20
 
 
21
 
from bzrlib import (
22
 
    errors,
23
 
    generate_ids,
24
 
    symbol_versioning,
25
 
    tests,
26
 
    urlutils,
27
 
    )
28
 
from bzrlib.bzrdir import BzrDir
29
 
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
30
 
                              UnversionedParent, ParentLoop, DeletingParent,)
31
 
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
32
 
                           ReusingTransform, CantMoveRoot, 
33
 
                           PathsNotVersionedError, ExistingLimbo,
34
 
                           ExistingPendingDeletion, ImmortalLimbo,
35
 
                           ImmortalPendingDeletion, LockError)
36
 
from bzrlib.osutils import file_kind, pathjoin
37
 
from bzrlib.merge import Merge3Merger
38
 
from bzrlib.tests import (
39
 
    CaseInsensitiveFilesystemFeature,
40
 
    SymlinkFeature,
41
 
    TestCase,
42
 
    TestCaseInTempDir,
43
 
    TestSkipped,
44
 
    )
45
 
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
46
 
                              resolve_conflicts, cook_conflicts, 
47
 
                              find_interesting, build_tree, get_backup_name,
48
 
                              change_entry, _FileMover, resolve_checkout)
49
 
 
50
 
 
51
 
class TestTreeTransform(tests.TestCaseWithTransport):
52
 
 
53
 
    def setUp(self):
54
 
        super(TestTreeTransform, self).setUp()
55
 
        self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
56
 
        os.chdir('..')
57
 
 
58
 
    def get_transform(self):
59
 
        transform = TreeTransform(self.wt)
60
 
        #self.addCleanup(transform.finalize)
61
 
        return transform, transform.root
62
 
 
63
 
    def test_existing_limbo(self):
64
 
        transform, root = self.get_transform()
65
 
        limbo_name = transform._limbodir
66
 
        deletion_path = transform._deletiondir
67
 
        os.mkdir(pathjoin(limbo_name, 'hehe'))
68
 
        self.assertRaises(ImmortalLimbo, transform.apply)
69
 
        self.assertRaises(LockError, self.wt.unlock)
70
 
        self.assertRaises(ExistingLimbo, self.get_transform)
71
 
        self.assertRaises(LockError, self.wt.unlock)
72
 
        os.rmdir(pathjoin(limbo_name, 'hehe'))
73
 
        os.rmdir(limbo_name)
74
 
        os.rmdir(deletion_path)
75
 
        transform, root = self.get_transform()
76
 
        transform.apply()
77
 
 
78
 
    def test_existing_pending_deletion(self):
79
 
        transform, root = self.get_transform()
80
 
        deletion_path = self._limbodir = urlutils.local_path_from_url(
81
 
            transform._tree._control_files.controlfilename('pending-deletion'))
82
 
        os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
83
 
        self.assertRaises(ImmortalPendingDeletion, transform.apply)
84
 
        self.assertRaises(LockError, self.wt.unlock)
85
 
        self.assertRaises(ExistingPendingDeletion, self.get_transform)
86
 
 
87
 
    def test_build(self):
88
 
        transform, root = self.get_transform()
89
 
        self.wt.lock_tree_write()
90
 
        self.addCleanup(self.wt.unlock)
91
 
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
92
 
        imaginary_id = transform.trans_id_tree_path('imaginary')
93
 
        imaginary_id2 = transform.trans_id_tree_path('imaginary/')
94
 
        self.assertEqual(imaginary_id, imaginary_id2)
95
 
        self.assertEqual(transform.get_tree_parent(imaginary_id), root)
96
 
        self.assertEqual(transform.final_kind(root), 'directory')
97
 
        self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
98
 
        trans_id = transform.create_path('name', root)
99
 
        self.assertIs(transform.final_file_id(trans_id), None)
100
 
        self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
101
 
        transform.create_file('contents', trans_id)
102
 
        transform.set_executability(True, trans_id)
103
 
        transform.version_file('my_pretties', trans_id)
104
 
        self.assertRaises(DuplicateKey, transform.version_file,
105
 
                          'my_pretties', trans_id)
106
 
        self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
107
 
        self.assertEqual(transform.final_parent(trans_id), root)
108
 
        self.assertIs(transform.final_parent(root), ROOT_PARENT)
109
 
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
110
 
        oz_id = transform.create_path('oz', root)
111
 
        transform.create_directory(oz_id)
112
 
        transform.version_file('ozzie', oz_id)
113
 
        trans_id2 = transform.create_path('name2', root)
114
 
        transform.create_file('contents', trans_id2)
115
 
        transform.set_executability(False, trans_id2)
116
 
        transform.version_file('my_pretties2', trans_id2)
117
 
        modified_paths = transform.apply().modified_paths
118
 
        self.assertEqual('contents', self.wt.get_file_byname('name').read())
119
 
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
120
 
        self.assertIs(self.wt.is_executable('my_pretties'), True)
121
 
        self.assertIs(self.wt.is_executable('my_pretties2'), False)
122
 
        self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
123
 
        self.assertEqual(len(modified_paths), 3)
124
 
        tree_mod_paths = [self.wt.id2abspath(f) for f in 
125
 
                          ('ozzie', 'my_pretties', 'my_pretties2')]
126
 
        self.assertSubset(tree_mod_paths, modified_paths)
127
 
        # is it safe to finalize repeatedly?
128
 
        transform.finalize()
129
 
        transform.finalize()
130
 
 
131
 
    def test_convenience(self):
132
 
        transform, root = self.get_transform()
133
 
        self.wt.lock_tree_write()
134
 
        self.addCleanup(self.wt.unlock)
135
 
        trans_id = transform.new_file('name', root, 'contents', 
136
 
                                      'my_pretties', True)
137
 
        oz = transform.new_directory('oz', root, 'oz-id')
138
 
        dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
139
 
        toto = transform.new_file('toto', dorothy, 'toto-contents', 
140
 
                                  'toto-id', False)
141
 
 
142
 
        self.assertEqual(len(transform.find_conflicts()), 0)
143
 
        transform.apply()
144
 
        self.assertRaises(ReusingTransform, transform.find_conflicts)
145
 
        self.assertEqual('contents', file(self.wt.abspath('name')).read())
146
 
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
147
 
        self.assertIs(self.wt.is_executable('my_pretties'), True)
148
 
        self.assertEqual(self.wt.path2id('oz'), 'oz-id')
149
 
        self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
150
 
        self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
151
 
 
152
 
        self.assertEqual('toto-contents',
153
 
                         self.wt.get_file_byname('oz/dorothy/toto').read())
154
 
        self.assertIs(self.wt.is_executable('toto-id'), False)
155
 
 
156
 
    def test_tree_reference(self):
157
 
        transform, root = self.get_transform()
158
 
        tree = transform._tree
159
 
        trans_id = transform.new_directory('reference', root, 'subtree-id')
160
 
        transform.set_tree_reference('subtree-revision', trans_id)
161
 
        transform.apply()
162
 
        tree.lock_read()
163
 
        self.addCleanup(tree.unlock)
164
 
        self.assertEqual('subtree-revision',
165
 
                         tree.inventory['subtree-id'].reference_revision)
166
 
 
167
 
    def test_conflicts(self):
168
 
        transform, root = self.get_transform()
169
 
        trans_id = transform.new_file('name', root, 'contents', 
170
 
                                      'my_pretties')
171
 
        self.assertEqual(len(transform.find_conflicts()), 0)
172
 
        trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
173
 
        self.assertEqual(transform.find_conflicts(), 
174
 
                         [('duplicate', trans_id, trans_id2, 'name')])
175
 
        self.assertRaises(MalformedTransform, transform.apply)
176
 
        transform.adjust_path('name', trans_id, trans_id2)
177
 
        self.assertEqual(transform.find_conflicts(), 
178
 
                         [('non-directory parent', trans_id)])
179
 
        tinman_id = transform.trans_id_tree_path('tinman')
180
 
        transform.adjust_path('name', tinman_id, trans_id2)
181
 
        self.assertEqual(transform.find_conflicts(), 
182
 
                         [('unversioned parent', tinman_id), 
183
 
                          ('missing parent', tinman_id)])
184
 
        lion_id = transform.create_path('lion', root)
185
 
        self.assertEqual(transform.find_conflicts(), 
186
 
                         [('unversioned parent', tinman_id), 
187
 
                          ('missing parent', tinman_id)])
188
 
        transform.adjust_path('name', lion_id, trans_id2)
189
 
        self.assertEqual(transform.find_conflicts(), 
190
 
                         [('unversioned parent', lion_id),
191
 
                          ('missing parent', lion_id)])
192
 
        transform.version_file("Courage", lion_id)
193
 
        self.assertEqual(transform.find_conflicts(), 
194
 
                         [('missing parent', lion_id), 
195
 
                          ('versioning no contents', lion_id)])
196
 
        transform.adjust_path('name2', root, trans_id2)
197
 
        self.assertEqual(transform.find_conflicts(), 
198
 
                         [('versioning no contents', lion_id)])
199
 
        transform.create_file('Contents, okay?', lion_id)
200
 
        transform.adjust_path('name2', trans_id2, trans_id2)
201
 
        self.assertEqual(transform.find_conflicts(), 
202
 
                         [('parent loop', trans_id2), 
203
 
                          ('non-directory parent', trans_id2)])
204
 
        transform.adjust_path('name2', root, trans_id2)
205
 
        oz_id = transform.new_directory('oz', root)
206
 
        transform.set_executability(True, oz_id)
207
 
        self.assertEqual(transform.find_conflicts(), 
208
 
                         [('unversioned executability', oz_id)])
209
 
        transform.version_file('oz-id', oz_id)
210
 
        self.assertEqual(transform.find_conflicts(), 
211
 
                         [('non-file executability', oz_id)])
212
 
        transform.set_executability(None, oz_id)
213
 
        tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
214
 
        transform.apply()
215
 
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
216
 
        self.assertEqual('contents', file(self.wt.abspath('name')).read())
217
 
        transform2, root = self.get_transform()
218
 
        oz_id = transform2.trans_id_tree_file_id('oz-id')
219
 
        newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
220
 
        result = transform2.find_conflicts()
221
 
        fp = FinalPaths(transform2)
222
 
        self.assert_('oz/tip' in transform2._tree_path_ids)
223
 
        self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
224
 
        self.assertEqual(len(result), 2)
225
 
        self.assertEqual((result[0][0], result[0][1]), 
226
 
                         ('duplicate', newtip))
227
 
        self.assertEqual((result[1][0], result[1][2]), 
228
 
                         ('duplicate id', newtip))
229
 
        transform2.finalize()
230
 
        transform3 = TreeTransform(self.wt)
231
 
        self.addCleanup(transform3.finalize)
232
 
        oz_id = transform3.trans_id_tree_file_id('oz-id')
233
 
        transform3.delete_contents(oz_id)
234
 
        self.assertEqual(transform3.find_conflicts(), 
235
 
                         [('missing parent', oz_id)])
236
 
        root_id = transform3.root
237
 
        tip_id = transform3.trans_id_tree_file_id('tip-id')
238
 
        transform3.adjust_path('tip', root_id, tip_id)
239
 
        transform3.apply()
240
 
 
241
 
    def test_conflict_on_case_insensitive(self):
242
 
        tree = self.make_branch_and_tree('tree')
243
 
        # Don't try this at home, kids!
244
 
        # Force the tree to report that it is case sensitive, for conflict
245
 
        # resolution tests
246
 
        tree.case_sensitive = True
247
 
        transform = TreeTransform(tree)
248
 
        self.addCleanup(transform.finalize)
249
 
        transform.new_file('file', transform.root, 'content')
250
 
        transform.new_file('FiLe', transform.root, 'content')
251
 
        result = transform.find_conflicts()
252
 
        self.assertEqual([], result)
253
 
        # Force the tree to report that it is case insensitive, for conflict
254
 
        # generation tests
255
 
        tree.case_sensitive = False
256
 
        result = transform.find_conflicts()
257
 
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
258
 
 
259
 
    def test_conflict_on_case_insensitive_existing(self):
260
 
        tree = self.make_branch_and_tree('tree')
261
 
        self.build_tree(['tree/FiLe'])
262
 
        # Don't try this at home, kids!
263
 
        # Force the tree to report that it is case sensitive, for conflict
264
 
        # resolution tests
265
 
        tree.case_sensitive = True
266
 
        transform = TreeTransform(tree)
267
 
        self.addCleanup(transform.finalize)
268
 
        transform.new_file('file', transform.root, 'content')
269
 
        result = transform.find_conflicts()
270
 
        self.assertEqual([], result)
271
 
        # Force the tree to report that it is case insensitive, for conflict
272
 
        # generation tests
273
 
        tree.case_sensitive = False
274
 
        result = transform.find_conflicts()
275
 
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
276
 
 
277
 
    def test_resolve_case_insensitive_conflict(self):
278
 
        tree = self.make_branch_and_tree('tree')
279
 
        # Don't try this at home, kids!
280
 
        # Force the tree to report that it is case insensitive, for conflict
281
 
        # resolution tests
282
 
        tree.case_sensitive = False
283
 
        transform = TreeTransform(tree)
284
 
        self.addCleanup(transform.finalize)
285
 
        transform.new_file('file', transform.root, 'content')
286
 
        transform.new_file('FiLe', transform.root, 'content')
287
 
        resolve_conflicts(transform)
288
 
        transform.apply()
289
 
        self.failUnlessExists('tree/file')
290
 
        self.failUnlessExists('tree/FiLe.moved')
291
 
 
292
 
    def test_resolve_checkout_case_conflict(self):
293
 
        tree = self.make_branch_and_tree('tree')
294
 
        # Don't try this at home, kids!
295
 
        # Force the tree to report that it is case insensitive, for conflict
296
 
        # resolution tests
297
 
        tree.case_sensitive = False
298
 
        transform = TreeTransform(tree)
299
 
        self.addCleanup(transform.finalize)
300
 
        transform.new_file('file', transform.root, 'content')
301
 
        transform.new_file('FiLe', transform.root, 'content')
302
 
        resolve_conflicts(transform,
303
 
                          pass_func=lambda t, c: resolve_checkout(t, c, []))
304
 
        transform.apply()
305
 
        self.failUnlessExists('tree/file')
306
 
        self.failUnlessExists('tree/FiLe.moved')
307
 
 
308
 
    def test_apply_case_conflict(self):
309
 
        """Ensure that a transform with case conflicts can always be applied"""
310
 
        tree = self.make_branch_and_tree('tree')
311
 
        transform = TreeTransform(tree)
312
 
        self.addCleanup(transform.finalize)
313
 
        transform.new_file('file', transform.root, 'content')
314
 
        transform.new_file('FiLe', transform.root, 'content')
315
 
        dir = transform.new_directory('dir', transform.root)
316
 
        transform.new_file('dirfile', dir, 'content')
317
 
        transform.new_file('dirFiLe', dir, 'content')
318
 
        resolve_conflicts(transform)
319
 
        transform.apply()
320
 
        self.failUnlessExists('tree/file')
321
 
        if not os.path.exists('tree/FiLe.moved'):
322
 
            self.failUnlessExists('tree/FiLe')
323
 
        self.failUnlessExists('tree/dir/dirfile')
324
 
        if not os.path.exists('tree/dir/dirFiLe.moved'):
325
 
            self.failUnlessExists('tree/dir/dirFiLe')
326
 
 
327
 
    def test_case_insensitive_limbo(self):
328
 
        tree = self.make_branch_and_tree('tree')
329
 
        # Don't try this at home, kids!
330
 
        # Force the tree to report that it is case insensitive
331
 
        tree.case_sensitive = False
332
 
        transform = TreeTransform(tree)
333
 
        self.addCleanup(transform.finalize)
334
 
        dir = transform.new_directory('dir', transform.root)
335
 
        first = transform.new_file('file', dir, 'content')
336
 
        second = transform.new_file('FiLe', dir, 'content')
337
 
        self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
338
 
        self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
339
 
 
340
 
    def test_add_del(self):
341
 
        start, root = self.get_transform()
342
 
        start.new_directory('a', root, 'a')
343
 
        start.apply()
344
 
        transform, root = self.get_transform()
345
 
        transform.delete_versioned(transform.trans_id_tree_file_id('a'))
346
 
        transform.new_directory('a', root, 'a')
347
 
        transform.apply()
348
 
 
349
 
    def test_unversioning(self):
350
 
        create_tree, root = self.get_transform()
351
 
        parent_id = create_tree.new_directory('parent', root, 'parent-id')
352
 
        create_tree.new_file('child', parent_id, 'child', 'child-id')
353
 
        create_tree.apply()
354
 
        unversion = TreeTransform(self.wt)
355
 
        self.addCleanup(unversion.finalize)
356
 
        parent = unversion.trans_id_tree_path('parent')
357
 
        unversion.unversion_file(parent)
358
 
        self.assertEqual(unversion.find_conflicts(), 
359
 
                         [('unversioned parent', parent_id)])
360
 
        file_id = unversion.trans_id_tree_file_id('child-id')
361
 
        unversion.unversion_file(file_id)
362
 
        unversion.apply()
363
 
 
364
 
    def test_name_invariants(self):
365
 
        create_tree, root = self.get_transform()
366
 
        # prepare tree
367
 
        root = create_tree.root
368
 
        create_tree.new_file('name1', root, 'hello1', 'name1')
369
 
        create_tree.new_file('name2', root, 'hello2', 'name2')
370
 
        ddir = create_tree.new_directory('dying_directory', root, 'ddir')
371
 
        create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
372
 
        create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
373
 
        create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
374
 
        create_tree.apply()
375
 
 
376
 
        mangle_tree,root = self.get_transform()
377
 
        root = mangle_tree.root
378
 
        #swap names
379
 
        name1 = mangle_tree.trans_id_tree_file_id('name1')
380
 
        name2 = mangle_tree.trans_id_tree_file_id('name2')
381
 
        mangle_tree.adjust_path('name2', root, name1)
382
 
        mangle_tree.adjust_path('name1', root, name2)
383
 
 
384
 
        #tests for deleting parent directories 
385
 
        ddir = mangle_tree.trans_id_tree_file_id('ddir')
386
 
        mangle_tree.delete_contents(ddir)
387
 
        dfile = mangle_tree.trans_id_tree_file_id('dfile')
388
 
        mangle_tree.delete_versioned(dfile)
389
 
        mangle_tree.unversion_file(dfile)
390
 
        mfile = mangle_tree.trans_id_tree_file_id('mfile')
391
 
        mangle_tree.adjust_path('mfile', root, mfile)
392
 
 
393
 
        #tests for adding parent directories
394
 
        newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
395
 
        mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
396
 
        mangle_tree.adjust_path('mfile2', newdir, mfile2)
397
 
        mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
398
 
        self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
399
 
        self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
400
 
        self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
401
 
        mangle_tree.apply()
402
 
        self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
403
 
        self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
404
 
        mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
405
 
        self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
406
 
        self.assertEqual(file(mfile2_path).read(), 'later2')
407
 
        self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
408
 
        self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
409
 
        newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
410
 
        self.assertEqual(file(newfile_path).read(), 'hello3')
411
 
        self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
412
 
        self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
413
 
        mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
414
 
 
415
 
    def test_both_rename(self):
416
 
        create_tree,root = self.get_transform()
417
 
        newdir = create_tree.new_directory('selftest', root, 'selftest-id')
418
 
        create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
419
 
        create_tree.apply()        
420
 
        mangle_tree,root = self.get_transform()
421
 
        selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
422
 
        blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
423
 
        mangle_tree.adjust_path('test', root, selftest)
424
 
        mangle_tree.adjust_path('test_too_much', root, selftest)
425
 
        mangle_tree.set_executability(True, blackbox)
426
 
        mangle_tree.apply()
427
 
 
428
 
    def test_both_rename2(self):
429
 
        create_tree,root = self.get_transform()
430
 
        bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
431
 
        tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
432
 
        blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
433
 
        create_tree.new_file('test_too_much.py', blackbox, 'hello1', 
434
 
                             'test_too_much-id')
435
 
        create_tree.apply()        
436
 
        mangle_tree,root = self.get_transform()
437
 
        bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
438
 
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
439
 
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
440
 
        mangle_tree.adjust_path('selftest', bzrlib, tests)
441
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
442
 
        mangle_tree.set_executability(True, test_too_much)
443
 
        mangle_tree.apply()
444
 
 
445
 
    def test_both_rename3(self):
446
 
        create_tree,root = self.get_transform()
447
 
        tests = create_tree.new_directory('tests', root, 'tests-id')
448
 
        create_tree.new_file('test_too_much.py', tests, 'hello1', 
449
 
                             'test_too_much-id')
450
 
        create_tree.apply()        
451
 
        mangle_tree,root = self.get_transform()
452
 
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
453
 
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
454
 
        mangle_tree.adjust_path('selftest', root, tests)
455
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
456
 
        mangle_tree.set_executability(True, test_too_much)
457
 
        mangle_tree.apply()
458
 
 
459
 
    def test_move_dangling_ie(self):
460
 
        create_tree, root = self.get_transform()
461
 
        # prepare tree
462
 
        root = create_tree.root
463
 
        create_tree.new_file('name1', root, 'hello1', 'name1')
464
 
        create_tree.apply()
465
 
        delete_contents, root = self.get_transform()
466
 
        file = delete_contents.trans_id_tree_file_id('name1')
467
 
        delete_contents.delete_contents(file)
468
 
        delete_contents.apply()
469
 
        move_id, root = self.get_transform()
470
 
        name1 = move_id.trans_id_tree_file_id('name1')
471
 
        newdir = move_id.new_directory('dir', root, 'newdir')
472
 
        move_id.adjust_path('name2', newdir, name1)
473
 
        move_id.apply()
474
 
        
475
 
    def test_replace_dangling_ie(self):
476
 
        create_tree, root = self.get_transform()
477
 
        # prepare tree
478
 
        root = create_tree.root
479
 
        create_tree.new_file('name1', root, 'hello1', 'name1')
480
 
        create_tree.apply()
481
 
        delete_contents = TreeTransform(self.wt)
482
 
        self.addCleanup(delete_contents.finalize)
483
 
        file = delete_contents.trans_id_tree_file_id('name1')
484
 
        delete_contents.delete_contents(file)
485
 
        delete_contents.apply()
486
 
        delete_contents.finalize()
487
 
        replace = TreeTransform(self.wt)
488
 
        self.addCleanup(replace.finalize)
489
 
        name2 = replace.new_file('name2', root, 'hello2', 'name1')
490
 
        conflicts = replace.find_conflicts()
491
 
        name1 = replace.trans_id_tree_file_id('name1')
492
 
        self.assertEqual(conflicts, [('duplicate id', name1, name2)])
493
 
        resolve_conflicts(replace)
494
 
        replace.apply()
495
 
 
496
 
    def test_symlinks(self):
497
 
        self.requireFeature(SymlinkFeature)
498
 
        transform,root = self.get_transform()
499
 
        oz_id = transform.new_directory('oz', root, 'oz-id')
500
 
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target', 
501
 
                                       'wizard-id')
502
 
        wiz_id = transform.create_path('wizard2', oz_id)
503
 
        transform.create_symlink('behind_curtain', wiz_id)
504
 
        transform.version_file('wiz-id2', wiz_id)            
505
 
        transform.set_executability(True, wiz_id)
506
 
        self.assertEqual(transform.find_conflicts(), 
507
 
                         [('non-file executability', wiz_id)])
508
 
        transform.set_executability(None, wiz_id)
509
 
        transform.apply()
510
 
        self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
511
 
        self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
512
 
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')), 
513
 
                         'behind_curtain')
514
 
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
515
 
                         'wizard-target')
516
 
 
517
 
    def test_unable_create_symlink(self):
518
 
        def tt_helper():
519
 
            wt = self.make_branch_and_tree('.')
520
 
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
521
 
            try:
522
 
                tt.new_symlink('foo', tt.root, 'bar')
523
 
                tt.apply()
524
 
            finally:
525
 
                wt.unlock()
526
 
        os_symlink = getattr(os, 'symlink', None)
527
 
        os.symlink = None
528
 
        try:
529
 
            err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
530
 
            self.assertEquals(
531
 
                "Unable to create symlink 'foo' on this platform",
532
 
                str(err))
533
 
        finally:
534
 
            if os_symlink:
535
 
                os.symlink = os_symlink
536
 
 
537
 
    def get_conflicted(self):
538
 
        create,root = self.get_transform()
539
 
        create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
540
 
        oz = create.new_directory('oz', root, 'oz-id')
541
 
        create.new_directory('emeraldcity', oz, 'emerald-id')
542
 
        create.apply()
543
 
        conflicts,root = self.get_transform()
544
 
        # set up duplicate entry, duplicate id
545
 
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy', 
546
 
                                         'dorothy-id')
547
 
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
548
 
        oz = conflicts.trans_id_tree_file_id('oz-id')
549
 
        # set up DeletedParent parent conflict
550
 
        conflicts.delete_versioned(oz)
551
 
        emerald = conflicts.trans_id_tree_file_id('emerald-id')
552
 
        # set up MissingParent conflict
553
 
        munchkincity = conflicts.trans_id_file_id('munchkincity-id')
554
 
        conflicts.adjust_path('munchkincity', root, munchkincity)
555
 
        conflicts.new_directory('auntem', munchkincity, 'auntem-id')
556
 
        # set up parent loop
557
 
        conflicts.adjust_path('emeraldcity', emerald, emerald)
558
 
        return conflicts, emerald, oz, old_dorothy, new_dorothy
559
 
 
560
 
    def test_conflict_resolution(self):
561
 
        conflicts, emerald, oz, old_dorothy, new_dorothy =\
562
 
            self.get_conflicted()
563
 
        resolve_conflicts(conflicts)
564
 
        self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
565
 
        self.assertIs(conflicts.final_file_id(old_dorothy), None)
566
 
        self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
567
 
        self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
568
 
        self.assertEqual(conflicts.final_parent(emerald), oz)
569
 
        conflicts.apply()
570
 
 
571
 
    def test_cook_conflicts(self):
572
 
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
573
 
        raw_conflicts = resolve_conflicts(tt)
574
 
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
575
 
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved', 
576
 
                                   'dorothy', None, 'dorothy-id')
577
 
        self.assertEqual(cooked_conflicts[0], duplicate)
578
 
        duplicate_id = DuplicateID('Unversioned existing file', 
579
 
                                   'dorothy.moved', 'dorothy', None,
580
 
                                   'dorothy-id')
581
 
        self.assertEqual(cooked_conflicts[1], duplicate_id)
582
 
        missing_parent = MissingParent('Created directory', 'munchkincity',
583
 
                                       'munchkincity-id')
584
 
        deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
585
 
        self.assertEqual(cooked_conflicts[2], missing_parent)
586
 
        unversioned_parent = UnversionedParent('Versioned directory',
587
 
                                               'munchkincity',
588
 
                                               'munchkincity-id')
589
 
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
590
 
                                               'oz-id')
591
 
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
592
 
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity', 
593
 
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
594
 
        self.assertEqual(cooked_conflicts[4], deleted_parent)
595
 
        self.assertEqual(cooked_conflicts[5], unversioned_parent2)
596
 
        self.assertEqual(cooked_conflicts[6], parent_loop)
597
 
        self.assertEqual(len(cooked_conflicts), 7)
598
 
        tt.finalize()
599
 
 
600
 
    def test_string_conflicts(self):
601
 
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
602
 
        raw_conflicts = resolve_conflicts(tt)
603
 
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
604
 
        tt.finalize()
605
 
        conflicts_s = [str(c) for c in cooked_conflicts]
606
 
        self.assertEqual(len(cooked_conflicts), len(conflicts_s))
607
 
        self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy.  '
608
 
                                         'Moved existing file to '
609
 
                                         'dorothy.moved.')
610
 
        self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy.  '
611
 
                                         'Unversioned existing file '
612
 
                                         'dorothy.moved.')
613
 
        self.assertEqual(conflicts_s[2], 'Conflict adding files to'
614
 
                                         ' munchkincity.  Created directory.')
615
 
        self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
616
 
                                         ' versioned, but has versioned'
617
 
                                         ' children.  Versioned directory.')
618
 
        self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
619
 
                                         " is not empty.  Not deleting.")
620
 
        self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
621
 
                                         ' versioned, but has versioned'
622
 
                                         ' children.  Versioned directory.')
623
 
        self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
624
 
                                         ' oz/emeraldcity.  Cancelled move.')
625
 
 
626
 
    def test_moving_versioned_directories(self):
627
 
        create, root = self.get_transform()
628
 
        kansas = create.new_directory('kansas', root, 'kansas-id')
629
 
        create.new_directory('house', kansas, 'house-id')
630
 
        create.new_directory('oz', root, 'oz-id')
631
 
        create.apply()
632
 
        cyclone, root = self.get_transform()
633
 
        oz = cyclone.trans_id_tree_file_id('oz-id')
634
 
        house = cyclone.trans_id_tree_file_id('house-id')
635
 
        cyclone.adjust_path('house', oz, house)
636
 
        cyclone.apply()
637
 
 
638
 
    def test_moving_root(self):
639
 
        create, root = self.get_transform()
640
 
        fun = create.new_directory('fun', root, 'fun-id')
641
 
        create.new_directory('sun', root, 'sun-id')
642
 
        create.new_directory('moon', root, 'moon')
643
 
        create.apply()
644
 
        transform, root = self.get_transform()
645
 
        transform.adjust_root_path('oldroot', fun)
646
 
        new_root=transform.trans_id_tree_path('')
647
 
        transform.version_file('new-root', new_root)
648
 
        transform.apply()
649
 
 
650
 
    def test_renames(self):
651
 
        create, root = self.get_transform()
652
 
        old = create.new_directory('old-parent', root, 'old-id')
653
 
        intermediate = create.new_directory('intermediate', old, 'im-id')
654
 
        myfile = create.new_file('myfile', intermediate, 'myfile-text',
655
 
                                 'myfile-id')
656
 
        create.apply()
657
 
        rename, root = self.get_transform()
658
 
        old = rename.trans_id_file_id('old-id')
659
 
        rename.adjust_path('new', root, old)
660
 
        myfile = rename.trans_id_file_id('myfile-id')
661
 
        rename.set_executability(True, myfile)
662
 
        rename.apply()
663
 
 
664
 
    def test_find_interesting(self):
665
 
        create, root = self.get_transform()
666
 
        wt = create._tree
667
 
        create.new_file('vfile', root, 'myfile-text', 'myfile-id')
668
 
        create.new_file('uvfile', root, 'othertext')
669
 
        create.apply()
670
 
        result = self.applyDeprecated(symbol_versioning.zero_fifteen,
671
 
            find_interesting, wt, wt, ['vfile'])
672
 
        self.assertEqual(result, set(['myfile-id']))
673
 
 
674
 
    def test_set_executability_order(self):
675
 
        """Ensure that executability behaves the same, no matter what order.
676
 
        
677
 
        - create file and set executability simultaneously
678
 
        - create file and set executability afterward
679
 
        - unsetting the executability of a file whose executability has not been
680
 
        declared should throw an exception (this may happen when a
681
 
        merge attempts to create a file with a duplicate ID)
682
 
        """
683
 
        transform, root = self.get_transform()
684
 
        wt = transform._tree
685
 
        wt.lock_read()
686
 
        self.addCleanup(wt.unlock)
687
 
        transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
688
 
                           True)
689
 
        sac = transform.new_file('set_after_creation', root,
690
 
                                 'Set after creation', 'sac')
691
 
        transform.set_executability(True, sac)
692
 
        uws = transform.new_file('unset_without_set', root, 'Unset badly',
693
 
                                 'uws')
694
 
        self.assertRaises(KeyError, transform.set_executability, None, uws)
695
 
        transform.apply()
696
 
        self.assertTrue(wt.is_executable('soc'))
697
 
        self.assertTrue(wt.is_executable('sac'))
698
 
 
699
 
    def test_preserve_mode(self):
700
 
        """File mode is preserved when replacing content"""
701
 
        if sys.platform == 'win32':
702
 
            raise TestSkipped('chmod has no effect on win32')
703
 
        transform, root = self.get_transform()
704
 
        transform.new_file('file1', root, 'contents', 'file1-id', True)
705
 
        transform.apply()
706
 
        self.assertTrue(self.wt.is_executable('file1-id'))
707
 
        transform, root = self.get_transform()
708
 
        file1_id = transform.trans_id_tree_file_id('file1-id')
709
 
        transform.delete_contents(file1_id)
710
 
        transform.create_file('contents2', file1_id)
711
 
        transform.apply()
712
 
        self.assertTrue(self.wt.is_executable('file1-id'))
713
 
 
714
 
    def test__set_mode_stats_correctly(self):
715
 
        """_set_mode stats to determine file mode."""
716
 
        if sys.platform == 'win32':
717
 
            raise TestSkipped('chmod has no effect on win32')
718
 
 
719
 
        stat_paths = []
720
 
        real_stat = os.stat
721
 
        def instrumented_stat(path):
722
 
            stat_paths.append(path)
723
 
            return real_stat(path)
724
 
 
725
 
        transform, root = self.get_transform()
726
 
 
727
 
        bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
728
 
                                     file_id='bar-id-1', executable=False)
729
 
        transform.apply()
730
 
 
731
 
        transform, root = self.get_transform()
732
 
        bar1_id = transform.trans_id_tree_path('bar')
733
 
        bar2_id = transform.trans_id_tree_path('bar2')
734
 
        try:
735
 
            os.stat = instrumented_stat
736
 
            transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
737
 
        finally:
738
 
            os.stat = real_stat
739
 
            transform.finalize()
740
 
 
741
 
        bar1_abspath = self.wt.abspath('bar')
742
 
        self.assertEqual([bar1_abspath], stat_paths)
743
 
 
744
 
    def test_iter_changes(self):
745
 
        self.wt.set_root_id('eert_toor')
746
 
        transform, root = self.get_transform()
747
 
        transform.new_file('old', root, 'blah', 'id-1', True)
748
 
        transform.apply()
749
 
        transform, root = self.get_transform()
750
 
        try:
751
 
            self.assertEqual([], list(transform._iter_changes()))
752
 
            old = transform.trans_id_tree_file_id('id-1')
753
 
            transform.unversion_file(old)
754
 
            self.assertEqual([('id-1', ('old', None), False, (True, False),
755
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
756
 
                (True, True))], list(transform._iter_changes()))
757
 
            transform.new_directory('new', root, 'id-1')
758
 
            self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
759
 
                ('eert_toor', 'eert_toor'), ('old', 'new'),
760
 
                ('file', 'directory'),
761
 
                (True, False))], list(transform._iter_changes()))
762
 
        finally:
763
 
            transform.finalize()
764
 
 
765
 
    def test_iter_changes_new(self):
766
 
        self.wt.set_root_id('eert_toor')
767
 
        transform, root = self.get_transform()
768
 
        transform.new_file('old', root, 'blah')
769
 
        transform.apply()
770
 
        transform, root = self.get_transform()
771
 
        try:
772
 
            old = transform.trans_id_tree_path('old')
773
 
            transform.version_file('id-1', old)
774
 
            self.assertEqual([('id-1', (None, 'old'), False, (False, True),
775
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
776
 
                (False, False))], list(transform._iter_changes()))
777
 
        finally:
778
 
            transform.finalize()
779
 
 
780
 
    def test_iter_changes_modifications(self):
781
 
        self.wt.set_root_id('eert_toor')
782
 
        transform, root = self.get_transform()
783
 
        transform.new_file('old', root, 'blah', 'id-1')
784
 
        transform.new_file('new', root, 'blah')
785
 
        transform.new_directory('subdir', root, 'subdir-id')
786
 
        transform.apply()
787
 
        transform, root = self.get_transform()
788
 
        try:
789
 
            old = transform.trans_id_tree_path('old')
790
 
            subdir = transform.trans_id_tree_file_id('subdir-id')
791
 
            new = transform.trans_id_tree_path('new')
792
 
            self.assertEqual([], list(transform._iter_changes()))
793
 
 
794
 
            #content deletion
795
 
            transform.delete_contents(old)
796
 
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
797
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
798
 
                (False, False))], list(transform._iter_changes()))
799
 
 
800
 
            #content change
801
 
            transform.create_file('blah', old)
802
 
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
803
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
804
 
                (False, False))], list(transform._iter_changes()))
805
 
            transform.cancel_deletion(old)
806
 
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
807
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
808
 
                (False, False))], list(transform._iter_changes()))
809
 
            transform.cancel_creation(old)
810
 
 
811
 
            # move file_id to a different file
812
 
            self.assertEqual([], list(transform._iter_changes()))
813
 
            transform.unversion_file(old)
814
 
            transform.version_file('id-1', new)
815
 
            transform.adjust_path('old', root, new)
816
 
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
817
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
818
 
                (False, False))], list(transform._iter_changes()))
819
 
            transform.cancel_versioning(new)
820
 
            transform._removed_id = set()
821
 
 
822
 
            #execute bit
823
 
            self.assertEqual([], list(transform._iter_changes()))
824
 
            transform.set_executability(True, old)
825
 
            self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
826
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
827
 
                (False, True))], list(transform._iter_changes()))
828
 
            transform.set_executability(None, old)
829
 
 
830
 
            # filename
831
 
            self.assertEqual([], list(transform._iter_changes()))
832
 
            transform.adjust_path('new', root, old)
833
 
            transform._new_parent = {}
834
 
            self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
835
 
                ('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
836
 
                (False, False))], list(transform._iter_changes()))
837
 
            transform._new_name = {}
838
 
 
839
 
            # parent directory
840
 
            self.assertEqual([], list(transform._iter_changes()))
841
 
            transform.adjust_path('new', subdir, old)
842
 
            transform._new_name = {}
843
 
            self.assertEqual([('id-1', ('old', 'subdir/old'), False,
844
 
                (True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
845
 
                ('file', 'file'), (False, False))],
846
 
                list(transform._iter_changes()))
847
 
            transform._new_path = {}
848
 
 
849
 
        finally:
850
 
            transform.finalize()
851
 
 
852
 
    def test_iter_changes_modified_bleed(self):
853
 
        self.wt.set_root_id('eert_toor')
854
 
        """Modified flag should not bleed from one change to another"""
855
 
        # unfortunately, we have no guarantee that file1 (which is modified)
856
 
        # will be applied before file2.  And if it's applied after file2, it
857
 
        # obviously can't bleed into file2's change output.  But for now, it
858
 
        # works.
859
 
        transform, root = self.get_transform()
860
 
        transform.new_file('file1', root, 'blah', 'id-1')
861
 
        transform.new_file('file2', root, 'blah', 'id-2')
862
 
        transform.apply()
863
 
        transform, root = self.get_transform()
864
 
        try:
865
 
            transform.delete_contents(transform.trans_id_file_id('id-1'))
866
 
            transform.set_executability(True,
867
 
            transform.trans_id_file_id('id-2'))
868
 
            self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
869
 
                ('eert_toor', 'eert_toor'), ('file1', u'file1'),
870
 
                ('file', None), (False, False)),
871
 
                ('id-2', (u'file2', u'file2'), False, (True, True),
872
 
                ('eert_toor', 'eert_toor'), ('file2', u'file2'),
873
 
                ('file', 'file'), (False, True))],
874
 
                list(transform._iter_changes()))
875
 
        finally:
876
 
            transform.finalize()
877
 
 
878
 
    def test_iter_changes_move_missing(self):
879
 
        """Test moving ids with no files around"""
880
 
        self.wt.set_root_id('toor_eert')
881
 
        # Need two steps because versioning a non-existant file is a conflict.
882
 
        transform, root = self.get_transform()
883
 
        transform.new_directory('floater', root, 'floater-id')
884
 
        transform.apply()
885
 
        transform, root = self.get_transform()
886
 
        transform.delete_contents(transform.trans_id_tree_path('floater'))
887
 
        transform.apply()
888
 
        transform, root = self.get_transform()
889
 
        floater = transform.trans_id_tree_path('floater')
890
 
        try:
891
 
            transform.adjust_path('flitter', root, floater)
892
 
            self.assertEqual([('floater-id', ('floater', 'flitter'), False,
893
 
            (True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
894
 
            (None, None), (False, False))], list(transform._iter_changes()))
895
 
        finally:
896
 
            transform.finalize()
897
 
 
898
 
    def test_iter_changes_pointless(self):
899
 
        """Ensure that no-ops are not treated as modifications"""
900
 
        self.wt.set_root_id('eert_toor')
901
 
        transform, root = self.get_transform()
902
 
        transform.new_file('old', root, 'blah', 'id-1')
903
 
        transform.new_directory('subdir', root, 'subdir-id')
904
 
        transform.apply()
905
 
        transform, root = self.get_transform()
906
 
        try:
907
 
            old = transform.trans_id_tree_path('old')
908
 
            subdir = transform.trans_id_tree_file_id('subdir-id')
909
 
            self.assertEqual([], list(transform._iter_changes()))
910
 
            transform.delete_contents(subdir)
911
 
            transform.create_directory(subdir)
912
 
            transform.set_executability(False, old)
913
 
            transform.unversion_file(old)
914
 
            transform.version_file('id-1', old)
915
 
            transform.adjust_path('old', root, old)
916
 
            self.assertEqual([], list(transform._iter_changes()))
917
 
        finally:
918
 
            transform.finalize()
919
 
 
920
 
    def test_rename_count(self):
921
 
        transform, root = self.get_transform()
922
 
        transform.new_file('name1', root, 'contents')
923
 
        self.assertEqual(transform.rename_count, 0)
924
 
        transform.apply()
925
 
        self.assertEqual(transform.rename_count, 1)
926
 
        transform2, root = self.get_transform()
927
 
        transform2.adjust_path('name2', root,
928
 
                               transform2.trans_id_tree_path('name1'))
929
 
        self.assertEqual(transform2.rename_count, 0)
930
 
        transform2.apply()
931
 
        self.assertEqual(transform2.rename_count, 2)
932
 
 
933
 
    def test_change_parent(self):
934
 
        """Ensure that after we change a parent, the results are still right.
935
 
 
936
 
        Renames and parent changes on pending transforms can happen as part
937
 
        of conflict resolution, and are explicitly permitted by the
938
 
        TreeTransform API.
939
 
 
940
 
        This test ensures they work correctly with the rename-avoidance
941
 
        optimization.
942
 
        """
943
 
        transform, root = self.get_transform()
944
 
        parent1 = transform.new_directory('parent1', root)
945
 
        child1 = transform.new_file('child1', parent1, 'contents')
946
 
        parent2 = transform.new_directory('parent2', root)
947
 
        transform.adjust_path('child1', parent2, child1)
948
 
        transform.apply()
949
 
        self.failIfExists(self.wt.abspath('parent1/child1'))
950
 
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
951
 
        # rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
952
 
        # no rename for child1 (counting only renames during apply)
953
 
        self.failUnlessEqual(2, transform.rename_count)
954
 
 
955
 
    def test_cancel_parent(self):
956
 
        """Cancelling a parent doesn't cause deletion of a non-empty directory
957
 
 
958
 
        This is like the test_change_parent, except that we cancel the parent
959
 
        before adjusting the path.  The transform must detect that the
960
 
        directory is non-empty, and move children to safe locations.
961
 
        """
962
 
        transform, root = self.get_transform()
963
 
        parent1 = transform.new_directory('parent1', root)
964
 
        child1 = transform.new_file('child1', parent1, 'contents')
965
 
        child2 = transform.new_file('child2', parent1, 'contents')
966
 
        try:
967
 
            transform.cancel_creation(parent1)
968
 
        except OSError:
969
 
            self.fail('Failed to move child1 before deleting parent1')
970
 
        transform.cancel_creation(child2)
971
 
        transform.create_directory(parent1)
972
 
        try:
973
 
            transform.cancel_creation(parent1)
974
 
        # If the transform incorrectly believes that child2 is still in
975
 
        # parent1's limbo directory, it will try to rename it and fail
976
 
        # because was already moved by the first cancel_creation.
977
 
        except OSError:
978
 
            self.fail('Transform still thinks child2 is a child of parent1')
979
 
        parent2 = transform.new_directory('parent2', root)
980
 
        transform.adjust_path('child1', parent2, child1)
981
 
        transform.apply()
982
 
        self.failIfExists(self.wt.abspath('parent1'))
983
 
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
984
 
        # rename limbo/new-3 => parent2, rename limbo/new-2 => child1
985
 
        self.failUnlessEqual(2, transform.rename_count)
986
 
 
987
 
    def test_adjust_and_cancel(self):
988
 
        """Make sure adjust_path keeps track of limbo children properly"""
989
 
        transform, root = self.get_transform()
990
 
        parent1 = transform.new_directory('parent1', root)
991
 
        child1 = transform.new_file('child1', parent1, 'contents')
992
 
        parent2 = transform.new_directory('parent2', root)
993
 
        transform.adjust_path('child1', parent2, child1)
994
 
        transform.cancel_creation(child1)
995
 
        try:
996
 
            transform.cancel_creation(parent1)
997
 
        # if the transform thinks child1 is still in parent1's limbo
998
 
        # directory, it will attempt to move it and fail.
999
 
        except OSError:
1000
 
            self.fail('Transform still thinks child1 is a child of parent1')
1001
 
        transform.finalize()
1002
 
 
1003
 
    def test_noname_contents(self):
1004
 
        """TreeTransform should permit deferring naming files."""
1005
 
        transform, root = self.get_transform()
1006
 
        parent = transform.trans_id_file_id('parent-id')
1007
 
        try:
1008
 
            transform.create_directory(parent)
1009
 
        except KeyError:
1010
 
            self.fail("Can't handle contents with no name")
1011
 
        transform.finalize()
1012
 
 
1013
 
    def test_noname_contents_nested(self):
1014
 
        """TreeTransform should permit deferring naming files."""
1015
 
        transform, root = self.get_transform()
1016
 
        parent = transform.trans_id_file_id('parent-id')
1017
 
        try:
1018
 
            transform.create_directory(parent)
1019
 
        except KeyError:
1020
 
            self.fail("Can't handle contents with no name")
1021
 
        child = transform.new_directory('child', parent)
1022
 
        transform.adjust_path('parent', root, parent)
1023
 
        transform.apply()
1024
 
        self.failUnlessExists(self.wt.abspath('parent/child'))
1025
 
        self.assertEqual(1, transform.rename_count)
1026
 
 
1027
 
    def test_reuse_name(self):
1028
 
        """Avoid reusing the same limbo name for different files"""
1029
 
        transform, root = self.get_transform()
1030
 
        parent = transform.new_directory('parent', root)
1031
 
        child1 = transform.new_directory('child', parent)
1032
 
        try:
1033
 
            child2 = transform.new_directory('child', parent)
1034
 
        except OSError:
1035
 
            self.fail('Tranform tried to use the same limbo name twice')
1036
 
        transform.adjust_path('child2', parent, child2)
1037
 
        transform.apply()
1038
 
        # limbo/new-1 => parent, limbo/new-3 => parent/child2
1039
 
        # child2 is put into top-level limbo because child1 has already
1040
 
        # claimed the direct limbo path when child2 is created.  There is no
1041
 
        # advantage in renaming files once they're in top-level limbo, except
1042
 
        # as part of apply.
1043
 
        self.assertEqual(2, transform.rename_count)
1044
 
 
1045
 
    def test_reuse_when_first_moved(self):
1046
 
        """Don't avoid direct paths when it is safe to use them"""
1047
 
        transform, root = self.get_transform()
1048
 
        parent = transform.new_directory('parent', root)
1049
 
        child1 = transform.new_directory('child', parent)
1050
 
        transform.adjust_path('child1', parent, child1)
1051
 
        child2 = transform.new_directory('child', parent)
1052
 
        transform.apply()
1053
 
        # limbo/new-1 => parent
1054
 
        self.assertEqual(1, transform.rename_count)
1055
 
 
1056
 
    def test_reuse_after_cancel(self):
1057
 
        """Don't avoid direct paths when it is safe to use them"""
1058
 
        transform, root = self.get_transform()
1059
 
        parent2 = transform.new_directory('parent2', root)
1060
 
        child1 = transform.new_directory('child1', parent2)
1061
 
        transform.cancel_creation(parent2)
1062
 
        transform.create_directory(parent2)
1063
 
        child2 = transform.new_directory('child1', parent2)
1064
 
        transform.adjust_path('child2', parent2, child1)
1065
 
        transform.apply()
1066
 
        # limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1067
 
        self.assertEqual(2, transform.rename_count)
1068
 
 
1069
 
    def test_finalize_order(self):
1070
 
        """Finalize must be done in child-to-parent order"""
1071
 
        transform, root = self.get_transform()
1072
 
        parent = transform.new_directory('parent', root)
1073
 
        child = transform.new_directory('child', parent)
1074
 
        try:
1075
 
            transform.finalize()
1076
 
        except OSError:
1077
 
            self.fail('Tried to remove parent before child1')
1078
 
 
1079
 
    def test_cancel_with_cancelled_child_should_succeed(self):
1080
 
        transform, root = self.get_transform()
1081
 
        parent = transform.new_directory('parent', root)
1082
 
        child = transform.new_directory('child', parent)
1083
 
        transform.cancel_creation(child)
1084
 
        transform.cancel_creation(parent)
1085
 
        transform.finalize()
1086
 
 
1087
 
    def test_change_entry(self):
1088
 
        txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
1089
 
        self.callDeprecated([txt], change_entry, None, None, None, None, None,
1090
 
            None, None, None)
1091
 
 
1092
 
    def test_case_insensitive_clash(self):
1093
 
        self.requireFeature(CaseInsensitiveFilesystemFeature)
1094
 
        def tt_helper():
1095
 
            wt = self.make_branch_and_tree('.')
1096
 
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
1097
 
            try:
1098
 
                tt.new_file('foo', tt.root, 'bar')
1099
 
                tt.new_file('Foo', tt.root, 'spam')
1100
 
                # Lie to tt that we've already resolved all conflicts.
1101
 
                tt.apply(no_conflicts=True)
1102
 
            except:
1103
 
                wt.unlock()
1104
 
                raise
1105
 
        err = self.assertRaises(errors.FileExists, tt_helper)
1106
 
        self.assertContainsRe(str(err),
1107
 
            "^File exists: .+/foo")
1108
 
 
1109
 
    def test_two_directories_clash(self):
1110
 
        def tt_helper():
1111
 
            wt = self.make_branch_and_tree('.')
1112
 
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
1113
 
            try:
1114
 
                foo_1 = tt.new_directory('foo', tt.root)
1115
 
                tt.new_directory('bar', foo_1)
1116
 
                foo_2 = tt.new_directory('foo', tt.root)
1117
 
                tt.new_directory('baz', foo_2)
1118
 
                # Lie to tt that we've already resolved all conflicts.
1119
 
                tt.apply(no_conflicts=True)
1120
 
            except:
1121
 
                wt.unlock()
1122
 
                raise
1123
 
        err = self.assertRaises(errors.FileExists, tt_helper)
1124
 
        self.assertContainsRe(str(err),
1125
 
            "^File exists: .+/foo")
1126
 
 
1127
 
    def test_two_directories_clash_finalize(self):
1128
 
        def tt_helper():
1129
 
            wt = self.make_branch_and_tree('.')
1130
 
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
1131
 
            try:
1132
 
                foo_1 = tt.new_directory('foo', tt.root)
1133
 
                tt.new_directory('bar', foo_1)
1134
 
                foo_2 = tt.new_directory('foo', tt.root)
1135
 
                tt.new_directory('baz', foo_2)
1136
 
                # Lie to tt that we've already resolved all conflicts.
1137
 
                tt.apply(no_conflicts=True)
1138
 
            except:
1139
 
                tt.finalize()
1140
 
                raise
1141
 
        err = self.assertRaises(errors.FileExists, tt_helper)
1142
 
        self.assertContainsRe(str(err),
1143
 
            "^File exists: .+/foo")
1144
 
 
1145
 
 
1146
 
class TransformGroup(object):
1147
 
 
1148
 
    def __init__(self, dirname, root_id):
1149
 
        self.name = dirname
1150
 
        os.mkdir(dirname)
1151
 
        self.wt = BzrDir.create_standalone_workingtree(dirname)
1152
 
        self.wt.set_root_id(root_id)
1153
 
        self.b = self.wt.branch
1154
 
        self.tt = TreeTransform(self.wt)
1155
 
        self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1156
 
 
1157
 
 
1158
 
def conflict_text(tree, merge):
1159
 
    template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1160
 
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1161
 
 
1162
 
 
1163
 
class TestTransformMerge(TestCaseInTempDir):
1164
 
    def test_text_merge(self):
1165
 
        root_id = generate_ids.gen_root_id()
1166
 
        base = TransformGroup("base", root_id)
1167
 
        base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1168
 
        base.tt.new_file('b', base.root, 'b1', 'b')
1169
 
        base.tt.new_file('c', base.root, 'c', 'c')
1170
 
        base.tt.new_file('d', base.root, 'd', 'd')
1171
 
        base.tt.new_file('e', base.root, 'e', 'e')
1172
 
        base.tt.new_file('f', base.root, 'f', 'f')
1173
 
        base.tt.new_directory('g', base.root, 'g')
1174
 
        base.tt.new_directory('h', base.root, 'h')
1175
 
        base.tt.apply()
1176
 
        other = TransformGroup("other", root_id)
1177
 
        other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1178
 
        other.tt.new_file('b', other.root, 'b2', 'b')
1179
 
        other.tt.new_file('c', other.root, 'c2', 'c')
1180
 
        other.tt.new_file('d', other.root, 'd', 'd')
1181
 
        other.tt.new_file('e', other.root, 'e2', 'e')
1182
 
        other.tt.new_file('f', other.root, 'f', 'f')
1183
 
        other.tt.new_file('g', other.root, 'g', 'g')
1184
 
        other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1185
 
        other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1186
 
        other.tt.apply()
1187
 
        this = TransformGroup("this", root_id)
1188
 
        this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1189
 
        this.tt.new_file('b', this.root, 'b', 'b')
1190
 
        this.tt.new_file('c', this.root, 'c', 'c')
1191
 
        this.tt.new_file('d', this.root, 'd2', 'd')
1192
 
        this.tt.new_file('e', this.root, 'e2', 'e')
1193
 
        this.tt.new_file('f', this.root, 'f', 'f')
1194
 
        this.tt.new_file('g', this.root, 'g', 'g')
1195
 
        this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1196
 
        this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1197
 
        this.tt.apply()
1198
 
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1199
 
        # textual merge
1200
 
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1201
 
        # three-way text conflict
1202
 
        self.assertEqual(this.wt.get_file('b').read(), 
1203
 
                         conflict_text('b', 'b2'))
1204
 
        # OTHER wins
1205
 
        self.assertEqual(this.wt.get_file('c').read(), 'c2')
1206
 
        # THIS wins
1207
 
        self.assertEqual(this.wt.get_file('d').read(), 'd2')
1208
 
        # Ambigious clean merge
1209
 
        self.assertEqual(this.wt.get_file('e').read(), 'e2')
1210
 
        # No change
1211
 
        self.assertEqual(this.wt.get_file('f').read(), 'f')
1212
 
        # Correct correct results when THIS == OTHER 
1213
 
        self.assertEqual(this.wt.get_file('g').read(), 'g')
1214
 
        # Text conflict when THIS & OTHER are text and BASE is dir
1215
 
        self.assertEqual(this.wt.get_file('h').read(), 
1216
 
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1217
 
        self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1218
 
                         '1\n2\n3\n4\n')
1219
 
        self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1220
 
                         'h\ni\nj\nk\n')
1221
 
        self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1222
 
        self.assertEqual(this.wt.get_file('i').read(), 
1223
 
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1224
 
        self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1225
 
                         '1\n2\n3\n4\n')
1226
 
        self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1227
 
                         'h\ni\nj\nk\n')
1228
 
        self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1229
 
        modified = ['a', 'b', 'c', 'h', 'i']
1230
 
        merge_modified = this.wt.merge_modified()
1231
 
        self.assertSubset(merge_modified, modified)
1232
 
        self.assertEqual(len(merge_modified), len(modified))
1233
 
        file(this.wt.id2abspath('a'), 'wb').write('booga')
1234
 
        modified.pop(0)
1235
 
        merge_modified = this.wt.merge_modified()
1236
 
        self.assertSubset(merge_modified, modified)
1237
 
        self.assertEqual(len(merge_modified), len(modified))
1238
 
        this.wt.remove('b')
1239
 
        this.wt.revert()
1240
 
 
1241
 
    def test_file_merge(self):
1242
 
        self.requireFeature(SymlinkFeature)
1243
 
        root_id = generate_ids.gen_root_id()
1244
 
        base = TransformGroup("BASE", root_id)
1245
 
        this = TransformGroup("THIS", root_id)
1246
 
        other = TransformGroup("OTHER", root_id)
1247
 
        for tg in this, base, other:
1248
 
            tg.tt.new_directory('a', tg.root, 'a')
1249
 
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
1250
 
            tg.tt.new_file('c', tg.root, 'c', 'c')
1251
 
            tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1252
 
        targets = ((base, 'base-e', 'base-f', None, None), 
1253
 
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'), 
1254
 
                   (other, 'other-e', None, 'other-g', 'other-h'))
1255
 
        for tg, e_target, f_target, g_target, h_target in targets:
1256
 
            for link, target in (('e', e_target), ('f', f_target), 
1257
 
                                 ('g', g_target), ('h', h_target)):
1258
 
                if target is not None:
1259
 
                    tg.tt.new_symlink(link, tg.root, target, link)
1260
 
 
1261
 
        for tg in this, base, other:
1262
 
            tg.tt.apply()
1263
 
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1264
 
        self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1265
 
        self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1266
 
        self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1267
 
        for suffix in ('THIS', 'BASE', 'OTHER'):
1268
 
            self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1269
 
        self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1270
 
        self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1271
 
        self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1272
 
        self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1273
 
        self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1274
 
        self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1275
 
        self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1276
 
        self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1277
 
        self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1278
 
        self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1279
 
        self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1280
 
        self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1281
 
        self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1282
 
 
1283
 
    def test_filename_merge(self):
1284
 
        root_id = generate_ids.gen_root_id()
1285
 
        base = TransformGroup("BASE", root_id)
1286
 
        this = TransformGroup("THIS", root_id)
1287
 
        other = TransformGroup("OTHER", root_id)
1288
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
1289
 
                                   for t in [base, this, other]]
1290
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
1291
 
                                   for t in [base, this, other]]
1292
 
        base.tt.new_directory('c', base_a, 'c')
1293
 
        this.tt.new_directory('c1', this_a, 'c')
1294
 
        other.tt.new_directory('c', other_b, 'c')
1295
 
 
1296
 
        base.tt.new_directory('d', base_a, 'd')
1297
 
        this.tt.new_directory('d1', this_b, 'd')
1298
 
        other.tt.new_directory('d', other_a, 'd')
1299
 
 
1300
 
        base.tt.new_directory('e', base_a, 'e')
1301
 
        this.tt.new_directory('e', this_a, 'e')
1302
 
        other.tt.new_directory('e1', other_b, 'e')
1303
 
 
1304
 
        base.tt.new_directory('f', base_a, 'f')
1305
 
        this.tt.new_directory('f1', this_b, 'f')
1306
 
        other.tt.new_directory('f1', other_b, 'f')
1307
 
 
1308
 
        for tg in [this, base, other]:
1309
 
            tg.tt.apply()
1310
 
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1311
 
        self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1312
 
        self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1313
 
        self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1314
 
        self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1315
 
 
1316
 
    def test_filename_merge_conflicts(self):
1317
 
        root_id = generate_ids.gen_root_id()
1318
 
        base = TransformGroup("BASE", root_id)
1319
 
        this = TransformGroup("THIS", root_id)
1320
 
        other = TransformGroup("OTHER", root_id)
1321
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
1322
 
                                   for t in [base, this, other]]
1323
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
1324
 
                                   for t in [base, this, other]]
1325
 
 
1326
 
        base.tt.new_file('g', base_a, 'g', 'g')
1327
 
        other.tt.new_file('g1', other_b, 'g1', 'g')
1328
 
 
1329
 
        base.tt.new_file('h', base_a, 'h', 'h')
1330
 
        this.tt.new_file('h1', this_b, 'h1', 'h')
1331
 
 
1332
 
        base.tt.new_file('i', base.root, 'i', 'i')
1333
 
        other.tt.new_directory('i1', this_b, 'i')
1334
 
 
1335
 
        for tg in [this, base, other]:
1336
 
            tg.tt.apply()
1337
 
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1338
 
 
1339
 
        self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1340
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1341
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1342
 
        self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1343
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1344
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1345
 
        self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1346
 
 
1347
 
 
1348
 
class TestBuildTree(tests.TestCaseWithTransport):
1349
 
 
1350
 
    def test_build_tree_with_symlinks(self):
1351
 
        self.requireFeature(SymlinkFeature)
1352
 
        os.mkdir('a')
1353
 
        a = BzrDir.create_standalone_workingtree('a')
1354
 
        os.mkdir('a/foo')
1355
 
        file('a/foo/bar', 'wb').write('contents')
1356
 
        os.symlink('a/foo/bar', 'a/foo/baz')
1357
 
        a.add(['foo', 'foo/bar', 'foo/baz'])
1358
 
        a.commit('initial commit')
1359
 
        b = BzrDir.create_standalone_workingtree('b')
1360
 
        basis = a.basis_tree()
1361
 
        basis.lock_read()
1362
 
        self.addCleanup(basis.unlock)
1363
 
        build_tree(basis, b)
1364
 
        self.assertIs(os.path.isdir('b/foo'), True)
1365
 
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1366
 
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1367
 
 
1368
 
    def test_build_with_references(self):
1369
 
        tree = self.make_branch_and_tree('source',
1370
 
            format='dirstate-with-subtree')
1371
 
        subtree = self.make_branch_and_tree('source/subtree',
1372
 
            format='dirstate-with-subtree')
1373
 
        tree.add_reference(subtree)
1374
 
        tree.commit('a revision')
1375
 
        tree.branch.create_checkout('target')
1376
 
        self.failUnlessExists('target')
1377
 
        self.failUnlessExists('target/subtree')
1378
 
 
1379
 
    def test_file_conflict_handling(self):
1380
 
        """Ensure that when building trees, conflict handling is done"""
1381
 
        source = self.make_branch_and_tree('source')
1382
 
        target = self.make_branch_and_tree('target')
1383
 
        self.build_tree(['source/file', 'target/file'])
1384
 
        source.add('file', 'new-file')
1385
 
        source.commit('added file')
1386
 
        build_tree(source.basis_tree(), target)
1387
 
        self.assertEqual([DuplicateEntry('Moved existing file to',
1388
 
                          'file.moved', 'file', None, 'new-file')],
1389
 
                         target.conflicts())
1390
 
        target2 = self.make_branch_and_tree('target2')
1391
 
        target_file = file('target2/file', 'wb')
1392
 
        try:
1393
 
            source_file = file('source/file', 'rb')
1394
 
            try:
1395
 
                target_file.write(source_file.read())
1396
 
            finally:
1397
 
                source_file.close()
1398
 
        finally:
1399
 
            target_file.close()
1400
 
        build_tree(source.basis_tree(), target2)
1401
 
        self.assertEqual([], target2.conflicts())
1402
 
 
1403
 
    def test_symlink_conflict_handling(self):
1404
 
        """Ensure that when building trees, conflict handling is done"""
1405
 
        self.requireFeature(SymlinkFeature)
1406
 
        source = self.make_branch_and_tree('source')
1407
 
        os.symlink('foo', 'source/symlink')
1408
 
        source.add('symlink', 'new-symlink')
1409
 
        source.commit('added file')
1410
 
        target = self.make_branch_and_tree('target')
1411
 
        os.symlink('bar', 'target/symlink')
1412
 
        build_tree(source.basis_tree(), target)
1413
 
        self.assertEqual([DuplicateEntry('Moved existing file to',
1414
 
            'symlink.moved', 'symlink', None, 'new-symlink')],
1415
 
            target.conflicts())
1416
 
        target = self.make_branch_and_tree('target2')
1417
 
        os.symlink('foo', 'target2/symlink')
1418
 
        build_tree(source.basis_tree(), target)
1419
 
        self.assertEqual([], target.conflicts())
1420
 
        
1421
 
    def test_directory_conflict_handling(self):
1422
 
        """Ensure that when building trees, conflict handling is done"""
1423
 
        source = self.make_branch_and_tree('source')
1424
 
        target = self.make_branch_and_tree('target')
1425
 
        self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1426
 
        source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1427
 
        source.commit('added file')
1428
 
        build_tree(source.basis_tree(), target)
1429
 
        self.assertEqual([], target.conflicts())
1430
 
        self.failUnlessExists('target/dir1/file')
1431
 
 
1432
 
        # Ensure contents are merged
1433
 
        target = self.make_branch_and_tree('target2')
1434
 
        self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1435
 
        build_tree(source.basis_tree(), target)
1436
 
        self.assertEqual([], target.conflicts())
1437
 
        self.failUnlessExists('target2/dir1/file2')
1438
 
        self.failUnlessExists('target2/dir1/file')
1439
 
 
1440
 
        # Ensure new contents are suppressed for existing branches
1441
 
        target = self.make_branch_and_tree('target3')
1442
 
        self.make_branch('target3/dir1')
1443
 
        self.build_tree(['target3/dir1/file2'])
1444
 
        build_tree(source.basis_tree(), target)
1445
 
        self.failIfExists('target3/dir1/file')
1446
 
        self.failUnlessExists('target3/dir1/file2')
1447
 
        self.failUnlessExists('target3/dir1.diverted/file')
1448
 
        self.assertEqual([DuplicateEntry('Diverted to',
1449
 
            'dir1.diverted', 'dir1', 'new-dir1', None)],
1450
 
            target.conflicts())
1451
 
 
1452
 
        target = self.make_branch_and_tree('target4')
1453
 
        self.build_tree(['target4/dir1/'])
1454
 
        self.make_branch('target4/dir1/file')
1455
 
        build_tree(source.basis_tree(), target)
1456
 
        self.failUnlessExists('target4/dir1/file')
1457
 
        self.assertEqual('directory', file_kind('target4/dir1/file'))
1458
 
        self.failUnlessExists('target4/dir1/file.diverted')
1459
 
        self.assertEqual([DuplicateEntry('Diverted to',
1460
 
            'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1461
 
            target.conflicts())
1462
 
 
1463
 
    def test_mixed_conflict_handling(self):
1464
 
        """Ensure that when building trees, conflict handling is done"""
1465
 
        source = self.make_branch_and_tree('source')
1466
 
        target = self.make_branch_and_tree('target')
1467
 
        self.build_tree(['source/name', 'target/name/'])
1468
 
        source.add('name', 'new-name')
1469
 
        source.commit('added file')
1470
 
        build_tree(source.basis_tree(), target)
1471
 
        self.assertEqual([DuplicateEntry('Moved existing file to',
1472
 
            'name.moved', 'name', None, 'new-name')], target.conflicts())
1473
 
 
1474
 
    def test_raises_in_populated(self):
1475
 
        source = self.make_branch_and_tree('source')
1476
 
        self.build_tree(['source/name'])
1477
 
        source.add('name')
1478
 
        source.commit('added name')
1479
 
        target = self.make_branch_and_tree('target')
1480
 
        self.build_tree(['target/name'])
1481
 
        target.add('name')
1482
 
        self.assertRaises(errors.WorkingTreeAlreadyPopulated, 
1483
 
            build_tree, source.basis_tree(), target)
1484
 
 
1485
 
    def test_build_tree_rename_count(self):
1486
 
        source = self.make_branch_and_tree('source')
1487
 
        self.build_tree(['source/file1', 'source/dir1/'])
1488
 
        source.add(['file1', 'dir1'])
1489
 
        source.commit('add1')
1490
 
        target1 = self.make_branch_and_tree('target1')
1491
 
        transform_result = build_tree(source.basis_tree(), target1)
1492
 
        self.assertEqual(2, transform_result.rename_count)
1493
 
 
1494
 
        self.build_tree(['source/dir1/file2'])
1495
 
        source.add(['dir1/file2'])
1496
 
        source.commit('add3')
1497
 
        target2 = self.make_branch_and_tree('target2')
1498
 
        transform_result = build_tree(source.basis_tree(), target2)
1499
 
        # children of non-root directories should not be renamed
1500
 
        self.assertEqual(2, transform_result.rename_count)
1501
 
 
1502
 
    def test_build_tree_accelerator_tree(self):
1503
 
        source = self.make_branch_and_tree('source')
1504
 
        self.build_tree_contents([('source/file1', 'A')])
1505
 
        self.build_tree_contents([('source/file2', 'B')])
1506
 
        source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1507
 
        source.commit('commit files')
1508
 
        self.build_tree_contents([('source/file2', 'C')])
1509
 
        calls = []
1510
 
        real_source_get_file = source.get_file
1511
 
        def get_file(file_id, path=None):
1512
 
            calls.append(file_id)
1513
 
            return real_source_get_file(file_id, path)
1514
 
        source.get_file = get_file
1515
 
        target = self.make_branch_and_tree('target')
1516
 
        build_tree(source.basis_tree(), target, source)
1517
 
        self.assertEqual(['file1-id'], calls)
1518
 
 
1519
 
    def test_build_tree_accelerator_tree_missing_file(self):
1520
 
        source = self.make_branch_and_tree('source')
1521
 
        self.build_tree_contents([('source/file1', 'A')])
1522
 
        self.build_tree_contents([('source/file2', 'B')])
1523
 
        source.add(['file1', 'file2'])
1524
 
        source.commit('commit files')
1525
 
        os.unlink('source/file1')
1526
 
        source.remove(['file2'])
1527
 
        target = self.make_branch_and_tree('target')
1528
 
        build_tree(source.basis_tree(), target, source)
1529
 
 
1530
 
 
1531
 
class MockTransform(object):
1532
 
 
1533
 
    def has_named_child(self, by_parent, parent_id, name):
1534
 
        for child_id in by_parent[parent_id]:
1535
 
            if child_id == '0':
1536
 
                if name == "name~":
1537
 
                    return True
1538
 
            elif name == "name.~%s~" % child_id:
1539
 
                return True
1540
 
        return False
1541
 
 
1542
 
 
1543
 
class MockEntry(object):
1544
 
    def __init__(self):
1545
 
        object.__init__(self)
1546
 
        self.name = "name"
1547
 
 
1548
 
 
1549
 
class TestGetBackupName(TestCase):
1550
 
    def test_get_backup_name(self):
1551
 
        tt = MockTransform()
1552
 
        name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1553
 
        self.assertEqual(name, 'name.~1~')
1554
 
        name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1555
 
        self.assertEqual(name, 'name.~2~')
1556
 
        name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1557
 
        self.assertEqual(name, 'name.~1~')
1558
 
        name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1559
 
        self.assertEqual(name, 'name.~1~')
1560
 
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1561
 
        self.assertEqual(name, 'name.~4~')
1562
 
 
1563
 
 
1564
 
class TestFileMover(tests.TestCaseWithTransport):
1565
 
 
1566
 
    def test_file_mover(self):
1567
 
        self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1568
 
        mover = _FileMover()
1569
 
        mover.rename('a', 'q')
1570
 
        self.failUnlessExists('q')
1571
 
        self.failIfExists('a')
1572
 
        self.failUnlessExists('q/b')
1573
 
        self.failUnlessExists('c')
1574
 
        self.failUnlessExists('c/d')
1575
 
 
1576
 
    def test_pre_delete_rollback(self):
1577
 
        self.build_tree(['a/'])
1578
 
        mover = _FileMover()
1579
 
        mover.pre_delete('a', 'q')
1580
 
        self.failUnlessExists('q')
1581
 
        self.failIfExists('a')
1582
 
        mover.rollback()
1583
 
        self.failIfExists('q')
1584
 
        self.failUnlessExists('a')
1585
 
 
1586
 
    def test_apply_deletions(self):
1587
 
        self.build_tree(['a/', 'b/'])
1588
 
        mover = _FileMover()
1589
 
        mover.pre_delete('a', 'q')
1590
 
        mover.pre_delete('b', 'r')
1591
 
        self.failUnlessExists('q')
1592
 
        self.failUnlessExists('r')
1593
 
        self.failIfExists('a')
1594
 
        self.failIfExists('b')
1595
 
        mover.apply_deletions()
1596
 
        self.failIfExists('q')
1597
 
        self.failIfExists('r')
1598
 
        self.failIfExists('a')
1599
 
        self.failIfExists('b')
1600
 
 
1601
 
    def test_file_mover_rollback(self):
1602
 
        self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1603
 
        mover = _FileMover()
1604
 
        mover.rename('c/d', 'c/f')
1605
 
        mover.rename('c/e', 'c/d')
1606
 
        try:
1607
 
            mover.rename('a', 'c')
1608
 
        except errors.FileExists, e:
1609
 
            mover.rollback()
1610
 
        self.failUnlessExists('a')
1611
 
        self.failUnlessExists('c/d')
1612
 
 
1613
 
 
1614
 
class Bogus(Exception):
1615
 
    pass
1616
 
 
1617
 
 
1618
 
class TestTransformRollback(tests.TestCaseWithTransport):
1619
 
 
1620
 
    class ExceptionFileMover(_FileMover):
1621
 
 
1622
 
        def __init__(self, bad_source=None, bad_target=None):
1623
 
            _FileMover.__init__(self)
1624
 
            self.bad_source = bad_source
1625
 
            self.bad_target = bad_target
1626
 
 
1627
 
        def rename(self, source, target):
1628
 
            if (self.bad_source is not None and
1629
 
                source.endswith(self.bad_source)):
1630
 
                raise Bogus
1631
 
            elif (self.bad_target is not None and
1632
 
                target.endswith(self.bad_target)):
1633
 
                raise Bogus
1634
 
            else:
1635
 
                _FileMover.rename(self, source, target)
1636
 
 
1637
 
    def test_rollback_rename(self):
1638
 
        tree = self.make_branch_and_tree('.')
1639
 
        self.build_tree(['a/', 'a/b'])
1640
 
        tt = TreeTransform(tree)
1641
 
        self.addCleanup(tt.finalize)
1642
 
        a_id = tt.trans_id_tree_path('a')
1643
 
        tt.adjust_path('c', tt.root, a_id)
1644
 
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1645
 
        self.assertRaises(Bogus, tt.apply,
1646
 
                          _mover=self.ExceptionFileMover(bad_source='a'))
1647
 
        self.failUnlessExists('a')
1648
 
        self.failUnlessExists('a/b')
1649
 
        tt.apply()
1650
 
        self.failUnlessExists('c')
1651
 
        self.failUnlessExists('c/d')
1652
 
 
1653
 
    def test_rollback_rename_into_place(self):
1654
 
        tree = self.make_branch_and_tree('.')
1655
 
        self.build_tree(['a/', 'a/b'])
1656
 
        tt = TreeTransform(tree)
1657
 
        self.addCleanup(tt.finalize)
1658
 
        a_id = tt.trans_id_tree_path('a')
1659
 
        tt.adjust_path('c', tt.root, a_id)
1660
 
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1661
 
        self.assertRaises(Bogus, tt.apply,
1662
 
                          _mover=self.ExceptionFileMover(bad_target='c/d'))
1663
 
        self.failUnlessExists('a')
1664
 
        self.failUnlessExists('a/b')
1665
 
        tt.apply()
1666
 
        self.failUnlessExists('c')
1667
 
        self.failUnlessExists('c/d')
1668
 
 
1669
 
    def test_rollback_deletion(self):
1670
 
        tree = self.make_branch_and_tree('.')
1671
 
        self.build_tree(['a/', 'a/b'])
1672
 
        tt = TreeTransform(tree)
1673
 
        self.addCleanup(tt.finalize)
1674
 
        a_id = tt.trans_id_tree_path('a')
1675
 
        tt.delete_contents(a_id)
1676
 
        tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
1677
 
        self.assertRaises(Bogus, tt.apply,
1678
 
                          _mover=self.ExceptionFileMover(bad_target='d'))
1679
 
        self.failUnlessExists('a')
1680
 
        self.failUnlessExists('a/b')
1681
 
 
1682
 
    def test_resolve_no_parent(self):
1683
 
        wt = self.make_branch_and_tree('.')
1684
 
        tt = TreeTransform(wt)
1685
 
        self.addCleanup(tt.finalize)
1686
 
        parent = tt.trans_id_file_id('parent-id')
1687
 
        tt.new_file('file', parent, 'Contents')
1688
 
        resolve_conflicts(tt)