/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-11-26 20:18:50 UTC
  • mfrom: (2998.2.3 push_packs_to_knits)
  • Revision ID: pqm@pqm.ubuntu.com-20071126201850-7bugi709x0jjfqpg
(John Arbash Meinel) Change pushing from a pack-repo to a knit-repo
        to not rebuild the full history of effected knits.

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