~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Aaron Bentley
  • Date: 2007-12-29 22:05:47 UTC
  • mto: This revision was merged to the branch mainline in revision 3162.
  • Revision ID: aaron.bentley@utoronto.ca-20071229220547-fi8mxlg9fv9ivq68
do case-insensitive comparision of iexplore filename

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