~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Martin Pool
  • Date: 2007-10-24 05:11:38 UTC
  • mto: This revision was merged to the branch mainline in revision 2933.
  • Revision ID: mbp@sourcefrog.net-20071024051138-x6b3ff9n51ectin8
Rename RepositoryPackCollection.release_names to _unlock_names

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