/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: Aaron Bentley
  • Date: 2007-12-20 20:19:22 UTC
  • mto: This revision was merged to the branch mainline in revision 3235.
  • Revision ID: abentley@panoramicfeedback.com-20071220201922-r6a2vsx612x87yf6
Implement hard-linking for build_tree

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