/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: Canonical.com Patch Queue Manager
  • Date: 2007-11-28 05:35:06 UTC
  • mfrom: (3034.3.1 164637-delta-order)
  • Revision ID: pqm@pqm.ubuntu.com-20071128053506-it05wgucjiw2chfq
Post-review cleanups from Robert for KnitVersionedFile.get_data_stream

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