~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: John Arbash Meinel
  • Date: 2007-11-13 20:37:09 UTC
  • mto: This revision was merged to the branch mainline in revision 3001.
  • Revision ID: john@arbash-meinel.com-20071113203709-kysdte0emqv84pnj
Fix bug #162486, by having RemoteBranch properly initialize self._revision_id_to_revno_map.

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