/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: Andrew Bennetts
  • Date: 2008-01-14 22:45:15 UTC
  • mfrom: (3179 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3180.
  • Revision ID: andrew.bennetts@canonical.com-20080114224515-izp51fxci3hhopap
Merge from bzr.dev.

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.wt.lock_write()
 
763
        self.addCleanup(self.wt.unlock)
 
764
        self.assertTrue(self.wt.is_executable('file1-id'))
 
765
        transform, root = self.get_transform()
 
766
        file1_id = transform.trans_id_tree_file_id('file1-id')
 
767
        transform.delete_contents(file1_id)
 
768
        transform.create_file('contents2', file1_id)
 
769
        transform.apply()
 
770
        self.assertTrue(self.wt.is_executable('file1-id'))
 
771
 
 
772
    def test__set_mode_stats_correctly(self):
 
773
        """_set_mode stats to determine file mode."""
 
774
        if sys.platform == 'win32':
 
775
            raise TestSkipped('chmod has no effect on win32')
 
776
 
 
777
        stat_paths = []
 
778
        real_stat = os.stat
 
779
        def instrumented_stat(path):
 
780
            stat_paths.append(path)
 
781
            return real_stat(path)
 
782
 
 
783
        transform, root = self.get_transform()
 
784
 
 
785
        bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
 
786
                                     file_id='bar-id-1', executable=False)
 
787
        transform.apply()
 
788
 
 
789
        transform, root = self.get_transform()
 
790
        bar1_id = transform.trans_id_tree_path('bar')
 
791
        bar2_id = transform.trans_id_tree_path('bar2')
 
792
        try:
 
793
            os.stat = instrumented_stat
 
794
            transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
 
795
        finally:
 
796
            os.stat = real_stat
 
797
            transform.finalize()
 
798
 
 
799
        bar1_abspath = self.wt.abspath('bar')
 
800
        self.assertEqual([bar1_abspath], stat_paths)
 
801
 
 
802
    def test_iter_changes(self):
 
803
        self.wt.set_root_id('eert_toor')
 
804
        transform, root = self.get_transform()
 
805
        transform.new_file('old', root, 'blah', 'id-1', True)
 
806
        transform.apply()
 
807
        transform, root = self.get_transform()
 
808
        try:
 
809
            self.assertEqual([], list(transform._iter_changes()))
 
810
            old = transform.trans_id_tree_file_id('id-1')
 
811
            transform.unversion_file(old)
 
812
            self.assertEqual([('id-1', ('old', None), False, (True, False),
 
813
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
814
                (True, True))], list(transform._iter_changes()))
 
815
            transform.new_directory('new', root, 'id-1')
 
816
            self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
 
817
                ('eert_toor', 'eert_toor'), ('old', 'new'),
 
818
                ('file', 'directory'),
 
819
                (True, False))], list(transform._iter_changes()))
 
820
        finally:
 
821
            transform.finalize()
 
822
 
 
823
    def test_iter_changes_new(self):
 
824
        self.wt.set_root_id('eert_toor')
 
825
        transform, root = self.get_transform()
 
826
        transform.new_file('old', root, 'blah')
 
827
        transform.apply()
 
828
        transform, root = self.get_transform()
 
829
        try:
 
830
            old = transform.trans_id_tree_path('old')
 
831
            transform.version_file('id-1', old)
 
832
            self.assertEqual([('id-1', (None, 'old'), False, (False, True),
 
833
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
834
                (False, False))], list(transform._iter_changes()))
 
835
        finally:
 
836
            transform.finalize()
 
837
 
 
838
    def test_iter_changes_modifications(self):
 
839
        self.wt.set_root_id('eert_toor')
 
840
        transform, root = self.get_transform()
 
841
        transform.new_file('old', root, 'blah', 'id-1')
 
842
        transform.new_file('new', root, 'blah')
 
843
        transform.new_directory('subdir', root, 'subdir-id')
 
844
        transform.apply()
 
845
        transform, root = self.get_transform()
 
846
        try:
 
847
            old = transform.trans_id_tree_path('old')
 
848
            subdir = transform.trans_id_tree_file_id('subdir-id')
 
849
            new = transform.trans_id_tree_path('new')
 
850
            self.assertEqual([], list(transform._iter_changes()))
 
851
 
 
852
            #content deletion
 
853
            transform.delete_contents(old)
 
854
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
855
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
 
856
                (False, False))], list(transform._iter_changes()))
 
857
 
 
858
            #content change
 
859
            transform.create_file('blah', old)
 
860
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
861
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
862
                (False, False))], list(transform._iter_changes()))
 
863
            transform.cancel_deletion(old)
 
864
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
865
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
866
                (False, False))], list(transform._iter_changes()))
 
867
            transform.cancel_creation(old)
 
868
 
 
869
            # move file_id to a different file
 
870
            self.assertEqual([], list(transform._iter_changes()))
 
871
            transform.unversion_file(old)
 
872
            transform.version_file('id-1', new)
 
873
            transform.adjust_path('old', root, new)
 
874
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
875
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
876
                (False, False))], list(transform._iter_changes()))
 
877
            transform.cancel_versioning(new)
 
878
            transform._removed_id = set()
 
879
 
 
880
            #execute bit
 
881
            self.assertEqual([], list(transform._iter_changes()))
 
882
            transform.set_executability(True, old)
 
883
            self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
 
884
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
885
                (False, True))], list(transform._iter_changes()))
 
886
            transform.set_executability(None, old)
 
887
 
 
888
            # filename
 
889
            self.assertEqual([], list(transform._iter_changes()))
 
890
            transform.adjust_path('new', root, old)
 
891
            transform._new_parent = {}
 
892
            self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
 
893
                ('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
 
894
                (False, False))], list(transform._iter_changes()))
 
895
            transform._new_name = {}
 
896
 
 
897
            # parent directory
 
898
            self.assertEqual([], list(transform._iter_changes()))
 
899
            transform.adjust_path('new', subdir, old)
 
900
            transform._new_name = {}
 
901
            self.assertEqual([('id-1', ('old', 'subdir/old'), False,
 
902
                (True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
 
903
                ('file', 'file'), (False, False))],
 
904
                list(transform._iter_changes()))
 
905
            transform._new_path = {}
 
906
 
 
907
        finally:
 
908
            transform.finalize()
 
909
 
 
910
    def test_iter_changes_modified_bleed(self):
 
911
        self.wt.set_root_id('eert_toor')
 
912
        """Modified flag should not bleed from one change to another"""
 
913
        # unfortunately, we have no guarantee that file1 (which is modified)
 
914
        # will be applied before file2.  And if it's applied after file2, it
 
915
        # obviously can't bleed into file2's change output.  But for now, it
 
916
        # works.
 
917
        transform, root = self.get_transform()
 
918
        transform.new_file('file1', root, 'blah', 'id-1')
 
919
        transform.new_file('file2', root, 'blah', 'id-2')
 
920
        transform.apply()
 
921
        transform, root = self.get_transform()
 
922
        try:
 
923
            transform.delete_contents(transform.trans_id_file_id('id-1'))
 
924
            transform.set_executability(True,
 
925
            transform.trans_id_file_id('id-2'))
 
926
            self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
 
927
                ('eert_toor', 'eert_toor'), ('file1', u'file1'),
 
928
                ('file', None), (False, False)),
 
929
                ('id-2', (u'file2', u'file2'), False, (True, True),
 
930
                ('eert_toor', 'eert_toor'), ('file2', u'file2'),
 
931
                ('file', 'file'), (False, True))],
 
932
                list(transform._iter_changes()))
 
933
        finally:
 
934
            transform.finalize()
 
935
 
 
936
    def test_iter_changes_move_missing(self):
 
937
        """Test moving ids with no files around"""
 
938
        self.wt.set_root_id('toor_eert')
 
939
        # Need two steps because versioning a non-existant file is a conflict.
 
940
        transform, root = self.get_transform()
 
941
        transform.new_directory('floater', root, 'floater-id')
 
942
        transform.apply()
 
943
        transform, root = self.get_transform()
 
944
        transform.delete_contents(transform.trans_id_tree_path('floater'))
 
945
        transform.apply()
 
946
        transform, root = self.get_transform()
 
947
        floater = transform.trans_id_tree_path('floater')
 
948
        try:
 
949
            transform.adjust_path('flitter', root, floater)
 
950
            self.assertEqual([('floater-id', ('floater', 'flitter'), False,
 
951
            (True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
 
952
            (None, None), (False, False))], list(transform._iter_changes()))
 
953
        finally:
 
954
            transform.finalize()
 
955
 
 
956
    def test_iter_changes_pointless(self):
 
957
        """Ensure that no-ops are not treated as modifications"""
 
958
        self.wt.set_root_id('eert_toor')
 
959
        transform, root = self.get_transform()
 
960
        transform.new_file('old', root, 'blah', 'id-1')
 
961
        transform.new_directory('subdir', root, 'subdir-id')
 
962
        transform.apply()
 
963
        transform, root = self.get_transform()
 
964
        try:
 
965
            old = transform.trans_id_tree_path('old')
 
966
            subdir = transform.trans_id_tree_file_id('subdir-id')
 
967
            self.assertEqual([], list(transform._iter_changes()))
 
968
            transform.delete_contents(subdir)
 
969
            transform.create_directory(subdir)
 
970
            transform.set_executability(False, old)
 
971
            transform.unversion_file(old)
 
972
            transform.version_file('id-1', old)
 
973
            transform.adjust_path('old', root, old)
 
974
            self.assertEqual([], list(transform._iter_changes()))
 
975
        finally:
 
976
            transform.finalize()
 
977
 
 
978
    def test_rename_count(self):
 
979
        transform, root = self.get_transform()
 
980
        transform.new_file('name1', root, 'contents')
 
981
        self.assertEqual(transform.rename_count, 0)
 
982
        transform.apply()
 
983
        self.assertEqual(transform.rename_count, 1)
 
984
        transform2, root = self.get_transform()
 
985
        transform2.adjust_path('name2', root,
 
986
                               transform2.trans_id_tree_path('name1'))
 
987
        self.assertEqual(transform2.rename_count, 0)
 
988
        transform2.apply()
 
989
        self.assertEqual(transform2.rename_count, 2)
 
990
 
 
991
    def test_change_parent(self):
 
992
        """Ensure that after we change a parent, the results are still right.
 
993
 
 
994
        Renames and parent changes on pending transforms can happen as part
 
995
        of conflict resolution, and are explicitly permitted by the
 
996
        TreeTransform API.
 
997
 
 
998
        This test ensures they work correctly with the rename-avoidance
 
999
        optimization.
 
1000
        """
 
1001
        transform, root = self.get_transform()
 
1002
        parent1 = transform.new_directory('parent1', root)
 
1003
        child1 = transform.new_file('child1', parent1, 'contents')
 
1004
        parent2 = transform.new_directory('parent2', root)
 
1005
        transform.adjust_path('child1', parent2, child1)
 
1006
        transform.apply()
 
1007
        self.failIfExists(self.wt.abspath('parent1/child1'))
 
1008
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
1009
        # rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
 
1010
        # no rename for child1 (counting only renames during apply)
 
1011
        self.failUnlessEqual(2, transform.rename_count)
 
1012
 
 
1013
    def test_cancel_parent(self):
 
1014
        """Cancelling a parent doesn't cause deletion of a non-empty directory
 
1015
 
 
1016
        This is like the test_change_parent, except that we cancel the parent
 
1017
        before adjusting the path.  The transform must detect that the
 
1018
        directory is non-empty, and move children to safe locations.
 
1019
        """
 
1020
        transform, root = self.get_transform()
 
1021
        parent1 = transform.new_directory('parent1', root)
 
1022
        child1 = transform.new_file('child1', parent1, 'contents')
 
1023
        child2 = transform.new_file('child2', parent1, 'contents')
 
1024
        try:
 
1025
            transform.cancel_creation(parent1)
 
1026
        except OSError:
 
1027
            self.fail('Failed to move child1 before deleting parent1')
 
1028
        transform.cancel_creation(child2)
 
1029
        transform.create_directory(parent1)
 
1030
        try:
 
1031
            transform.cancel_creation(parent1)
 
1032
        # If the transform incorrectly believes that child2 is still in
 
1033
        # parent1's limbo directory, it will try to rename it and fail
 
1034
        # because was already moved by the first cancel_creation.
 
1035
        except OSError:
 
1036
            self.fail('Transform still thinks child2 is a child of parent1')
 
1037
        parent2 = transform.new_directory('parent2', root)
 
1038
        transform.adjust_path('child1', parent2, child1)
 
1039
        transform.apply()
 
1040
        self.failIfExists(self.wt.abspath('parent1'))
 
1041
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
1042
        # rename limbo/new-3 => parent2, rename limbo/new-2 => child1
 
1043
        self.failUnlessEqual(2, transform.rename_count)
 
1044
 
 
1045
    def test_adjust_and_cancel(self):
 
1046
        """Make sure adjust_path keeps track of limbo children properly"""
 
1047
        transform, root = self.get_transform()
 
1048
        parent1 = transform.new_directory('parent1', root)
 
1049
        child1 = transform.new_file('child1', parent1, 'contents')
 
1050
        parent2 = transform.new_directory('parent2', root)
 
1051
        transform.adjust_path('child1', parent2, child1)
 
1052
        transform.cancel_creation(child1)
 
1053
        try:
 
1054
            transform.cancel_creation(parent1)
 
1055
        # if the transform thinks child1 is still in parent1's limbo
 
1056
        # directory, it will attempt to move it and fail.
 
1057
        except OSError:
 
1058
            self.fail('Transform still thinks child1 is a child of parent1')
 
1059
        transform.finalize()
 
1060
 
 
1061
    def test_noname_contents(self):
 
1062
        """TreeTransform should permit deferring naming files."""
 
1063
        transform, root = self.get_transform()
 
1064
        parent = transform.trans_id_file_id('parent-id')
 
1065
        try:
 
1066
            transform.create_directory(parent)
 
1067
        except KeyError:
 
1068
            self.fail("Can't handle contents with no name")
 
1069
        transform.finalize()
 
1070
 
 
1071
    def test_noname_contents_nested(self):
 
1072
        """TreeTransform should permit deferring naming files."""
 
1073
        transform, root = self.get_transform()
 
1074
        parent = transform.trans_id_file_id('parent-id')
 
1075
        try:
 
1076
            transform.create_directory(parent)
 
1077
        except KeyError:
 
1078
            self.fail("Can't handle contents with no name")
 
1079
        child = transform.new_directory('child', parent)
 
1080
        transform.adjust_path('parent', root, parent)
 
1081
        transform.apply()
 
1082
        self.failUnlessExists(self.wt.abspath('parent/child'))
 
1083
        self.assertEqual(1, transform.rename_count)
 
1084
 
 
1085
    def test_reuse_name(self):
 
1086
        """Avoid reusing the same limbo name for different files"""
 
1087
        transform, root = self.get_transform()
 
1088
        parent = transform.new_directory('parent', root)
 
1089
        child1 = transform.new_directory('child', parent)
 
1090
        try:
 
1091
            child2 = transform.new_directory('child', parent)
 
1092
        except OSError:
 
1093
            self.fail('Tranform tried to use the same limbo name twice')
 
1094
        transform.adjust_path('child2', parent, child2)
 
1095
        transform.apply()
 
1096
        # limbo/new-1 => parent, limbo/new-3 => parent/child2
 
1097
        # child2 is put into top-level limbo because child1 has already
 
1098
        # claimed the direct limbo path when child2 is created.  There is no
 
1099
        # advantage in renaming files once they're in top-level limbo, except
 
1100
        # as part of apply.
 
1101
        self.assertEqual(2, transform.rename_count)
 
1102
 
 
1103
    def test_reuse_when_first_moved(self):
 
1104
        """Don't avoid direct paths when it is safe to use them"""
 
1105
        transform, root = self.get_transform()
 
1106
        parent = transform.new_directory('parent', root)
 
1107
        child1 = transform.new_directory('child', parent)
 
1108
        transform.adjust_path('child1', parent, child1)
 
1109
        child2 = transform.new_directory('child', parent)
 
1110
        transform.apply()
 
1111
        # limbo/new-1 => parent
 
1112
        self.assertEqual(1, transform.rename_count)
 
1113
 
 
1114
    def test_reuse_after_cancel(self):
 
1115
        """Don't avoid direct paths when it is safe to use them"""
 
1116
        transform, root = self.get_transform()
 
1117
        parent2 = transform.new_directory('parent2', root)
 
1118
        child1 = transform.new_directory('child1', parent2)
 
1119
        transform.cancel_creation(parent2)
 
1120
        transform.create_directory(parent2)
 
1121
        child2 = transform.new_directory('child1', parent2)
 
1122
        transform.adjust_path('child2', parent2, child1)
 
1123
        transform.apply()
 
1124
        # limbo/new-1 => parent2, limbo/new-2 => parent2/child1
 
1125
        self.assertEqual(2, transform.rename_count)
 
1126
 
 
1127
    def test_finalize_order(self):
 
1128
        """Finalize must be done in child-to-parent order"""
 
1129
        transform, root = self.get_transform()
 
1130
        parent = transform.new_directory('parent', root)
 
1131
        child = transform.new_directory('child', parent)
 
1132
        try:
 
1133
            transform.finalize()
 
1134
        except OSError:
 
1135
            self.fail('Tried to remove parent before child1')
 
1136
 
 
1137
    def test_cancel_with_cancelled_child_should_succeed(self):
 
1138
        transform, root = self.get_transform()
 
1139
        parent = transform.new_directory('parent', root)
 
1140
        child = transform.new_directory('child', parent)
 
1141
        transform.cancel_creation(child)
 
1142
        transform.cancel_creation(parent)
 
1143
        transform.finalize()
 
1144
 
 
1145
    def test_change_entry(self):
 
1146
        txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
 
1147
        self.callDeprecated([txt], change_entry, None, None, None, None, None,
 
1148
            None, None, None)
 
1149
 
 
1150
    def test_case_insensitive_clash(self):
 
1151
        self.requireFeature(CaseInsensitiveFilesystemFeature)
 
1152
        def tt_helper():
 
1153
            wt = self.make_branch_and_tree('.')
 
1154
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1155
            try:
 
1156
                tt.new_file('foo', tt.root, 'bar')
 
1157
                tt.new_file('Foo', tt.root, 'spam')
 
1158
                # Lie to tt that we've already resolved all conflicts.
 
1159
                tt.apply(no_conflicts=True)
 
1160
            except:
 
1161
                wt.unlock()
 
1162
                raise
 
1163
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1164
        self.assertContainsRe(str(err),
 
1165
            "^File exists: .+/foo")
 
1166
 
 
1167
    def test_two_directories_clash(self):
 
1168
        def tt_helper():
 
1169
            wt = self.make_branch_and_tree('.')
 
1170
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1171
            try:
 
1172
                foo_1 = tt.new_directory('foo', tt.root)
 
1173
                tt.new_directory('bar', foo_1)
 
1174
                foo_2 = tt.new_directory('foo', tt.root)
 
1175
                tt.new_directory('baz', foo_2)
 
1176
                # Lie to tt that we've already resolved all conflicts.
 
1177
                tt.apply(no_conflicts=True)
 
1178
            except:
 
1179
                wt.unlock()
 
1180
                raise
 
1181
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1182
        self.assertContainsRe(str(err),
 
1183
            "^File exists: .+/foo")
 
1184
 
 
1185
    def test_two_directories_clash_finalize(self):
 
1186
        def tt_helper():
 
1187
            wt = self.make_branch_and_tree('.')
 
1188
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1189
            try:
 
1190
                foo_1 = tt.new_directory('foo', tt.root)
 
1191
                tt.new_directory('bar', foo_1)
 
1192
                foo_2 = tt.new_directory('foo', tt.root)
 
1193
                tt.new_directory('baz', foo_2)
 
1194
                # Lie to tt that we've already resolved all conflicts.
 
1195
                tt.apply(no_conflicts=True)
 
1196
            except:
 
1197
                tt.finalize()
 
1198
                raise
 
1199
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1200
        self.assertContainsRe(str(err),
 
1201
            "^File exists: .+/foo")
 
1202
 
 
1203
 
 
1204
class TransformGroup(object):
 
1205
 
 
1206
    def __init__(self, dirname, root_id):
 
1207
        self.name = dirname
 
1208
        os.mkdir(dirname)
 
1209
        self.wt = BzrDir.create_standalone_workingtree(dirname)
 
1210
        self.wt.set_root_id(root_id)
 
1211
        self.b = self.wt.branch
 
1212
        self.tt = TreeTransform(self.wt)
 
1213
        self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
 
1214
 
 
1215
 
 
1216
def conflict_text(tree, merge):
 
1217
    template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
 
1218
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
 
1219
 
 
1220
 
 
1221
class TestTransformMerge(TestCaseInTempDir):
 
1222
    def test_text_merge(self):
 
1223
        root_id = generate_ids.gen_root_id()
 
1224
        base = TransformGroup("base", root_id)
 
1225
        base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
 
1226
        base.tt.new_file('b', base.root, 'b1', 'b')
 
1227
        base.tt.new_file('c', base.root, 'c', 'c')
 
1228
        base.tt.new_file('d', base.root, 'd', 'd')
 
1229
        base.tt.new_file('e', base.root, 'e', 'e')
 
1230
        base.tt.new_file('f', base.root, 'f', 'f')
 
1231
        base.tt.new_directory('g', base.root, 'g')
 
1232
        base.tt.new_directory('h', base.root, 'h')
 
1233
        base.tt.apply()
 
1234
        other = TransformGroup("other", root_id)
 
1235
        other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
 
1236
        other.tt.new_file('b', other.root, 'b2', 'b')
 
1237
        other.tt.new_file('c', other.root, 'c2', 'c')
 
1238
        other.tt.new_file('d', other.root, 'd', 'd')
 
1239
        other.tt.new_file('e', other.root, 'e2', 'e')
 
1240
        other.tt.new_file('f', other.root, 'f', 'f')
 
1241
        other.tt.new_file('g', other.root, 'g', 'g')
 
1242
        other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
 
1243
        other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
 
1244
        other.tt.apply()
 
1245
        this = TransformGroup("this", root_id)
 
1246
        this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
 
1247
        this.tt.new_file('b', this.root, 'b', 'b')
 
1248
        this.tt.new_file('c', this.root, 'c', 'c')
 
1249
        this.tt.new_file('d', this.root, 'd2', 'd')
 
1250
        this.tt.new_file('e', this.root, 'e2', 'e')
 
1251
        this.tt.new_file('f', this.root, 'f', 'f')
 
1252
        this.tt.new_file('g', this.root, 'g', 'g')
 
1253
        this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
 
1254
        this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
 
1255
        this.tt.apply()
 
1256
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1257
        # textual merge
 
1258
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
 
1259
        # three-way text conflict
 
1260
        self.assertEqual(this.wt.get_file('b').read(), 
 
1261
                         conflict_text('b', 'b2'))
 
1262
        # OTHER wins
 
1263
        self.assertEqual(this.wt.get_file('c').read(), 'c2')
 
1264
        # THIS wins
 
1265
        self.assertEqual(this.wt.get_file('d').read(), 'd2')
 
1266
        # Ambigious clean merge
 
1267
        self.assertEqual(this.wt.get_file('e').read(), 'e2')
 
1268
        # No change
 
1269
        self.assertEqual(this.wt.get_file('f').read(), 'f')
 
1270
        # Correct correct results when THIS == OTHER 
 
1271
        self.assertEqual(this.wt.get_file('g').read(), 'g')
 
1272
        # Text conflict when THIS & OTHER are text and BASE is dir
 
1273
        self.assertEqual(this.wt.get_file('h').read(), 
 
1274
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
 
1275
        self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
 
1276
                         '1\n2\n3\n4\n')
 
1277
        self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
 
1278
                         'h\ni\nj\nk\n')
 
1279
        self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
 
1280
        self.assertEqual(this.wt.get_file('i').read(), 
 
1281
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
 
1282
        self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
 
1283
                         '1\n2\n3\n4\n')
 
1284
        self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
 
1285
                         'h\ni\nj\nk\n')
 
1286
        self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
 
1287
        modified = ['a', 'b', 'c', 'h', 'i']
 
1288
        merge_modified = this.wt.merge_modified()
 
1289
        self.assertSubset(merge_modified, modified)
 
1290
        self.assertEqual(len(merge_modified), len(modified))
 
1291
        file(this.wt.id2abspath('a'), 'wb').write('booga')
 
1292
        modified.pop(0)
 
1293
        merge_modified = this.wt.merge_modified()
 
1294
        self.assertSubset(merge_modified, modified)
 
1295
        self.assertEqual(len(merge_modified), len(modified))
 
1296
        this.wt.remove('b')
 
1297
        this.wt.revert()
 
1298
 
 
1299
    def test_file_merge(self):
 
1300
        self.requireFeature(SymlinkFeature)
 
1301
        root_id = generate_ids.gen_root_id()
 
1302
        base = TransformGroup("BASE", root_id)
 
1303
        this = TransformGroup("THIS", root_id)
 
1304
        other = TransformGroup("OTHER", root_id)
 
1305
        for tg in this, base, other:
 
1306
            tg.tt.new_directory('a', tg.root, 'a')
 
1307
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
 
1308
            tg.tt.new_file('c', tg.root, 'c', 'c')
 
1309
            tg.tt.new_symlink('d', tg.root, tg.name, 'd')
 
1310
        targets = ((base, 'base-e', 'base-f', None, None), 
 
1311
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'), 
 
1312
                   (other, 'other-e', None, 'other-g', 'other-h'))
 
1313
        for tg, e_target, f_target, g_target, h_target in targets:
 
1314
            for link, target in (('e', e_target), ('f', f_target), 
 
1315
                                 ('g', g_target), ('h', h_target)):
 
1316
                if target is not None:
 
1317
                    tg.tt.new_symlink(link, tg.root, target, link)
 
1318
 
 
1319
        for tg in this, base, other:
 
1320
            tg.tt.apply()
 
1321
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1322
        self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
 
1323
        self.assertIs(os.path.islink(this.wt.abspath('b')), True)
 
1324
        self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
 
1325
        for suffix in ('THIS', 'BASE', 'OTHER'):
 
1326
            self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
 
1327
        self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
 
1328
        self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
 
1329
        self.assertEqual(this.wt.id2path('f'), 'f.THIS')
 
1330
        self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
 
1331
        self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
 
1332
        self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
 
1333
        self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
 
1334
        self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
 
1335
        self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
 
1336
        self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
 
1337
        self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
 
1338
        self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
 
1339
        self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
 
1340
 
 
1341
    def test_filename_merge(self):
 
1342
        root_id = generate_ids.gen_root_id()
 
1343
        base = TransformGroup("BASE", root_id)
 
1344
        this = TransformGroup("THIS", root_id)
 
1345
        other = TransformGroup("OTHER", root_id)
 
1346
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
1347
                                   for t in [base, this, other]]
 
1348
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
 
1349
                                   for t in [base, this, other]]
 
1350
        base.tt.new_directory('c', base_a, 'c')
 
1351
        this.tt.new_directory('c1', this_a, 'c')
 
1352
        other.tt.new_directory('c', other_b, 'c')
 
1353
 
 
1354
        base.tt.new_directory('d', base_a, 'd')
 
1355
        this.tt.new_directory('d1', this_b, 'd')
 
1356
        other.tt.new_directory('d', other_a, 'd')
 
1357
 
 
1358
        base.tt.new_directory('e', base_a, 'e')
 
1359
        this.tt.new_directory('e', this_a, 'e')
 
1360
        other.tt.new_directory('e1', other_b, 'e')
 
1361
 
 
1362
        base.tt.new_directory('f', base_a, 'f')
 
1363
        this.tt.new_directory('f1', this_b, 'f')
 
1364
        other.tt.new_directory('f1', other_b, 'f')
 
1365
 
 
1366
        for tg in [this, base, other]:
 
1367
            tg.tt.apply()
 
1368
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1369
        self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
 
1370
        self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
 
1371
        self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
 
1372
        self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
 
1373
 
 
1374
    def test_filename_merge_conflicts(self):
 
1375
        root_id = generate_ids.gen_root_id()
 
1376
        base = TransformGroup("BASE", root_id)
 
1377
        this = TransformGroup("THIS", root_id)
 
1378
        other = TransformGroup("OTHER", root_id)
 
1379
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
1380
                                   for t in [base, this, other]]
 
1381
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
 
1382
                                   for t in [base, this, other]]
 
1383
 
 
1384
        base.tt.new_file('g', base_a, 'g', 'g')
 
1385
        other.tt.new_file('g1', other_b, 'g1', 'g')
 
1386
 
 
1387
        base.tt.new_file('h', base_a, 'h', 'h')
 
1388
        this.tt.new_file('h1', this_b, 'h1', 'h')
 
1389
 
 
1390
        base.tt.new_file('i', base.root, 'i', 'i')
 
1391
        other.tt.new_directory('i1', this_b, 'i')
 
1392
 
 
1393
        for tg in [this, base, other]:
 
1394
            tg.tt.apply()
 
1395
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1396
 
 
1397
        self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
 
1398
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
 
1399
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
 
1400
        self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
 
1401
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
 
1402
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
 
1403
        self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
 
1404
 
 
1405
 
 
1406
class TestBuildTree(tests.TestCaseWithTransport):
 
1407
 
 
1408
    def test_build_tree_with_symlinks(self):
 
1409
        self.requireFeature(SymlinkFeature)
 
1410
        os.mkdir('a')
 
1411
        a = BzrDir.create_standalone_workingtree('a')
 
1412
        os.mkdir('a/foo')
 
1413
        file('a/foo/bar', 'wb').write('contents')
 
1414
        os.symlink('a/foo/bar', 'a/foo/baz')
 
1415
        a.add(['foo', 'foo/bar', 'foo/baz'])
 
1416
        a.commit('initial commit')
 
1417
        b = BzrDir.create_standalone_workingtree('b')
 
1418
        basis = a.basis_tree()
 
1419
        basis.lock_read()
 
1420
        self.addCleanup(basis.unlock)
 
1421
        build_tree(basis, b)
 
1422
        self.assertIs(os.path.isdir('b/foo'), True)
 
1423
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
 
1424
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
 
1425
 
 
1426
    def test_build_with_references(self):
 
1427
        tree = self.make_branch_and_tree('source',
 
1428
            format='dirstate-with-subtree')
 
1429
        subtree = self.make_branch_and_tree('source/subtree',
 
1430
            format='dirstate-with-subtree')
 
1431
        tree.add_reference(subtree)
 
1432
        tree.commit('a revision')
 
1433
        tree.branch.create_checkout('target')
 
1434
        self.failUnlessExists('target')
 
1435
        self.failUnlessExists('target/subtree')
 
1436
 
 
1437
    def test_file_conflict_handling(self):
 
1438
        """Ensure that when building trees, conflict handling is done"""
 
1439
        source = self.make_branch_and_tree('source')
 
1440
        target = self.make_branch_and_tree('target')
 
1441
        self.build_tree(['source/file', 'target/file'])
 
1442
        source.add('file', 'new-file')
 
1443
        source.commit('added file')
 
1444
        build_tree(source.basis_tree(), target)
 
1445
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1446
                          'file.moved', 'file', None, 'new-file')],
 
1447
                         target.conflicts())
 
1448
        target2 = self.make_branch_and_tree('target2')
 
1449
        target_file = file('target2/file', 'wb')
 
1450
        try:
 
1451
            source_file = file('source/file', 'rb')
 
1452
            try:
 
1453
                target_file.write(source_file.read())
 
1454
            finally:
 
1455
                source_file.close()
 
1456
        finally:
 
1457
            target_file.close()
 
1458
        build_tree(source.basis_tree(), target2)
 
1459
        self.assertEqual([], target2.conflicts())
 
1460
 
 
1461
    def test_symlink_conflict_handling(self):
 
1462
        """Ensure that when building trees, conflict handling is done"""
 
1463
        self.requireFeature(SymlinkFeature)
 
1464
        source = self.make_branch_and_tree('source')
 
1465
        os.symlink('foo', 'source/symlink')
 
1466
        source.add('symlink', 'new-symlink')
 
1467
        source.commit('added file')
 
1468
        target = self.make_branch_and_tree('target')
 
1469
        os.symlink('bar', 'target/symlink')
 
1470
        build_tree(source.basis_tree(), target)
 
1471
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1472
            'symlink.moved', 'symlink', None, 'new-symlink')],
 
1473
            target.conflicts())
 
1474
        target = self.make_branch_and_tree('target2')
 
1475
        os.symlink('foo', 'target2/symlink')
 
1476
        build_tree(source.basis_tree(), target)
 
1477
        self.assertEqual([], target.conflicts())
 
1478
        
 
1479
    def test_directory_conflict_handling(self):
 
1480
        """Ensure that when building trees, conflict handling is done"""
 
1481
        source = self.make_branch_and_tree('source')
 
1482
        target = self.make_branch_and_tree('target')
 
1483
        self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
 
1484
        source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
 
1485
        source.commit('added file')
 
1486
        build_tree(source.basis_tree(), target)
 
1487
        self.assertEqual([], target.conflicts())
 
1488
        self.failUnlessExists('target/dir1/file')
 
1489
 
 
1490
        # Ensure contents are merged
 
1491
        target = self.make_branch_and_tree('target2')
 
1492
        self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
 
1493
        build_tree(source.basis_tree(), target)
 
1494
        self.assertEqual([], target.conflicts())
 
1495
        self.failUnlessExists('target2/dir1/file2')
 
1496
        self.failUnlessExists('target2/dir1/file')
 
1497
 
 
1498
        # Ensure new contents are suppressed for existing branches
 
1499
        target = self.make_branch_and_tree('target3')
 
1500
        self.make_branch('target3/dir1')
 
1501
        self.build_tree(['target3/dir1/file2'])
 
1502
        build_tree(source.basis_tree(), target)
 
1503
        self.failIfExists('target3/dir1/file')
 
1504
        self.failUnlessExists('target3/dir1/file2')
 
1505
        self.failUnlessExists('target3/dir1.diverted/file')
 
1506
        self.assertEqual([DuplicateEntry('Diverted to',
 
1507
            'dir1.diverted', 'dir1', 'new-dir1', None)],
 
1508
            target.conflicts())
 
1509
 
 
1510
        target = self.make_branch_and_tree('target4')
 
1511
        self.build_tree(['target4/dir1/'])
 
1512
        self.make_branch('target4/dir1/file')
 
1513
        build_tree(source.basis_tree(), target)
 
1514
        self.failUnlessExists('target4/dir1/file')
 
1515
        self.assertEqual('directory', file_kind('target4/dir1/file'))
 
1516
        self.failUnlessExists('target4/dir1/file.diverted')
 
1517
        self.assertEqual([DuplicateEntry('Diverted to',
 
1518
            'dir1/file.diverted', 'dir1/file', 'new-file', None)],
 
1519
            target.conflicts())
 
1520
 
 
1521
    def test_mixed_conflict_handling(self):
 
1522
        """Ensure that when building trees, conflict handling is done"""
 
1523
        source = self.make_branch_and_tree('source')
 
1524
        target = self.make_branch_and_tree('target')
 
1525
        self.build_tree(['source/name', 'target/name/'])
 
1526
        source.add('name', 'new-name')
 
1527
        source.commit('added file')
 
1528
        build_tree(source.basis_tree(), target)
 
1529
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1530
            'name.moved', 'name', None, 'new-name')], target.conflicts())
 
1531
 
 
1532
    def test_raises_in_populated(self):
 
1533
        source = self.make_branch_and_tree('source')
 
1534
        self.build_tree(['source/name'])
 
1535
        source.add('name')
 
1536
        source.commit('added name')
 
1537
        target = self.make_branch_and_tree('target')
 
1538
        self.build_tree(['target/name'])
 
1539
        target.add('name')
 
1540
        self.assertRaises(errors.WorkingTreeAlreadyPopulated, 
 
1541
            build_tree, source.basis_tree(), target)
 
1542
 
 
1543
    def test_build_tree_rename_count(self):
 
1544
        source = self.make_branch_and_tree('source')
 
1545
        self.build_tree(['source/file1', 'source/dir1/'])
 
1546
        source.add(['file1', 'dir1'])
 
1547
        source.commit('add1')
 
1548
        target1 = self.make_branch_and_tree('target1')
 
1549
        transform_result = build_tree(source.basis_tree(), target1)
 
1550
        self.assertEqual(2, transform_result.rename_count)
 
1551
 
 
1552
        self.build_tree(['source/dir1/file2'])
 
1553
        source.add(['dir1/file2'])
 
1554
        source.commit('add3')
 
1555
        target2 = self.make_branch_and_tree('target2')
 
1556
        transform_result = build_tree(source.basis_tree(), target2)
 
1557
        # children of non-root directories should not be renamed
 
1558
        self.assertEqual(2, transform_result.rename_count)
 
1559
 
 
1560
    def test_build_tree_accelerator_tree(self):
 
1561
        source = self.make_branch_and_tree('source')
 
1562
        self.build_tree_contents([('source/file1', 'A')])
 
1563
        self.build_tree_contents([('source/file2', 'B')])
 
1564
        source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
 
1565
        source.commit('commit files')
 
1566
        self.build_tree_contents([('source/file2', 'C')])
 
1567
        calls = []
 
1568
        real_source_get_file = source.get_file
 
1569
        def get_file(file_id, path=None):
 
1570
            calls.append(file_id)
 
1571
            return real_source_get_file(file_id, path)
 
1572
        source.get_file = get_file
 
1573
        source.lock_read()
 
1574
        self.addCleanup(source.unlock)
 
1575
        target = self.make_branch_and_tree('target')
 
1576
        revision_tree = source.basis_tree()
 
1577
        revision_tree.lock_read()
 
1578
        self.addCleanup(revision_tree.unlock)
 
1579
        build_tree(revision_tree, target, source)
 
1580
        self.assertEqual(['file1-id'], calls)
 
1581
        target.lock_read()
 
1582
        self.addCleanup(target.unlock)
 
1583
        self.assertEqual([], list(target._iter_changes(revision_tree)))
 
1584
 
 
1585
    def test_build_tree_accelerator_tree_missing_file(self):
 
1586
        source = self.make_branch_and_tree('source')
 
1587
        self.build_tree_contents([('source/file1', 'A')])
 
1588
        self.build_tree_contents([('source/file2', 'B')])
 
1589
        source.add(['file1', 'file2'])
 
1590
        source.commit('commit files')
 
1591
        os.unlink('source/file1')
 
1592
        source.remove(['file2'])
 
1593
        target = self.make_branch_and_tree('target')
 
1594
        revision_tree = source.basis_tree()
 
1595
        revision_tree.lock_read()
 
1596
        self.addCleanup(revision_tree.unlock)
 
1597
        build_tree(revision_tree, target, source)
 
1598
        target.lock_read()
 
1599
        self.addCleanup(target.unlock)
 
1600
        self.assertEqual([], list(target._iter_changes(revision_tree)))
 
1601
 
 
1602
    def test_build_tree_accelerator_wrong_kind(self):
 
1603
        self.requireFeature(SymlinkFeature)
 
1604
        source = self.make_branch_and_tree('source')
 
1605
        self.build_tree_contents([('source/file1', '')])
 
1606
        self.build_tree_contents([('source/file2', '')])
 
1607
        source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
 
1608
        source.commit('commit files')
 
1609
        os.unlink('source/file2')
 
1610
        self.build_tree_contents([('source/file2/', 'C')])
 
1611
        os.unlink('source/file1')
 
1612
        os.symlink('file2', 'source/file1')
 
1613
        calls = []
 
1614
        real_source_get_file = source.get_file
 
1615
        def get_file(file_id, path=None):
 
1616
            calls.append(file_id)
 
1617
            return real_source_get_file(file_id, path)
 
1618
        source.get_file = get_file
 
1619
        source.lock_read()
 
1620
        self.addCleanup(source.unlock)
 
1621
        target = self.make_branch_and_tree('target')
 
1622
        revision_tree = source.basis_tree()
 
1623
        revision_tree.lock_read()
 
1624
        self.addCleanup(revision_tree.unlock)
 
1625
        build_tree(revision_tree, target, source)
 
1626
        self.assertEqual([], calls)
 
1627
        target.lock_read()
 
1628
        self.addCleanup(target.unlock)
 
1629
        self.assertEqual([], list(target._iter_changes(revision_tree)))
 
1630
 
 
1631
    def test_build_tree_accelerator_tree_moved(self):
 
1632
        source = self.make_branch_and_tree('source')
 
1633
        self.build_tree_contents([('source/file1', 'A')])
 
1634
        source.add(['file1'], ['file1-id'])
 
1635
        source.commit('commit files')
 
1636
        source.rename_one('file1', 'file2')
 
1637
        source.lock_read()
 
1638
        self.addCleanup(source.unlock)
 
1639
        target = self.make_branch_and_tree('target')
 
1640
        revision_tree = source.basis_tree()
 
1641
        revision_tree.lock_read()
 
1642
        self.addCleanup(revision_tree.unlock)
 
1643
        build_tree(revision_tree, target, source)
 
1644
        target.lock_read()
 
1645
        self.addCleanup(target.unlock)
 
1646
        self.assertEqual([], list(target._iter_changes(revision_tree)))
 
1647
 
 
1648
 
 
1649
class MockTransform(object):
 
1650
 
 
1651
    def has_named_child(self, by_parent, parent_id, name):
 
1652
        for child_id in by_parent[parent_id]:
 
1653
            if child_id == '0':
 
1654
                if name == "name~":
 
1655
                    return True
 
1656
            elif name == "name.~%s~" % child_id:
 
1657
                return True
 
1658
        return False
 
1659
 
 
1660
 
 
1661
class MockEntry(object):
 
1662
    def __init__(self):
 
1663
        object.__init__(self)
 
1664
        self.name = "name"
 
1665
 
 
1666
 
 
1667
class TestGetBackupName(TestCase):
 
1668
    def test_get_backup_name(self):
 
1669
        tt = MockTransform()
 
1670
        name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
 
1671
        self.assertEqual(name, 'name.~1~')
 
1672
        name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
 
1673
        self.assertEqual(name, 'name.~2~')
 
1674
        name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
 
1675
        self.assertEqual(name, 'name.~1~')
 
1676
        name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
 
1677
        self.assertEqual(name, 'name.~1~')
 
1678
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
 
1679
        self.assertEqual(name, 'name.~4~')
 
1680
 
 
1681
 
 
1682
class TestFileMover(tests.TestCaseWithTransport):
 
1683
 
 
1684
    def test_file_mover(self):
 
1685
        self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
 
1686
        mover = _FileMover()
 
1687
        mover.rename('a', 'q')
 
1688
        self.failUnlessExists('q')
 
1689
        self.failIfExists('a')
 
1690
        self.failUnlessExists('q/b')
 
1691
        self.failUnlessExists('c')
 
1692
        self.failUnlessExists('c/d')
 
1693
 
 
1694
    def test_pre_delete_rollback(self):
 
1695
        self.build_tree(['a/'])
 
1696
        mover = _FileMover()
 
1697
        mover.pre_delete('a', 'q')
 
1698
        self.failUnlessExists('q')
 
1699
        self.failIfExists('a')
 
1700
        mover.rollback()
 
1701
        self.failIfExists('q')
 
1702
        self.failUnlessExists('a')
 
1703
 
 
1704
    def test_apply_deletions(self):
 
1705
        self.build_tree(['a/', 'b/'])
 
1706
        mover = _FileMover()
 
1707
        mover.pre_delete('a', 'q')
 
1708
        mover.pre_delete('b', 'r')
 
1709
        self.failUnlessExists('q')
 
1710
        self.failUnlessExists('r')
 
1711
        self.failIfExists('a')
 
1712
        self.failIfExists('b')
 
1713
        mover.apply_deletions()
 
1714
        self.failIfExists('q')
 
1715
        self.failIfExists('r')
 
1716
        self.failIfExists('a')
 
1717
        self.failIfExists('b')
 
1718
 
 
1719
    def test_file_mover_rollback(self):
 
1720
        self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
 
1721
        mover = _FileMover()
 
1722
        mover.rename('c/d', 'c/f')
 
1723
        mover.rename('c/e', 'c/d')
 
1724
        try:
 
1725
            mover.rename('a', 'c')
 
1726
        except errors.FileExists, e:
 
1727
            mover.rollback()
 
1728
        self.failUnlessExists('a')
 
1729
        self.failUnlessExists('c/d')
 
1730
 
 
1731
 
 
1732
class Bogus(Exception):
 
1733
    pass
 
1734
 
 
1735
 
 
1736
class TestTransformRollback(tests.TestCaseWithTransport):
 
1737
 
 
1738
    class ExceptionFileMover(_FileMover):
 
1739
 
 
1740
        def __init__(self, bad_source=None, bad_target=None):
 
1741
            _FileMover.__init__(self)
 
1742
            self.bad_source = bad_source
 
1743
            self.bad_target = bad_target
 
1744
 
 
1745
        def rename(self, source, target):
 
1746
            if (self.bad_source is not None and
 
1747
                source.endswith(self.bad_source)):
 
1748
                raise Bogus
 
1749
            elif (self.bad_target is not None and
 
1750
                target.endswith(self.bad_target)):
 
1751
                raise Bogus
 
1752
            else:
 
1753
                _FileMover.rename(self, source, target)
 
1754
 
 
1755
    def test_rollback_rename(self):
 
1756
        tree = self.make_branch_and_tree('.')
 
1757
        self.build_tree(['a/', 'a/b'])
 
1758
        tt = TreeTransform(tree)
 
1759
        self.addCleanup(tt.finalize)
 
1760
        a_id = tt.trans_id_tree_path('a')
 
1761
        tt.adjust_path('c', tt.root, a_id)
 
1762
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
1763
        self.assertRaises(Bogus, tt.apply,
 
1764
                          _mover=self.ExceptionFileMover(bad_source='a'))
 
1765
        self.failUnlessExists('a')
 
1766
        self.failUnlessExists('a/b')
 
1767
        tt.apply()
 
1768
        self.failUnlessExists('c')
 
1769
        self.failUnlessExists('c/d')
 
1770
 
 
1771
    def test_rollback_rename_into_place(self):
 
1772
        tree = self.make_branch_and_tree('.')
 
1773
        self.build_tree(['a/', 'a/b'])
 
1774
        tt = TreeTransform(tree)
 
1775
        self.addCleanup(tt.finalize)
 
1776
        a_id = tt.trans_id_tree_path('a')
 
1777
        tt.adjust_path('c', tt.root, a_id)
 
1778
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
1779
        self.assertRaises(Bogus, tt.apply,
 
1780
                          _mover=self.ExceptionFileMover(bad_target='c/d'))
 
1781
        self.failUnlessExists('a')
 
1782
        self.failUnlessExists('a/b')
 
1783
        tt.apply()
 
1784
        self.failUnlessExists('c')
 
1785
        self.failUnlessExists('c/d')
 
1786
 
 
1787
    def test_rollback_deletion(self):
 
1788
        tree = self.make_branch_and_tree('.')
 
1789
        self.build_tree(['a/', 'a/b'])
 
1790
        tt = TreeTransform(tree)
 
1791
        self.addCleanup(tt.finalize)
 
1792
        a_id = tt.trans_id_tree_path('a')
 
1793
        tt.delete_contents(a_id)
 
1794
        tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
 
1795
        self.assertRaises(Bogus, tt.apply,
 
1796
                          _mover=self.ExceptionFileMover(bad_target='d'))
 
1797
        self.failUnlessExists('a')
 
1798
        self.failUnlessExists('a/b')
 
1799
 
 
1800
    def test_resolve_no_parent(self):
 
1801
        wt = self.make_branch_and_tree('.')
 
1802
        tt = TreeTransform(wt)
 
1803
        self.addCleanup(tt.finalize)
 
1804
        parent = tt.trans_id_file_id('parent-id')
 
1805
        tt.new_file('file', parent, 'Contents')
 
1806
        resolve_conflicts(tt)