/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

  • Committer: Alexander Belchenko
  • Date: 2007-11-28 21:04:08 UTC
  • mto: This revision was merged to the branch mainline in revision 3044.
  • Revision ID: bialix@ukr.net-20071128210408-bfc2z901pk1e6fje
skip test_workingtree.TestWorkingTree.test_case_sensitive for WT2

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