/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-01-03 18:09:01 UTC
  • mfrom: (3159.1.1 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20080103180901-w987y1ftqoh02qbm
(vila) Fix #179368 by keeping the current range hint on
        ShortReadvErrors

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