/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: Martin Pool
  • Date: 2007-12-05 03:26:37 UTC
  • mto: This revision was merged to the branch mainline in revision 3085.
  • Revision ID: mbp@sourcefrog.net-20071205032637-7i5ptn1jd67196ea
test__create_temp_file_with_commit_template_in_unicode_dir needs a unicode filesystem

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